From 445fd37bef2c9d086369f19eed878e9be703cf31 Mon Sep 17 00:00:00 2001 From: Jaden Weiss Date: Mon, 13 Apr 2020 13:47:55 -0400 Subject: [PATCH 01/82] main: update version for beginning of v0.14 development cycle --- version.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.go b/version.go index 44e4994c39..0d35efa1f8 100644 --- a/version.go +++ b/version.go @@ -2,4 +2,4 @@ package main // version of this package. // Update this value before release of new version of software. -const version = "0.13.1" +const version = "0.14.0-dev" From f66492a3389ee1655e54826ed5ca74aa144721bb Mon Sep 17 00:00:00 2001 From: Yannis Huber Date: Wed, 22 Apr 2020 20:32:46 +0200 Subject: [PATCH 02/82] Fix return address in scheduler --- src/runtime/scheduler_tinygoriscv.S | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/runtime/scheduler_tinygoriscv.S b/src/runtime/scheduler_tinygoriscv.S index 60924f1d0d..3766bae754 100644 --- a/src/runtime/scheduler_tinygoriscv.S +++ b/src/runtime/scheduler_tinygoriscv.S @@ -22,6 +22,9 @@ tinygo_scanCurrentStack: mv a0, sp call tinygo_scanstack + // Restore return address. + lw ra, 60(sp) + // Restore stack state. addi sp, sp, 64 From fc4857e98c1c315a92ed687bc45ff895f0eb9600 Mon Sep 17 00:00:00 2001 From: Ayke van Laethem Date: Sat, 4 Apr 2020 14:58:56 +0200 Subject: [PATCH 03/82] runtime: avoid recursion in printuint64 function This function is called from runtime.printitf, which is called from runtime._panic, and is therefore the leaf function of many call paths. This makes analyzing stack usage very difficult. Also forwarding printuint32 to printuint64 as it reduces code size in the few examples I've tested. Printing numbers is not often done so it doesn't matter if it's a bit slow (the serial connection is probably slower anyway). --- src/runtime/print.go | 36 ++++++++++++++++-------------------- 1 file changed, 16 insertions(+), 20 deletions(-) diff --git a/src/runtime/print.go b/src/runtime/print.go index 37a6a58214..e4d715176b 100644 --- a/src/runtime/print.go +++ b/src/runtime/print.go @@ -47,23 +47,8 @@ func printint16(n int16) { printint32(int32(n)) } -//go:nobounds func printuint32(n uint32) { - digits := [10]byte{} // enough to hold (2^32)-1 - // Fill in all 10 digits. - firstdigit := 9 // digit index that isn't zero (by default, the last to handle '0' correctly) - for i := 9; i >= 0; i-- { - digit := byte(n%10 + '0') - digits[i] = digit - if digit != '0' { - firstdigit = i - } - n /= 10 - } - // Print digits without the leading zeroes. - for i := firstdigit; i < 10; i++ { - putchar(digits[i]) - } + printuint64(uint64(n)) } func printint32(n int32) { @@ -76,12 +61,23 @@ func printint32(n int32) { printuint32(uint32(n)) } +//go:nobounds func printuint64(n uint64) { - prevdigits := n / 10 - if prevdigits != 0 { - printuint64(prevdigits) + digits := [20]byte{} // enough to hold (2^64)-1 + // Fill in all 10 digits. + firstdigit := 19 // digit index that isn't zero (by default, the last to handle '0' correctly) + for i := 19; i >= 0; i-- { + digit := byte(n%10 + '0') + digits[i] = digit + if digit != '0' { + firstdigit = i + } + n /= 10 + } + // Print digits without the leading zeroes. + for i := firstdigit; i < 20; i++ { + putchar(digits[i]) } - putchar(byte((n % 10) + '0')) } func printint64(n int64) { From 9342e73ae158226b6c7dddb887906427aa0493dc Mon Sep 17 00:00:00 2001 From: Ayke van Laethem Date: Mon, 13 Apr 2020 18:07:10 +0200 Subject: [PATCH 04/82] builder: fix picolibc include path Previously we used --sysroot to set the sysroot explicitly. Unfortunately, this flag is not used directly by Clang to set the include path (/include) but is instead interpreted by the toolchain code. This means that even when the toolchain is explicitly set (using the --sysroot parameter), it may still decide to use a different include path such as /usr/include (such as on baremetal aarch64). This commit uses the Clang-internal -internal-isystem flag which sets the include directory directly (as a system include path). This should be more robust. The reason the --sysroot parameter has so far worked is that all existing targets happened to add /include as an include path. The relevant Clang code is here: https://github.com/llvm/llvm-project/blob/release/9.x/clang/lib/Driver/Driver.cpp#L4693-L4739 So far, RISC-V is handled by RISCVToolchain, Cortex-M targets by BareMetal (which seems to be specific to ARM unlike what the name says) and aarch64 fell back to Generic_ELF. --- builder/picolibc.go | 2 +- compileopts/config.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/builder/picolibc.go b/builder/picolibc.go index d3a095d636..436c34815a 100644 --- a/builder/picolibc.go +++ b/builder/picolibc.go @@ -12,7 +12,7 @@ var Picolibc = Library{ name: "picolibc", cflags: func() []string { picolibcDir := filepath.Join(goenv.Get("TINYGOROOT"), "lib/picolibc/newlib/libc") - return []string{"-Werror", "-Wall", "-std=gnu11", "-D_COMPILING_NEWLIB", "--sysroot=" + picolibcDir, "-I" + picolibcDir + "/tinystdio", "-I" + goenv.Get("TINYGOROOT") + "/lib/picolibc-include"} + return []string{"-Werror", "-Wall", "-std=gnu11", "-D_COMPILING_NEWLIB", "-nostdlibinc", "-Xclang", "-internal-isystem", "-Xclang", picolibcDir + "/include", "-I" + picolibcDir + "/tinystdio", "-I" + goenv.Get("TINYGOROOT") + "/lib/picolibc-include"} }, sourceDir: "lib/picolibc/newlib/libc", sources: func(target string) []string { diff --git a/compileopts/config.go b/compileopts/config.go index 9ec71bf8f7..435dda80a3 100644 --- a/compileopts/config.go +++ b/compileopts/config.go @@ -173,7 +173,7 @@ func (c *Config) CFlags() []string { } if c.Target.Libc == "picolibc" { root := goenv.Get("TINYGOROOT") - cflags = append(cflags, "--sysroot="+filepath.Join(root, "lib", "picolibc", "newlib", "libc")) + cflags = append(cflags, "-nostdlibinc", "-Xclang", "-internal-isystem", "-Xclang", filepath.Join(root, "lib", "picolibc", "newlib", "libc", "include")) cflags = append(cflags, "-I"+filepath.Join(root, "lib/picolibc-include")) } return cflags From 6389e45d992c134ffc749e44be8fe19473bb33d1 Mon Sep 17 00:00:00 2001 From: Ayke van Laethem Date: Mon, 6 Apr 2020 17:06:34 +0200 Subject: [PATCH 05/82] all: replace ReadRegister with AsmFull inline assembly This makes AsmFull more powerful (by supporting return values) and avoids a compiler builtin. --- compiler/compiler.go | 2 - compiler/inlineasm.go | 82 ++++++++++++++++----------------- src/device/arm/arm.go | 9 ++-- src/device/avr/avr.go | 5 +- src/device/riscv/riscv.go | 17 +++++-- src/runtime/arch_arm.go | 2 +- src/runtime/arch_cortexm.go | 2 +- src/runtime/arch_tinygoriscv.go | 2 +- 8 files changed, 66 insertions(+), 55 deletions(-) diff --git a/compiler/compiler.go b/compiler/compiler.go index 6ab553b43c..e4cd8d4bd1 100644 --- a/compiler/compiler.go +++ b/compiler/compiler.go @@ -1364,8 +1364,6 @@ 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 == "device/arm.ReadRegister" || name == "device/riscv.ReadRegister": - return b.createReadRegister(name, instr.Args) case name == "device/arm.Asm" || name == "device/avr.Asm" || name == "device/riscv.Asm": return b.createInlineAsm(instr.Args) case name == "device/arm.AsmFull" || name == "device/avr.AsmFull" || name == "device/riscv.AsmFull": diff --git a/compiler/inlineasm.go b/compiler/inlineasm.go index 2727228a18..6089f096a7 100644 --- a/compiler/inlineasm.go +++ b/compiler/inlineasm.go @@ -13,27 +13,6 @@ import ( "tinygo.org/x/go-llvm" ) -// This is a compiler builtin, which reads the given register by name: -// -// func ReadRegister(name string) uintptr -// -// The register name must be a constant, for example "sp". -func (b *builder) createReadRegister(name string, args []ssa.Value) (llvm.Value, error) { - fnType := llvm.FunctionType(b.uintptrType, []llvm.Type{}, false) - regname := constant.StringVal(args[0].(*ssa.Const).Value) - var asm string - switch name { - case "device/arm.ReadRegister": - asm = "mov $0, " + regname - case "device/riscv.ReadRegister": - asm = "mv $0, " + regname - default: - panic("unknown architecture") - } - target := llvm.InlineAsm(fnType, asm, "=r", false, false, 0) - return b.CreateCall(target, nil, ""), nil -} - // This is a compiler builtin, which emits a piece of inline assembly with no // operands or return values. It is useful for trivial instructions, like wfi in // ARM or sleep in AVR. @@ -52,7 +31,7 @@ func (b *builder) createInlineAsm(args []ssa.Value) (llvm.Value, error) { // This is a compiler builtin, which allows assembly to be called in a flexible // way. // -// func AsmFull(asm string, regs map[string]interface{}) +// func AsmFull(asm string, regs map[string]interface{}) uintptr // // The asm parameter must be a constant string. The regs parameter must be // provided immediately. For example: @@ -66,24 +45,24 @@ func (b *builder) createInlineAsm(args []ssa.Value) (llvm.Value, error) { func (b *builder) createInlineAsmFull(instr *ssa.CallCommon) (llvm.Value, error) { asmString := constant.StringVal(instr.Args[0].(*ssa.Const).Value) registers := map[string]llvm.Value{} - registerMap := instr.Args[1].(*ssa.MakeMap) - for _, r := range *registerMap.Referrers() { - switch r := r.(type) { - case *ssa.DebugRef: - // ignore - case *ssa.MapUpdate: - if r.Block() != registerMap.Block() { - return llvm.Value{}, b.makeError(instr.Pos(), "register value map must be created in the same basic block") - } - key := constant.StringVal(r.Key.(*ssa.Const).Value) - //println("value:", r.Value.(*ssa.MakeInterface).X.String()) - registers[key] = b.getValue(r.Value.(*ssa.MakeInterface).X) - case *ssa.Call: - if r.Common() == instr { - break + if registerMap, ok := instr.Args[1].(*ssa.MakeMap); ok { + for _, r := range *registerMap.Referrers() { + switch r := r.(type) { + case *ssa.DebugRef: + // ignore + case *ssa.MapUpdate: + if r.Block() != registerMap.Block() { + return llvm.Value{}, b.makeError(instr.Pos(), "register value map must be created in the same basic block") + } + key := constant.StringVal(r.Key.(*ssa.Const).Value) + registers[key] = b.getValue(r.Value.(*ssa.MakeInterface).X) + case *ssa.Call: + if r.Common() == instr { + break + } + default: + return llvm.Value{}, b.makeError(instr.Pos(), "don't know how to handle argument to inline assembly: "+r.String()) } - default: - return llvm.Value{}, b.makeError(instr.Pos(), "don't know how to handle argument to inline assembly: "+r.String()) } } // TODO: handle dollar signs in asm string @@ -92,6 +71,15 @@ func (b *builder) createInlineAsmFull(instr *ssa.CallCommon) (llvm.Value, error) argTypes := []llvm.Type{} args := []llvm.Value{} constraints := []string{} + hasOutput := false + asmString = regexp.MustCompile("\\{\\}").ReplaceAllStringFunc(asmString, func(s string) string { + hasOutput = true + return "$0" + }) + if hasOutput { + constraints = append(constraints, "=&r") + registerNumbers[""] = 0 + } asmString = regexp.MustCompile("\\{[a-zA-Z]+\\}").ReplaceAllStringFunc(asmString, func(s string) string { // TODO: skip strings like {r4} etc. that look like ARM push/pop // instructions. @@ -121,9 +109,21 @@ func (b *builder) createInlineAsmFull(instr *ssa.CallCommon) (llvm.Value, error) if err != nil { return llvm.Value{}, err } - fnType := llvm.FunctionType(b.ctx.VoidType(), argTypes, false) + var outputType llvm.Type + if hasOutput { + outputType = b.uintptrType + } else { + outputType = b.ctx.VoidType() + } + fnType := llvm.FunctionType(outputType, argTypes, false) target := llvm.InlineAsm(fnType, asmString, strings.Join(constraints, ","), true, false, 0) - return b.CreateCall(target, args, ""), nil + result := b.CreateCall(target, args, "") + if hasOutput { + return result, nil + } else { + // Make sure we return something valid. + return llvm.ConstInt(b.uintptrType, 0, false), nil + } } // This is a compiler builtin which emits an inline SVCall instruction. It can diff --git a/src/device/arm/arm.go b/src/device/arm/arm.go index 21ba3bbd95..ac11199e75 100644 --- a/src/device/arm/arm.go +++ b/src/device/arm/arm.go @@ -52,11 +52,10 @@ func Asm(asm string) // "value": 1 // "result": &dest, // }) -func AsmFull(asm string, regs map[string]interface{}) - -// ReadRegister returns the contents of the specified register. The register -// must be a processor register, reachable with the "mov" instruction. -func ReadRegister(name string) uintptr +// +// You can use {} in the asm string (which expands to a register) to set the +// return value. +func AsmFull(asm string, regs map[string]interface{}) uintptr // Run the following system call (SVCall) with 0 arguments. func SVCall0(num uintptr) uintptr diff --git a/src/device/avr/avr.go b/src/device/avr/avr.go index 7fa6bf9552..12f145dd7d 100644 --- a/src/device/avr/avr.go +++ b/src/device/avr/avr.go @@ -15,4 +15,7 @@ func Asm(asm string) // "value": 1 // "result": &dest, // }) -func AsmFull(asm string, regs map[string]interface{}) +// +// You can use {} in the asm string (which expands to a register) to set the +// return value. +func AsmFull(asm string, regs map[string]interface{}) uintptr diff --git a/src/device/riscv/riscv.go b/src/device/riscv/riscv.go index 947b3f181c..3a2e6cc43e 100644 --- a/src/device/riscv/riscv.go +++ b/src/device/riscv/riscv.go @@ -5,6 +5,17 @@ package riscv // optimizer. func Asm(asm string) -// ReadRegister returns the contents of the specified register. The register -// must be a processor register, reachable with the "mov" instruction. -func ReadRegister(name string) uintptr +// Run the given inline assembly. The code will be marked as having side +// effects, as it would otherwise be optimized away. The inline assembly string +// recognizes template values in the form {name}, like so: +// +// arm.AsmFull( +// "st {value}, {result}", +// map[string]interface{}{ +// "value": 1 +// "result": &dest, +// }) +// +// You can use {} in the asm string (which expands to a register) to set the +// return value. +func AsmFull(asm string, regs map[string]interface{}) uintptr diff --git a/src/runtime/arch_arm.go b/src/runtime/arch_arm.go index 995e6d2469..0a98f6d532 100644 --- a/src/runtime/arch_arm.go +++ b/src/runtime/arch_arm.go @@ -15,5 +15,5 @@ func align(ptr uintptr) uintptr { } func getCurrentStackPointer() uintptr { - return arm.ReadRegister("sp") + return arm.AsmFull("mov {}, sp", nil) } diff --git a/src/runtime/arch_cortexm.go b/src/runtime/arch_cortexm.go index 7a0aeffdc4..17b824087b 100644 --- a/src/runtime/arch_cortexm.go +++ b/src/runtime/arch_cortexm.go @@ -17,5 +17,5 @@ func align(ptr uintptr) uintptr { } func getCurrentStackPointer() uintptr { - return arm.ReadRegister("sp") + return arm.AsmFull("mov {}, sp", nil) } diff --git a/src/runtime/arch_tinygoriscv.go b/src/runtime/arch_tinygoriscv.go index 1c5d8b78f4..242f9b9691 100644 --- a/src/runtime/arch_tinygoriscv.go +++ b/src/runtime/arch_tinygoriscv.go @@ -15,5 +15,5 @@ func align(ptr uintptr) uintptr { } func getCurrentStackPointer() uintptr { - return riscv.ReadRegister("sp") + return riscv.AsmFull("mv {}, sp", nil) } From 23e88bfb156b1e3ac69b6bf7e9f8431ab94791c8 Mon Sep 17 00:00:00 2001 From: Ayke van Laethem Date: Mon, 6 Apr 2020 17:30:56 +0200 Subject: [PATCH 06/82] arm: allow nesting in DisableInterrupts and EnableInterrupts This finally fixes a TODO left in the code. --- src/device/arm/arm.go | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/src/device/arm/arm.go b/src/device/arm/arm.go index ac11199e75..a806bbc6b1 100644 --- a/src/device/arm/arm.go +++ b/src/device/arm/arm.go @@ -191,22 +191,21 @@ func SetPriority(irq uint32, priority uint32) { NVIC.IPR[regnum].Set((uint32(NVIC.IPR[regnum].Get()) &^ mask) | priority) } -// DisableInterrupts disables all interrupts, and returns the old state. -// -// TODO: it doesn't actually return the old state, meaning that it cannot be -// nested. +// DisableInterrupts disables all interrupts, and returns the old interrupt +// state. func DisableInterrupts() uintptr { - Asm("cpsid if") - return 0 + return AsmFull(` + mrs {}, PRIMASK + cpsid if + `, nil) } // EnableInterrupts enables all interrupts again. The value passed in must be // the mask returned by DisableInterrupts. -// -// TODO: it doesn't actually use the old state, meaning that it cannot be -// nested. func EnableInterrupts(mask uintptr) { - Asm("cpsie if") + AsmFull("msr PRIMASK, {mask}", map[string]interface{}{ + "mask": mask, + }) } // SystemReset performs a hard system reset. From d0b2585e8257b80c69f3c3a31c28bedef5acc579 Mon Sep 17 00:00:00 2001 From: Ayke van Laethem Date: Mon, 4 May 2020 01:02:59 +0200 Subject: [PATCH 07/82] main: update go-llvm to fix macOS build problem --- go.mod | 2 +- go.sum | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/go.mod b/go.mod index b668cf69e1..a2cbe4a787 100644 --- a/go.mod +++ b/go.mod @@ -9,5 +9,5 @@ require ( go.bug.st/serial v1.0.0 golang.org/x/tools v0.0.0-20200216192241-b320d3a0f5a2 google.golang.org/appengine v1.4.0 // indirect - tinygo.org/x/go-llvm v0.0.0-20200401165421-8d120882fc7a + tinygo.org/x/go-llvm v0.0.0-20200503225853-345b2947b59d ) diff --git a/go.sum b/go.sum index 3133c874f8..09327350e7 100644 --- a/go.sum +++ b/go.sum @@ -57,3 +57,5 @@ tinygo.org/x/go-llvm v0.0.0-20200226165415-53522ab6713d h1:mtgZh/e8a3wxneQFuLXoQ tinygo.org/x/go-llvm v0.0.0-20200226165415-53522ab6713d/go.mod h1:fv1F0BSNpxMfCL0zF3M4OPFbgYHnhtB6ST0HvUtu/LE= tinygo.org/x/go-llvm v0.0.0-20200401165421-8d120882fc7a h1:Ugje2Lxuv8CFncHzs5W+hWfJvPsM+W4K0zRvzFbLvoE= tinygo.org/x/go-llvm v0.0.0-20200401165421-8d120882fc7a/go.mod h1:fv1F0BSNpxMfCL0zF3M4OPFbgYHnhtB6ST0HvUtu/LE= +tinygo.org/x/go-llvm v0.0.0-20200503225853-345b2947b59d h1:hcX7vpB067GWM/EH4sGGOti0PMgIx+0bbZwUXctOIvE= +tinygo.org/x/go-llvm v0.0.0-20200503225853-345b2947b59d/go.mod h1:fv1F0BSNpxMfCL0zF3M4OPFbgYHnhtB6ST0HvUtu/LE= From 904fa852f664787cd9c6e6fc3e634155c9886d70 Mon Sep 17 00:00:00 2001 From: Ayke van Laethem Date: Sun, 3 May 2020 23:40:02 +0200 Subject: [PATCH 08/82] transform: fix debug information in func lowering pass This commit fixes errors like the following: inlinable function call in a function with debug info must have a !dbg location call void @runtime.nilPanic(i8* undef, i8* null) inlinable function call in a function with debug info must have a !dbg location %24 = call fastcc %runtime._interface @"(*github.com/vugu/vugu/domrender.JSRenderer).render$1"(%"github.com/vugu/vugu.VGNode"** %19, i32 %22, i32 %23, i8* %15, i8* undef) error: optimizations caused a verification failure Not all instructions had a debug location, which apparently caused issues for the inliner. --- transform/func-lowering.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/transform/func-lowering.go b/transform/func-lowering.go index caef38427f..03d1f440d0 100644 --- a/transform/func-lowering.go +++ b/transform/func-lowering.go @@ -225,6 +225,10 @@ func addFuncLoweringSwitch(mod llvm.Module, builder llvm.Builder, funcID, call l // in this gap. nextBlock := llvmutil.SplitBasicBlock(builder, sw, llvm.NextBasicBlock(sw.InstructionParent()), "func.next") + // Temporarily set the insert point to set the correct debug insert location + // for the builder. It got destroyed by the SplitBasicBlock call. + builder.SetInsertPointBefore(call) + // The 0 case, which is actually a nil check. nilBlock := ctx.InsertBasicBlock(nextBlock, "func.nil") builder.SetInsertPointAtEnd(nilBlock) From 8ce3cfad4014a23dec3bc44a67c7181c5f6f108d Mon Sep 17 00:00:00 2001 From: sago35 Date: Thu, 7 May 2020 09:08:56 +0900 Subject: [PATCH 09/82] flash: fix getDefaultPort() fails on Windows locales such as Japan --- main.go | 21 +++++---------------- 1 file changed, 5 insertions(+), 16 deletions(-) diff --git a/main.go b/main.go index 9af1a76d11..976591a93e 100644 --- a/main.go +++ b/main.go @@ -603,29 +603,18 @@ func getDefaultPort() (port string, err error) { case "freebsd": portPath = "/dev/cuaU*" case "windows": - cmd := exec.Command("wmic", - "PATH", "Win32_SerialPort", "WHERE", "Caption LIKE 'USB Serial%'", "GET", "DeviceID") - - var out bytes.Buffer - cmd.Stdout = &out - err := cmd.Run() + ports, err := serial.GetPortsList() if err != nil { return "", err } - if out.String() == "No Instance(s) Available." { + if len(ports) == 0 { return "", errors.New("no serial ports available") + } else if len(ports) > 1 { + return "", errors.New("multiple serial ports available - use -port flag") } - for _, line := range strings.Split(out.String(), "\n") { - words := strings.Fields(line) - if len(words) == 1 { - if strings.Contains(words[0], "COM") { - return words[0], nil - } - } - } - return "", errors.New("unable to locate a serial port") + return ports[0], nil default: return "", errors.New("unable to search for a default USB device to be flashed on this OS") } From 5ed0f67e1cbce113e1f0d53f64708cd840ab7172 Mon Sep 17 00:00:00 2001 From: sago35 Date: Fri, 8 May 2020 06:04:10 +0900 Subject: [PATCH 10/82] sam: fix ROM / RAM size on atsamd51j20 --- targets/atsamd51j20a.json | 2 +- targets/atsamd51j20a.ld | 10 ++++++++++ 2 files changed, 11 insertions(+), 1 deletion(-) create mode 100644 targets/atsamd51j20a.ld diff --git a/targets/atsamd51j20a.json b/targets/atsamd51j20a.json index cab3a624ab..cd8b431cb0 100644 --- a/targets/atsamd51j20a.json +++ b/targets/atsamd51j20a.json @@ -6,7 +6,7 @@ "--target=armv7em-none-eabi", "-Qunused-arguments" ], - "linkerscript": "targets/atsamd51.ld", + "linkerscript": "targets/atsamd51j20a.ld", "extra-files": [ "src/device/sam/atsamd51j20a.s" ] diff --git a/targets/atsamd51j20a.ld b/targets/atsamd51j20a.ld new file mode 100644 index 0000000000..5b8ce97190 --- /dev/null +++ b/targets/atsamd51j20a.ld @@ -0,0 +1,10 @@ + +MEMORY +{ + FLASH_TEXT (rw) : ORIGIN = 0x00000000+0x4000, LENGTH = 0x00100000-0x4000 /* First 16KB used by bootloader */ + RAM (xrw) : ORIGIN = 0x20000000, LENGTH = 0x00040000 +} + +_stack_size = 4K; + +INCLUDE "targets/arm.ld" From 171d0fe123b006003032d21885e81df7ceb50623 Mon Sep 17 00:00:00 2001 From: Jaden Weiss Date: Mon, 27 Jan 2020 16:22:46 -0500 Subject: [PATCH 11/82] implement mutex blocking --- src/sync/mutex.go | 25 ++++++++++++++++++++++--- 1 file changed, 22 insertions(+), 3 deletions(-) diff --git a/src/sync/mutex.go b/src/sync/mutex.go index 4e89285f93..20452dec88 100644 --- a/src/sync/mutex.go +++ b/src/sync/mutex.go @@ -1,16 +1,29 @@ package sync +import ( + "internal/task" + _ "unsafe" +) + // These mutexes assume there is only one thread of operation: no goroutines, // interrupts or anything else. type Mutex struct { - locked bool + locked bool + blocked task.Stack } +//go:linkname scheduleTask runtime.runqueuePushBack +func scheduleTask(*task.Task) + func (m *Mutex) Lock() { if m.locked { - panic("todo: block on locked mutex") + // Push self onto stack of blocked tasks, and wait to be resumed. + m.blocked.Push(task.Current()) + task.Pause() + return } + m.locked = true } @@ -18,7 +31,13 @@ func (m *Mutex) Unlock() { if !m.locked { panic("sync: unlock of unlocked Mutex") } - m.locked = false + + // Wake up a blocked task, if applicable. + if t := m.blocked.Pop(); t != nil { + scheduleTask(t) + } else { + m.locked = false + } } type RWMutex struct { From ccd79ee2899d5022418c024015236cd5ec1702b9 Mon Sep 17 00:00:00 2001 From: Jaden Weiss Date: Mon, 27 Jan 2020 17:46:37 -0500 Subject: [PATCH 12/82] add sync.Cond --- src/sync/cond.go | 80 +++++++++++++++++++++++++++++++++++++++++++++++ src/sync/mutex.go | 5 +++ 2 files changed, 85 insertions(+) create mode 100644 src/sync/cond.go diff --git a/src/sync/cond.go b/src/sync/cond.go new file mode 100644 index 0000000000..6f7c4be0e6 --- /dev/null +++ b/src/sync/cond.go @@ -0,0 +1,80 @@ +package sync + +import "internal/task" + +type Cond struct { + L Locker + + unlocking *earlySignal + blocked task.Stack +} + +// earlySignal is a type used to implement a stack for signalling waiters while they are unlocking. +type earlySignal struct { + next *earlySignal + + signaled bool +} + +func (c *Cond) trySignal() bool { + // Pop a blocked task off of the stack, and schedule it if applicable. + t := c.blocked.Pop() + if t != nil { + scheduleTask(t) + return true + } + + // If there any tasks which are currently unlocking, signal one. + if c.unlocking != nil { + c.unlocking.signaled = true + c.unlocking = c.unlocking.next + return true + } + + // There was nothing to signal. + return false +} + +func (c *Cond) Signal() { + c.trySignal() +} + +func (c *Cond) Broadcast() { + // Signal everything. + for c.trySignal() { + } +} + +func (c *Cond) Wait() { + // Add an earlySignal frame to the stack so we can be signalled while unlocking. + early := earlySignal{ + next: c.unlocking, + } + c.unlocking = &early + + // Temporarily unlock L. + c.L.Unlock() + defer c.L.Lock() + + // If we were signaled while unlocking, immediately complete. + if early.signaled { + return + } + + // Remove the earlySignal frame. + if c.unlocking == &early { + c.unlocking = early.next + } else { + // Something else happened after the unlock - the earlySignal is somewhere in the middle. + // This would be faster but less space-efficient if it were a doubly linked list. + prev := c.unlocking + for prev.next != &early { + prev = prev.next + } + prev.next = early.next + } + + // Wait for a signal. + c.blocked.Push(task.Current()) + task.Pause() +} diff --git a/src/sync/mutex.go b/src/sync/mutex.go index 20452dec88..acc02de3a3 100644 --- a/src/sync/mutex.go +++ b/src/sync/mutex.go @@ -69,3 +69,8 @@ func (rw *RWMutex) RUnlock() { rw.m.Unlock() } } + +type Locker interface { + Lock() + Unlock() +} From ae2cbbf851ddb9ff1879f5de8cb7d863bdb783dc Mon Sep 17 00:00:00 2001 From: Jaden Weiss Date: Tue, 17 Mar 2020 10:47:12 -0400 Subject: [PATCH 13/82] internal/task: fix nil panic in (*internal/task.Stack).Pop While adding some code to clear the Next field when popping from a task stack for safety reasons, the clear was placed outside of a nil pointer check. As a result, (*internal/task.Stack).Pop panicked when the Stack is empty. --- src/internal/task/queue.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/internal/task/queue.go b/src/internal/task/queue.go index c86bc596cb..4b0a22bf9c 100644 --- a/src/internal/task/queue.go +++ b/src/internal/task/queue.go @@ -68,8 +68,8 @@ func (s *Stack) Pop() *Task { t := s.top if t != nil { s.top = t.Next + t.Next = nil } - t.Next = nil return t } From afc6bd5cdd7f9a921342bde5834bb37e05dd205e Mon Sep 17 00:00:00 2001 From: Jaden Weiss Date: Tue, 17 Mar 2020 11:27:37 -0400 Subject: [PATCH 14/82] sync: add WaitGroup --- src/sync/waitgroup.go | 54 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 54 insertions(+) create mode 100644 src/sync/waitgroup.go diff --git a/src/sync/waitgroup.go b/src/sync/waitgroup.go new file mode 100644 index 0000000000..72ef24c809 --- /dev/null +++ b/src/sync/waitgroup.go @@ -0,0 +1,54 @@ +package sync + +import "internal/task" + +type WaitGroup struct { + counter uint + waiters task.Stack +} + +func (wg *WaitGroup) Add(delta int) { + if delta > 0 { + // Check for overflow. + if uint(delta) > (^uint(0))-wg.counter { + panic("sync: WaitGroup counter overflowed") + } + + // Add to the counter. + wg.counter += uint(delta) + } else { + // Check for underflow. + if uint(-delta) > wg.counter { + panic("sync: negative WaitGroup counter") + } + + // Subtract from the counter. + wg.counter -= uint(-delta) + + // If the counter is zero, everything is done and the waiters should be resumed. + // This code assumes that the waiters cannot wake up until after this function returns. + // In the current implementation, this is always correct. + if wg.counter == 0 { + for t := wg.waiters.Pop(); t != nil; t = wg.waiters.Pop() { + scheduleTask(t) + } + } + } +} + +func (wg *WaitGroup) Done() { + wg.Add(-1) +} + +func (wg *WaitGroup) Wait() { + if wg.counter == 0 { + // Everything already finished. + return + } + + // Push the current goroutine onto the waiter stack. + wg.waiters.Push(task.Current()) + + // Pause until the waiters are awoken by Add/Done. + task.Pause() +} From 7801921cc0ac53121b661452ce0edad00173fd97 Mon Sep 17 00:00:00 2001 From: Jaden Weiss Date: Tue, 17 Mar 2020 11:28:04 -0400 Subject: [PATCH 15/82] testdata: replace fake waitgroup in channel.go with sync.WaitGroup --- testdata/channel.go | 100 ++++++++++++++++---------------------------- 1 file changed, 36 insertions(+), 64 deletions(-) diff --git a/testdata/channel.go b/testdata/channel.go index 63011d50f6..52d498fcbc 100644 --- a/testdata/channel.go +++ b/testdata/channel.go @@ -2,45 +2,17 @@ package main import ( "runtime" + "sync" "time" ) -// waitGroup is a small type reimplementing some of the behavior of sync.WaitGroup -type waitGroup uint - -func (wg *waitGroup) wait() { - n := 0 - for *wg != 0 { - // pause and wait to be rescheduled - runtime.Gosched() - - if n > 100 { - // if something is using the sleep queue, this may be necessary - time.Sleep(time.Millisecond) - } - - n++ - } -} - -func (wg *waitGroup) add(n uint) { - *wg += waitGroup(n) -} - -func (wg *waitGroup) done() { - if *wg == 0 { - panic("wait group underflow") - } - *wg-- -} - -var wg waitGroup +var wg sync.WaitGroup func main() { ch := make(chan int) println("len, cap of channel:", len(ch), cap(ch), ch == nil) - wg.add(1) + wg.Add(1) go sender(ch) n, ok := <-ch @@ -50,7 +22,7 @@ func main() { println("received num:", n) } - wg.wait() + wg.Wait() n, ok = <-ch println("recv from closed channel:", n, ok) @@ -66,55 +38,55 @@ func main() { // Test bigger values ch2 := make(chan complex128) - wg.add(1) + wg.Add(1) go sendComplex(ch2) println("complex128:", <-ch2) - wg.wait() + wg.Wait() // Test multi-sender. ch = make(chan int) - wg.add(3) + wg.Add(3) go fastsender(ch, 10) go fastsender(ch, 23) go fastsender(ch, 40) slowreceiver(ch) - wg.wait() + wg.Wait() // Test multi-receiver. ch = make(chan int) - wg.add(3) + wg.Add(3) go fastreceiver(ch) go fastreceiver(ch) go fastreceiver(ch) slowsender(ch) - wg.wait() + wg.Wait() // Test iterator style channel. ch = make(chan int) - wg.add(1) + wg.Add(1) go iterator(ch, 100) sum := 0 for i := range ch { sum += i } - wg.wait() + wg.Wait() println("sum(100):", sum) // Test simple selects. go selectDeadlock() // cannot use waitGroup here - never terminates - wg.add(1) + wg.Add(1) go selectNoOp() - wg.wait() + wg.Wait() // Test select with a single send operation (transformed into chan send). ch = make(chan int) - wg.add(1) + wg.Add(1) go fastreceiver(ch) select { case ch <- 5: } close(ch) - wg.wait() + wg.Wait() println("did send one") // Test select with a single recv operation (transformed into chan recv). @@ -125,11 +97,11 @@ func main() { // Test select recv with channel that has one entry. ch = make(chan int) - wg.add(1) + wg.Add(1) go func(ch chan int) { runtime.Gosched() ch <- 55 - wg.done() + wg.Done() }(ch) select { case make(chan int) <- 3: @@ -141,7 +113,7 @@ func main() { case n := <-make(chan int): println("unreachable:", n) } - wg.wait() + wg.Wait() // Test select recv with closed channel. close(ch) @@ -156,7 +128,7 @@ func main() { // Test select send. ch = make(chan int) - wg.add(1) + wg.Add(1) go fastreceiver(ch) select { case ch <- 235: @@ -165,7 +137,7 @@ func main() { println("unreachable:", n) } close(ch) - wg.wait() + wg.Wait() // test non-concurrent buffered channels ch = make(chan int, 2) @@ -183,7 +155,7 @@ func main() { println("closed buffered channel recieve:", <-ch) // test using buffered channels as regular channels with special properties - wg.add(6) + wg.Add(6) ch = make(chan int, 2) go send(ch) go send(ch) @@ -191,7 +163,7 @@ func main() { go send(ch) go receive(ch) go receive(ch) - wg.wait() + wg.Wait() close(ch) var count int for range ch { @@ -204,19 +176,19 @@ func main() { sch1 := make(chan int) sch2 := make(chan int) sch3 := make(chan int) - wg.add(3) + wg.Add(3) go func() { - defer wg.done() + defer wg.Done() time.Sleep(time.Millisecond) sch1 <- 1 }() go func() { - defer wg.done() + defer wg.Done() time.Sleep(time.Millisecond) sch2 <- 2 }() go func() { - defer wg.done() + defer wg.Done() // merge sch2 and sch3 into ch for i := 0; i < 2; i++ { var v int @@ -240,18 +212,18 @@ func main() { sum += v } } - wg.wait() + wg.Wait() println("blocking select sum:", sum) } func send(ch chan<- int) { ch <- 1 - wg.done() + wg.Done() } func receive(ch <-chan int) { <-ch - wg.done() + wg.Done() } func sender(ch chan int) { @@ -263,18 +235,18 @@ func sender(ch chan int) { ch <- i } close(ch) - wg.done() + wg.Done() } func sendComplex(ch chan complex128) { ch <- 7 + 10.5i - wg.done() + wg.Done() } func fastsender(ch chan int, n int) { ch <- n ch <- n + 1 - wg.done() + wg.Done() } func slowreceiver(ch chan int) { @@ -300,7 +272,7 @@ func fastreceiver(ch chan int) { sum += n } println("sum:", sum) - wg.done() + wg.Done() } func iterator(ch chan int, top int) { @@ -308,7 +280,7 @@ func iterator(ch chan int, top int) { ch <- i } close(ch) - wg.done() + wg.Done() } func selectDeadlock() { @@ -323,5 +295,5 @@ func selectNoOp() { default: } println("after no-op") - wg.done() + wg.Done() } From c54e1cc9557cf16636dd3099c1a6742fe3fed260 Mon Sep 17 00:00:00 2001 From: Jaden Weiss Date: Thu, 26 Mar 2020 08:47:14 -0400 Subject: [PATCH 16/82] sync: modify sync.Cond --- src/sync/cond.go | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/src/sync/cond.go b/src/sync/cond.go index 6f7c4be0e6..e392bc6e2c 100644 --- a/src/sync/cond.go +++ b/src/sync/cond.go @@ -54,6 +54,8 @@ func (c *Cond) Wait() { // Temporarily unlock L. c.L.Unlock() + + // Re-acquire the lock before returning. defer c.L.Lock() // If we were signaled while unlocking, immediately complete. @@ -62,16 +64,14 @@ func (c *Cond) Wait() { } // Remove the earlySignal frame. - if c.unlocking == &early { - c.unlocking = early.next - } else { - // Something else happened after the unlock - the earlySignal is somewhere in the middle. - // This would be faster but less space-efficient if it were a doubly linked list. - prev := c.unlocking - for prev.next != &early { - prev = prev.next - } + prev := c.unlocking + for prev != nil && prev.next != &early { + prev = prev.next + } + if prev != nil { prev.next = early.next + } else { + c.unlocking = early.next } // Wait for a signal. From b4815192a6cb05e79fe70487c1f303b1b6f92026 Mon Sep 17 00:00:00 2001 From: Jaden Weiss Date: Fri, 27 Mar 2020 19:56:27 -0400 Subject: [PATCH 17/82] testdata, sync: add sync.Mutex test to testdata/coroutines.go --- testdata/coroutines.go | 28 +++++++++++++++++++++++++++- testdata/coroutines.txt | 6 ++++++ 2 files changed, 33 insertions(+), 1 deletion(-) diff --git a/testdata/coroutines.go b/testdata/coroutines.go index 77e14d0e0a..49fdfc2870 100644 --- a/testdata/coroutines.go +++ b/testdata/coroutines.go @@ -1,6 +1,9 @@ package main -import "time" +import ( + "sync" + "time" +) func main() { println("main 1") @@ -51,6 +54,29 @@ func main() { println("closure go call result:", x) time.Sleep(2 * time.Millisecond) + + var m sync.Mutex + m.Lock() + println("pre-acquired mutex") + go acquire(&m) + time.Sleep(2 * time.Millisecond) + println("releasing mutex") + m.Unlock() + time.Sleep(2 * time.Millisecond) + m.Lock() + println("re-acquired mutex") + m.Unlock() + println("done") + + time.Sleep(2 * time.Millisecond) +} + +func acquire(m *sync.Mutex) { + m.Lock() + println("acquired mutex from goroutine") + time.Sleep(2 * time.Millisecond) + m.Unlock() + println("released mutex from goroutine") } func sub() { diff --git a/testdata/coroutines.txt b/testdata/coroutines.txt index e296f8e0ce..1e29558afc 100644 --- a/testdata/coroutines.txt +++ b/testdata/coroutines.txt @@ -14,3 +14,9 @@ async interface method call slept inside func pointer 8 slept inside closure, with value: 20 8 closure go call result: 1 +pre-acquired mutex +releasing mutex +acquired mutex from goroutine +released mutex from goroutine +re-acquired mutex +done From 00f3a659038286877687eae40f48c341a8bdb0ab Mon Sep 17 00:00:00 2001 From: deadprogram Date: Sat, 9 May 2020 12:35:50 +0200 Subject: [PATCH 18/82] machine/arduino-nano33: use (U)SB flag to ensure that device can be found when not on default port Signed-off-by: deadprogram --- targets/arduino-nano33.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/targets/arduino-nano33.json b/targets/arduino-nano33.json index 788fa40769..5313562cf5 100644 --- a/targets/arduino-nano33.json +++ b/targets/arduino-nano33.json @@ -1,6 +1,6 @@ { "inherits": ["atsamd21g18a"], "build-tags": ["sam", "atsamd21g18a", "arduino_nano33"], - "flash-command": "bossac -d -i -e -w -v -R --port={port} --offset=0x2000 {bin}", + "flash-command": "bossac -d -i -e -w -v -R -U --port={port} --offset=0x2000 {bin}", "flash-1200-bps-reset": "true" } From 01f5c1d455c77670040408bccc1ca4c81b73603d Mon Sep 17 00:00:00 2001 From: deadprogram Date: Sat, 9 May 2020 12:57:11 +0200 Subject: [PATCH 19/82] machine/arduino-nano33: remove (d)ebug flag to reduce console noise when flashing Signed-off-by: deadprogram --- targets/arduino-nano33.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/targets/arduino-nano33.json b/targets/arduino-nano33.json index 5313562cf5..fe41338e67 100644 --- a/targets/arduino-nano33.json +++ b/targets/arduino-nano33.json @@ -1,6 +1,6 @@ { "inherits": ["atsamd21g18a"], "build-tags": ["sam", "atsamd21g18a", "arduino_nano33"], - "flash-command": "bossac -d -i -e -w -v -R -U --port={port} --offset=0x2000 {bin}", + "flash-command": "bossac -i -e -w -v -R -U --port={port} --offset=0x2000 {bin}", "flash-1200-bps-reset": "true" } From acdaa72365198167baba7dd7769bca69716a5b46 Mon Sep 17 00:00:00 2001 From: cornelk Date: Mon, 11 May 2020 17:35:03 +0300 Subject: [PATCH 20/82] runtime: fix compilation errors when using gc.extalloc --- src/runtime/gc_extalloc.go | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/runtime/gc_extalloc.go b/src/runtime/gc_extalloc.go index 95b06a4924..296ac7a096 100644 --- a/src/runtime/gc_extalloc.go +++ b/src/runtime/gc_extalloc.go @@ -602,3 +602,11 @@ func alloc(size uintptr) unsafe.Pointer { func free(ptr unsafe.Pointer) { // Currently unimplemented due to bugs in coroutine lowering. } + +func KeepAlive(x interface{}) { + // Unimplemented. Only required with SetFinalizer(). +} + +func SetFinalizer(obj interface{}, finalizer interface{}) { + // Unimplemented. +} From 1461563e3f4c6809049f123f5af6530a48db8274 Mon Sep 17 00:00:00 2001 From: cornelk Date: Mon, 11 May 2020 20:52:39 +0300 Subject: [PATCH 21/82] testdata: fix formatting --- testdata/float.go | 20 ++++++++++---------- testdata/init.go | 4 ++-- testdata/zeroalloc.go | 1 + 3 files changed, 13 insertions(+), 12 deletions(-) diff --git a/testdata/float.go b/testdata/float.go index 4063281b71..f2b7c52e32 100644 --- a/testdata/float.go +++ b/testdata/float.go @@ -58,14 +58,14 @@ func main() { println(complex128(c64)) // binops on complex numbers - c64 = 5+2i - println("complex64 add: ", c64 + -3+8i) - println("complex64 sub: ", c64 - -3+8i) - println("complex64 mul: ", c64 * -3+8i) - println("complex64 div: ", c64 / -3+8i) - c128 = -5+2i - println("complex128 add:", c128 + 2+6i) - println("complex128 sub:", c128 - 2+6i) - println("complex128 mul:", c128 * 2+6i) - println("complex128 div:", c128 / 2+6i) + c64 = 5 + 2i + println("complex64 add: ", c64+-3+8i) + println("complex64 sub: ", c64 - -3 + 8i) + println("complex64 mul: ", c64*-3+8i) + println("complex64 div: ", c64/-3+8i) + c128 = -5 + 2i + println("complex128 add:", c128+2+6i) + println("complex128 sub:", c128-2+6i) + println("complex128 mul:", c128*2+6i) + println("complex128 div:", c128/2+6i) } diff --git a/testdata/init.go b/testdata/init.go index 8f81668c53..6e7d9e7baf 100644 --- a/testdata/init.go +++ b/testdata/init.go @@ -38,8 +38,8 @@ var ( uint8SliceSrc = []uint8{3, 100} uint8SliceDst []uint8 - intSliceSrc = []int16{5, 123, 1024} - intSliceDst []int16 + intSliceSrc = []int16{5, 123, 1024} + intSliceDst []int16 ) func init() { diff --git a/testdata/zeroalloc.go b/testdata/zeroalloc.go index 53c6fa0bcb..e5893227fa 100644 --- a/testdata/zeroalloc.go +++ b/testdata/zeroalloc.go @@ -1,4 +1,5 @@ package main + func main() { p := []byte{} for len(p) >= 1 { From 7e64bc8f779994eec060f7cbe6325e9787c1a827 Mon Sep 17 00:00:00 2001 From: cornelk Date: Mon, 11 May 2020 20:54:22 +0300 Subject: [PATCH 22/82] runtime: add cap and len support for chans --- compiler/compiler.go | 8 ++------ src/runtime/chan.go | 32 ++++++++++++++++++++++++++++++++ testdata/channel.go | 6 +++++- testdata/channel.txt | 1 + 4 files changed, 40 insertions(+), 7 deletions(-) diff --git a/compiler/compiler.go b/compiler/compiler.go index e4cd8d4bd1..f5678887ad 100644 --- a/compiler/compiler.go +++ b/compiler/compiler.go @@ -1201,9 +1201,7 @@ func (b *builder) createBuiltin(args []ssa.Value, callName string, pos token.Pos var llvmCap llvm.Value switch args[0].Type().(type) { case *types.Chan: - // Channel. Buffered channels haven't been implemented yet so always - // return 0. - llvmCap = llvm.ConstInt(b.intType, 0, false) + llvmCap = b.createRuntimeCall("chanCap", []llvm.Value{value}, "cap") case *types.Slice: llvmCap = b.CreateExtractValue(value, 2, "cap") default: @@ -1259,9 +1257,7 @@ func (b *builder) createBuiltin(args []ssa.Value, callName string, pos token.Pos // string or slice llvmLen = b.CreateExtractValue(value, 1, "len") case *types.Chan: - // Channel. Buffered channels haven't been implemented yet so always - // return 0. - llvmLen = llvm.ConstInt(b.intType, 0, false) + llvmLen = b.createRuntimeCall("chanLen", []llvm.Value{value}, "len") case *types.Map: llvmLen = b.createRuntimeCall("hashmapLen", []llvm.Value{value}, "len") default: diff --git a/src/runtime/chan.go b/src/runtime/chan.go index 929b8c7300..6563d4557f 100644 --- a/src/runtime/chan.go +++ b/src/runtime/chan.go @@ -136,6 +136,38 @@ func chanMake(elementSize uintptr, bufSize uintptr) *channel { } } +// Return the number of entries in this chan, called from the len builtin. +// A nil chan is defined as having length 0. +//go:inline +func chanLen(c *channel) int { + if c == nil { + return 0 + } + return int(c.bufUsed) +} + +// wrapper for use in reflect +func chanLenUnsafePointer(p unsafe.Pointer) int { + c := (*channel)(p) + return chanLen(c) +} + +// Return the capacity of this chan, called from the cap builtin. +// A nil chan is defined as having capacity 0. +//go:inline +func chanCap(c *channel) int { + if c == nil { + return 0 + } + return int(c.bufSize) +} + +// wrapper for use in reflect +func chanCapUnsafePointer(p unsafe.Pointer) int { + c := (*channel)(p) + return chanCap(c) +} + // resumeRX resumes the next receiver and returns the destination pointer. // If the ok value is true, then the caller is expected to store a value into this pointer. func (ch *channel) resumeRX(ok bool) unsafe.Pointer { diff --git a/testdata/channel.go b/testdata/channel.go index 52d498fcbc..e1acea7ead 100644 --- a/testdata/channel.go +++ b/testdata/channel.go @@ -9,7 +9,11 @@ import ( var wg sync.WaitGroup func main() { - ch := make(chan int) + ch := make(chan int, 2) + ch <- 1 + println("len, cap of channel:", len(ch), cap(ch), ch == nil) + + ch = make(chan int) println("len, cap of channel:", len(ch), cap(ch), ch == nil) wg.Add(1) diff --git a/testdata/channel.txt b/testdata/channel.txt index b7036f2b27..883a547a64 100644 --- a/testdata/channel.txt +++ b/testdata/channel.txt @@ -1,3 +1,4 @@ +len, cap of channel: 1 2 false len, cap of channel: 0 0 false recv from open channel: 1 true received num: 2 From 2c71f08922540d5753c54270f5e486e6ad2dadf1 Mon Sep 17 00:00:00 2001 From: cornelk Date: Mon, 11 May 2020 20:55:20 +0300 Subject: [PATCH 23/82] reflect: add Cap and Len support for map and chan --- src/reflect/value.go | 33 ++++++++++++++++++++++++++------- src/runtime/hashmap.go | 9 ++++++++- 2 files changed, 34 insertions(+), 8 deletions(-) diff --git a/src/reflect/value.go b/src/reflect/value.go index a1c69370e0..e71eaee8b9 100644 --- a/src/reflect/value.go +++ b/src/reflect/value.go @@ -304,29 +304,48 @@ func (v Value) Slice(i, j int) Value { panic("unimplemented: (reflect.Value).Slice()") } +//go:linkname maplen runtime.hashmapLenUnsafePointer +func maplen(p unsafe.Pointer) int + +//go:linkname chanlen runtime.chanLenUnsafePointer +func chanlen(p unsafe.Pointer) int + // Len returns the length of this value for slices, strings, arrays, channels, -// and maps. For oter types, it panics. +// and maps. For other types, it panics. func (v Value) Len() int { t := v.Type() switch t.Kind() { + case Array: + return v.Type().Len() + case Chan: + return chanlen(v.value) + case Map: + return maplen(v.value) case Slice: return int((*SliceHeader)(v.value).Len) case String: return int((*StringHeader)(v.value).Len) - case Array: - return v.Type().Len() - default: // Chan, Map - panic("unimplemented: (reflect.Value).Len()") + default: + panic(&ValueError{"Len"}) } } +//go:linkname chancap runtime.chanCapUnsafePointer +func chancap(p unsafe.Pointer) int + +// Cap returns the capacity of this value for arrays, channels and slices. +// For other types, it panics. func (v Value) Cap() int { t := v.Type() switch t.Kind() { + case Array: + return v.Type().Len() + case Chan: + return chancap(v.value) case Slice: return int((*SliceHeader)(v.value).Cap) - default: // Array, Chan - panic("unimplemented: (reflect.Value).Cap()") + default: + panic(&ValueError{"Cap"}) } } diff --git a/src/runtime/hashmap.go b/src/runtime/hashmap.go index bd940c759f..d7d60c89fc 100644 --- a/src/runtime/hashmap.go +++ b/src/runtime/hashmap.go @@ -1,7 +1,7 @@ package runtime // This is a hashmap implementation for the map[T]T type. -// It is very rougly based on the implementation of the Go hashmap: +// It is very roughly based on the implementation of the Go hashmap: // // https://golang.org/src/runtime/map.go @@ -80,6 +80,7 @@ func hashmapMake(keySize, valueSize uint8, sizeHint uintptr) *hashmap { // Return the number of entries in this hashmap, called from the len builtin. // A nil hashmap is defined as having length 0. +//go:inline func hashmapLen(m *hashmap) int { if m == nil { return 0 @@ -87,6 +88,12 @@ func hashmapLen(m *hashmap) int { return int(m.count) } +// wrapper for use in reflect +func hashmapLenUnsafePointer(p unsafe.Pointer) int { + m := (*hashmap)(p) + return hashmapLen(m) +} + // Set a specified key to a given value. Grow the map if necessary. //go:nobounds func hashmapSet(m *hashmap, key unsafe.Pointer, value unsafe.Pointer, hash uint32, keyEqual func(x, y unsafe.Pointer, n uintptr) bool) { From 6b8940421e169a751dd039d8c7d1b1c4a1465fb2 Mon Sep 17 00:00:00 2001 From: Ayke van Laethem Date: Sun, 10 May 2020 15:07:58 +0200 Subject: [PATCH 24/82] avr: use standard pin numbering MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This commit changes pin numbering for atmega328 based boards (Uno, Nano) to use the standard format, where pin number is determined by the pin/port. Previously, pin numbers were based on what the Uno uses, which does not seem to have a clear pattern. One difference is that counting starts at port B, as there is no port A. So PB0 is 0, PB1 is 1… PC0 is 8. This commit also moves PWM code to the atmega328 file, as it may not be generic to all ATmega chips. --- Makefile | 6 +- src/examples/button/button.go | 4 +- src/examples/pwm/pwm.go | 6 +- src/machine/board_arduino.go | 36 ++++++--- src/machine/board_arduino_nano.go | 36 ++++++--- src/machine/board_atmega328p.go | 37 +++++++++ src/machine/machine_atmega.go | 66 ---------------- src/machine/machine_atmega328p.go | 124 +++++++++++++++++++++++------- 8 files changed, 195 insertions(+), 120 deletions(-) create mode 100644 src/machine/board_atmega328p.go diff --git a/Makefile b/Makefile index bacd9b5d37..b027d9c0de 100644 --- a/Makefile +++ b/Makefile @@ -182,7 +182,7 @@ tinygo-test: .PHONY: smoketest smoketest: $(TINYGO) version - # test all examples + # test all examples (except pwm) $(TINYGO) build -size short -o test.hex -target=pca10040 examples/blinky1 @$(MD5SUM) test.hex $(TINYGO) build -size short -o test.hex -target=pca10040 examples/adc @@ -203,8 +203,6 @@ smoketest: @$(MD5SUM) test.hex $(TINYGO) build -size short -o test.hex -target=microbit examples/microbit-blink @$(MD5SUM) test.hex - $(TINYGO) build -size short -o test.hex -target=pca10040 examples/pwm - @$(MD5SUM) test.hex $(TINYGO) build -size short -o test.hex -target=pca10040 examples/serial @$(MD5SUM) test.hex $(TINYGO) build -size short -o test.hex -target=pca10040 examples/systick @@ -296,6 +294,8 @@ ifneq ($(AVR), 0) @$(MD5SUM) test.hex $(TINYGO) build -size short -o test.hex -target=arduino examples/blinky1 @$(MD5SUM) test.hex + $(TINYGO) build -size short -o test.hex -target=arduino examples/pwm + @$(MD5SUM) test.hex $(TINYGO) build -size short -o test.hex -target=arduino -scheduler=tasks examples/blinky1 @$(MD5SUM) test.hex $(TINYGO) build -size short -o test.hex -target=arduino-nano examples/blinky1 diff --git a/src/examples/button/button.go b/src/examples/button/button.go index 8c245cdae6..ad028ea0d0 100644 --- a/src/examples/button/button.go +++ b/src/examples/button/button.go @@ -5,11 +5,9 @@ import ( "time" ) -// This example assumes that the button is connected to pin 8. Change the value -// below to use a different pin. const ( led = machine.LED - button = machine.Pin(8) + button = machine.BUTTON ) func main() { diff --git a/src/examples/pwm/pwm.go b/src/examples/pwm/pwm.go index 104934e569..96220ee57b 100644 --- a/src/examples/pwm/pwm.go +++ b/src/examples/pwm/pwm.go @@ -8,9 +8,9 @@ import ( // This example assumes that an RGB LED is connected to pins 3, 5 and 6 on an Arduino. // Change the values below to use different pins. const ( - redPin = 3 - greenPin = 5 - bluePin = 6 + redPin = machine.D3 + greenPin = machine.D5 + bluePin = machine.D6 ) // cycleColor is just a placeholder until math/rand or some equivalent is working. diff --git a/src/machine/board_arduino.go b/src/machine/board_arduino.go index 92f17dac40..001ba9c70c 100644 --- a/src/machine/board_arduino.go +++ b/src/machine/board_arduino.go @@ -7,21 +7,39 @@ func CPUFrequency() uint32 { return 16000000 } +// Digital pins, marked as plain numbers on the board. +const ( + D0 = PD0 // RX + D1 = PD1 // TX + D2 = PD2 + D3 = PD3 + D4 = PD4 + D5 = PD5 + D6 = PD6 + D7 = PD7 + D8 = PB0 + D9 = PB1 + D10 = PB2 + D11 = PB3 + D12 = PB4 + D13 = PB5 +) + // LED on the Arduino -const LED Pin = 13 +const LED Pin = D13 // ADC on the Arduino const ( - ADC0 Pin = 0 - ADC1 Pin = 1 - ADC2 Pin = 2 - ADC3 Pin = 3 - ADC4 Pin = 4 // Used by TWI for SDA - ADC5 Pin = 5 // Used by TWI for SCL + ADC0 Pin = PC0 + ADC1 Pin = PC1 + ADC2 Pin = PC2 + ADC3 Pin = PC3 + ADC4 Pin = PC4 // Used by TWI for SDA + ADC5 Pin = PC5 // Used by TWI for SCL ) // UART pins const ( - UART_TX_PIN Pin = 1 - UART_RX_PIN Pin = 0 + UART_TX_PIN Pin = PD1 + UART_RX_PIN Pin = PD0 ) diff --git a/src/machine/board_arduino_nano.go b/src/machine/board_arduino_nano.go index 1e96c95f28..d84b23f365 100644 --- a/src/machine/board_arduino_nano.go +++ b/src/machine/board_arduino_nano.go @@ -7,21 +7,39 @@ func CPUFrequency() uint32 { return 16000000 } +// Digital pins. +const ( + D0 = PD0 // RX0 + D1 = PD1 // TX1 + D2 = PD2 + D3 = PD3 + D4 = PD4 + D5 = PD5 + D6 = PD6 + D7 = PD7 + D8 = PB0 + D9 = PB1 + D10 = PB2 + D11 = PB3 + D12 = PB4 + D13 = PB5 +) + // LED on the Arduino -const LED Pin = 13 +const LED Pin = D13 // ADC on the Arduino const ( - ADC0 Pin = 0 - ADC1 Pin = 1 - ADC2 Pin = 2 - ADC3 Pin = 3 - ADC4 Pin = 4 // Used by TWI for SDA - ADC5 Pin = 5 // Used by TWI for SCL + ADC0 Pin = PC0 + ADC1 Pin = PC1 + ADC2 Pin = PC2 + ADC3 Pin = PC3 + ADC4 Pin = PC4 // Used by TWI for SDA + ADC5 Pin = PC5 // Used by TWI for SCL ) // UART pins const ( - UART_TX_PIN Pin = 1 - UART_RX_PIN Pin = 0 + UART_TX_PIN Pin = PD1 + UART_RX_PIN Pin = PD0 ) diff --git a/src/machine/board_atmega328p.go b/src/machine/board_atmega328p.go new file mode 100644 index 0000000000..030c78fe99 --- /dev/null +++ b/src/machine/board_atmega328p.go @@ -0,0 +1,37 @@ +// +build avr,atmega328p arduino arduino_nano + +package machine + +const ( + // Note: start at port B because there is no port A. + portB Pin = iota * 8 + portC + portD +) + +const ( + PB0 = portB + 0 + PB1 = portB + 1 + PB2 = portB + 2 + PB3 = portB + 3 + PB4 = portB + 4 + PB5 = portB + 5 + PB6 = portB + 6 + PB7 = portB + 7 + PC0 = portC + 0 + PC1 = portC + 1 + PC2 = portC + 2 + PC3 = portC + 3 + PC4 = portC + 4 + PC5 = portC + 5 + PC6 = portC + 6 + PC7 = portC + 7 + PD0 = portD + 0 + PD1 = portD + 1 + PD2 = portD + 2 + PD3 = portD + 3 + PD4 = portD + 4 + PD5 = portD + 5 + PD6 = portD + 6 + PD7 = portD + 7 +) diff --git a/src/machine/machine_atmega.go b/src/machine/machine_atmega.go index cac2ce6ecd..147d80ffe9 100644 --- a/src/machine/machine_atmega.go +++ b/src/machine/machine_atmega.go @@ -7,72 +7,6 @@ import ( "runtime/interrupt" ) -// InitPWM initializes the registers needed for PWM. -func InitPWM() { - // use waveform generation - avr.TCCR0A.SetBits(avr.TCCR0A_WGM00) - - // set timer 0 prescale factor to 64 - avr.TCCR0B.SetBits(avr.TCCR0B_CS01 | avr.TCCR0B_CS00) - - // set timer 1 prescale factor to 64 - avr.TCCR1B.SetBits(avr.TCCR1B_CS11) - - // put timer 1 in 8-bit phase correct pwm mode - avr.TCCR1A.SetBits(avr.TCCR1A_WGM10) - - // set timer 2 prescale factor to 64 - avr.TCCR2B.SetBits(avr.TCCR2B_CS22) - - // configure timer 2 for phase correct pwm (8-bit) - avr.TCCR2A.SetBits(avr.TCCR2A_WGM20) -} - -// Configure configures a PWM pin for output. -func (pwm PWM) Configure() { - if pwm.Pin < 8 { - avr.DDRD.SetBits(1 << uint8(pwm.Pin)) - } else { - avr.DDRB.SetBits(1 << uint8(pwm.Pin-8)) - } -} - -// Set turns on the duty cycle for a PWM pin using the provided value. On the AVR this is normally a -// 8-bit value ranging from 0 to 255. -func (pwm PWM) Set(value uint16) { - value8 := uint8(value >> 8) - switch pwm.Pin { - case 3: - // connect pwm to pin on timer 2, channel B - avr.TCCR2A.SetBits(avr.TCCR2A_COM2B1) - avr.OCR2B.Set(value8) // set pwm duty - case 5: - // connect pwm to pin on timer 0, channel B - avr.TCCR0A.SetBits(avr.TCCR0A_COM0B1) - avr.OCR0B.Set(value8) // set pwm duty - case 6: - // connect pwm to pin on timer 0, channel A - avr.TCCR0A.SetBits(avr.TCCR0A_COM0A1) - avr.OCR0A.Set(value8) // set pwm duty - case 9: - // connect pwm to pin on timer 1, channel A - avr.TCCR1A.SetBits(avr.TCCR1A_COM1A1) - // this is a 16-bit value, but we only currently allow the low order bits to be set - avr.OCR1AL.Set(value8) // set pwm duty - case 10: - // connect pwm to pin on timer 1, channel B - avr.TCCR1A.SetBits(avr.TCCR1A_COM1B1) - // this is a 16-bit value, but we only currently allow the low order bits to be set - avr.OCR1BL.Set(value8) // set pwm duty - case 11: - // connect pwm to pin on timer 2, channel A - avr.TCCR2A.SetBits(avr.TCCR2A_COM2A1) - avr.OCR2A.Set(value8) // set pwm duty - default: - panic("Invalid PWM pin") - } -} - // I2CConfig is used to store config info for I2C. type I2CConfig struct { Frequency uint32 diff --git a/src/machine/machine_atmega328p.go b/src/machine/machine_atmega328p.go index 60c36a37cc..2e7926a974 100644 --- a/src/machine/machine_atmega328p.go +++ b/src/machine/machine_atmega328p.go @@ -12,44 +12,114 @@ const irq_USART0_RX = avr.IRQ_USART_RX // Configure sets the pin to input or output. func (p Pin) Configure(config PinConfig) { if config.Mode == PinOutput { // set output bit - if p < 8 { - avr.DDRD.SetBits(1 << uint8(p)) - } else if p < 14 { - avr.DDRB.SetBits(1 << uint8(p-8)) - } else { - avr.DDRC.SetBits(1 << uint8(p-14)) + switch p / 8 { + case 0: // port B + avr.DDRB.SetBits(1 << uint8(p)) + case 1: // port C + avr.DDRC.SetBits(1 << uint8(p-8)) + case 2: // port D + avr.DDRD.SetBits(1 << uint8(p-16)) } } else { // configure input: clear output bit - if p < 8 { - avr.DDRD.ClearBits(1 << uint8(p)) - } else if p < 14 { - avr.DDRB.ClearBits(1 << uint8(p-8)) - } else { - avr.DDRC.ClearBits(1 << uint8(p-14)) + switch p / 8 { + case 0: // port B + avr.DDRB.ClearBits(1 << uint8(p)) + case 1: // port C + avr.DDRC.ClearBits(1 << uint8(p-8)) + case 2: // port D + avr.DDRD.ClearBits(1 << uint8(p-16)) } } } // Get returns the current value of a GPIO pin. func (p Pin) Get() bool { - if p < 8 { - val := avr.PIND.Get() & (1 << uint8(p)) - return (val > 0) - } else if p < 14 { - val := avr.PINB.Get() & (1 << uint8(p-8)) - return (val > 0) - } else { - val := avr.PINC.Get() & (1 << uint8(p-14)) - return (val > 0) + var val uint8 + switch p / 8 { + case 0: // port B + val = avr.PINB.Get() & (1 << uint8(p)) + case 1: // port C + val = avr.PINC.Get() & (1 << uint8(p-8)) + case 2: // port D + val = avr.PIND.Get() & (1 << uint8(p-16)) } + return val != 0 } func (p Pin) getPortMask() (*volatile.Register8, uint8) { - if p < 8 { - return avr.PORTD, 1 << uint8(p) - } else if p < 14 { - return avr.PORTB, 1 << uint8(p-8) - } else { - return avr.PORTC, 1 << uint8(p-14) + switch p / 8 { + case 0: // port B + return avr.PORTB, 1 << uint8(p) + case 1: + return avr.PORTC, 1 << uint8(p-8) + default: + return avr.PORTD, 1 << uint8(p-16) + } +} + +// InitPWM initializes the registers needed for PWM. +func InitPWM() { + // use waveform generation + avr.TCCR0A.SetBits(avr.TCCR0A_WGM00) + + // set timer 0 prescale factor to 64 + avr.TCCR0B.SetBits(avr.TCCR0B_CS01 | avr.TCCR0B_CS00) + + // set timer 1 prescale factor to 64 + avr.TCCR1B.SetBits(avr.TCCR1B_CS11) + + // put timer 1 in 8-bit phase correct pwm mode + avr.TCCR1A.SetBits(avr.TCCR1A_WGM10) + + // set timer 2 prescale factor to 64 + avr.TCCR2B.SetBits(avr.TCCR2B_CS22) + + // configure timer 2 for phase correct pwm (8-bit) + avr.TCCR2A.SetBits(avr.TCCR2A_WGM20) +} + +// Configure configures a PWM pin for output. +func (pwm PWM) Configure() { + switch pwm.Pin / 8 { + case 0: // port B + avr.DDRB.SetBits(1 << uint8(pwm.Pin)) + case 2: // port D + avr.DDRD.SetBits(1 << uint8(pwm.Pin-16)) + } +} + +// Set turns on the duty cycle for a PWM pin using the provided value. On the AVR this is normally a +// 8-bit value ranging from 0 to 255. +func (pwm PWM) Set(value uint16) { + value8 := uint8(value >> 8) + switch pwm.Pin { + case PD3: + // connect pwm to pin on timer 2, channel B + avr.TCCR2A.SetBits(avr.TCCR2A_COM2B1) + avr.OCR2B.Set(value8) // set pwm duty + case PD5: + // connect pwm to pin on timer 0, channel B + avr.TCCR0A.SetBits(avr.TCCR0A_COM0B1) + avr.OCR0B.Set(value8) // set pwm duty + case PD6: + // connect pwm to pin on timer 0, channel A + avr.TCCR0A.SetBits(avr.TCCR0A_COM0A1) + avr.OCR0A.Set(value8) // set pwm duty + case PB1: + // connect pwm to pin on timer 1, channel A + avr.TCCR1A.SetBits(avr.TCCR1A_COM1A1) + // this is a 16-bit value, but we only currently allow the low order bits to be set + avr.OCR1AL.Set(value8) // set pwm duty + case PB2: + // connect pwm to pin on timer 1, channel B + avr.TCCR1A.SetBits(avr.TCCR1A_COM1B1) + // this is a 16-bit value, but we only currently allow the low order bits to be set + avr.OCR1BL.Set(value8) // set pwm duty + case PB3: + // connect pwm to pin on timer 2, channel A + avr.TCCR2A.SetBits(avr.TCCR2A_COM2A1) + avr.OCR2A.Set(value8) // set pwm duty + default: + panic("Invalid PWM pin") } } From e907db1481c73ef778b37b5c5478037e390768fc Mon Sep 17 00:00:00 2001 From: cornelk Date: Tue, 12 May 2020 00:05:11 +0300 Subject: [PATCH 25/82] os: add Args and stub it with mock data --- src/os/proc.go | 9 +++++++++ src/runtime/runtime.go | 5 ++++- 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/src/os/proc.go b/src/os/proc.go index a7718c4365..d3bfb12708 100644 --- a/src/os/proc.go +++ b/src/os/proc.go @@ -9,6 +9,15 @@ import ( "syscall" ) +// Args hold the command-line arguments, starting with the program name. +var Args []string + +func init() { + Args = runtime_args() +} + +func runtime_args() []string // in package runtime + // Exit causes the current program to exit with the given status code. // Conventionally, code zero indicates success, non-zero an error. // The program terminates immediately; deferred functions are not run. diff --git a/src/runtime/runtime.go b/src/runtime/runtime.go index 61d21029e8..41d0b9ace3 100644 --- a/src/runtime/runtime.go +++ b/src/runtime/runtime.go @@ -24,9 +24,12 @@ func GOROOT() string { return "/usr/local/go" } +// TODO: fill with real args. +var args = []string{"/proc/self/exe"} + //go:linkname os_runtime_args os.runtime_args func os_runtime_args() []string { - return nil + return args } // Copy size bytes from src to dst. The memory areas must not overlap. From 6bcb40fe01d83451eafe42a195c4acf1b186b2dd Mon Sep 17 00:00:00 2001 From: Ayke van Laethem Date: Mon, 30 Mar 2020 20:52:52 +0200 Subject: [PATCH 26/82] os: implement virtual filesystem support This allows applications to mount filesystems in the os package. This is useful for mounting external flash filesystems, for example. --- src/os/file.go | 157 +++++++++++++++++++++------------- src/os/file_other.go | 32 +++++-- src/os/file_unix.go | 98 +++++++++++++++++++-- src/os/filesystem.go | 85 ++++++++++++++++++ src/syscall/syscall_darwin.go | 39 +++++---- src/syscall/syscall_libc.go | 8 ++ testdata/stdlib.go | 6 +- testdata/stdlib.txt | 6 +- 8 files changed, 336 insertions(+), 95 deletions(-) create mode 100644 src/os/filesystem.go diff --git a/src/os/file.go b/src/os/file.go index de971fb79d..6f4a8cc140 100644 --- a/src/os/file.go +++ b/src/os/file.go @@ -10,49 +10,119 @@ import ( ) // Portable analogs of some common system call errors. +// Note that these are exported for use in the Filesystem interface. var ( - errUnsupported = errors.New("operation not supported") - notImplemented = errors.New("os: not implemented") + ErrUnsupported = errors.New("operation not supported") + ErrNotImplemented = errors.New("operation not implemented") + ErrNotExist = errors.New("file not found") + ErrExist = errors.New("file exists") ) -// Stdin, Stdout, and Stderr are open Files pointing to the standard input, -// standard output, and standard error file descriptors. -var ( - Stdin = &File{0, "/dev/stdin"} - Stdout = &File{1, "/dev/stdout"} - Stderr = &File{2, "/dev/stderr"} -) +// Mkdir creates a directory. If the operation fails, it will return an error of +// type *PathError. +func Mkdir(path string, perm FileMode) error { + fs, suffix := findMount(path) + if fs == nil { + return &PathError{"mkdir", path, ErrNotExist} + } + err := fs.Mkdir(suffix, perm) + if err != nil { + return &PathError{"mkdir", path, err} + } + return nil +} + +// Remove removes a file or (empty) directory. If the operation fails, it will +// return an error of type *PathError. +func Remove(path string) error { + fs, suffix := findMount(path) + if fs == nil { + return &PathError{"remove", path, ErrNotExist} + } + err := fs.Remove(suffix) + if err != nil { + return &PathError{"remove", path, err} + } + return nil +} // File represents an open file descriptor. type File struct { - fd uintptr - name string + handle FileHandle + name string +} + +// Name returns the name of the file with which it was opened. +func (f *File) Name() string { + return f.name +} + +// OpenFile opens the named file. If the operation fails, the returned error +// will be of type *PathError. +func OpenFile(name string, flag int, perm FileMode) (*File, error) { + fs, suffix := findMount(name) + if fs == nil { + return nil, &PathError{"open", name, ErrNotExist} + } + handle, err := fs.OpenFile(suffix, flag, perm) + if err != nil { + return nil, &PathError{"open", name, err} + } + return &File{name: name, handle: handle}, nil +} + +// Open opens the file named for reading. +func Open(name string) (*File, error) { + return OpenFile(name, O_RDONLY, 0) +} + +// Create creates the named file, overwriting it if it already exists. +func Create(name string) (*File, error) { + return OpenFile(name, O_RDWR|O_CREATE|O_TRUNC, 0666) +} + +// Read reads up to len(b) bytes from the File. It returns the number of bytes +// read and any error encountered. At end of file, Read returns 0, io.EOF. +func (f *File) Read(b []byte) (n int, err error) { + n, err = f.handle.Read(b) + if err != nil { + err = &PathError{"read", f.name, err} + } + return +} + +// Write writes len(b) bytes to the File. It returns the number of bytes written +// and an error, if any. Write returns a non-nil error when n != len(b). +func (f *File) Write(b []byte) (n int, err error) { + n, err = f.handle.Write(b) + if err != nil { + err = &PathError{"write", f.name, err} + } + return +} + +// Close closes the File, rendering it unusable for I/O. +func (f *File) Close() (err error) { + err = f.handle.Close() + if err != nil { + err = &PathError{"close", f.name, err} + } + return } // Readdir is a stub, not yet implemented func (f *File) Readdir(n int) ([]FileInfo, error) { - return nil, notImplemented + return nil, &PathError{"readdir", f.name, ErrNotImplemented} } // Readdirnames is a stub, not yet implemented func (f *File) Readdirnames(n int) (names []string, err error) { - return nil, notImplemented + return nil, &PathError{"readdirnames", f.name, ErrNotImplemented} } // Stat is a stub, not yet implemented func (f *File) Stat() (FileInfo, error) { - return nil, notImplemented -} - -// NewFile returns a new File with the given file descriptor and name. -func NewFile(fd uintptr, name string) *File { - return &File{fd, name} -} - -// Fd returns the integer Unix file descriptor referencing the open file. The -// file descriptor is valid only until f.Close is called. -func (f *File) Fd() uintptr { - return f.fd + return nil, &PathError{"stat", f.name, ErrNotImplemented} } const ( @@ -72,32 +142,8 @@ type PathError struct { Err error } -func (e *PathError) Error() string { return e.Op + " " + e.Path + ": " + e.Err.Error() } - -// Open is a super simple stub function (for now), only capable of opening stdin, stdout, and stderr -func Open(name string) (*File, error) { - fd := uintptr(999) - switch name { - case "/dev/stdin": - fd = 0 - case "/dev/stdout": - fd = 1 - case "/dev/stderr": - fd = 2 - default: - return nil, &PathError{"open", name, notImplemented} - } - return &File{fd, name}, nil -} - -// OpenFile is a stub, passing through to the stub Open() call -func OpenFile(name string, flag int, perm FileMode) (*File, error) { - return Open(name) -} - -// Create is a stub, passing through to the stub Open() call -func Create(name string) (*File, error) { - return Open(name) +func (e *PathError) Error() string { + return e.Op + " " + e.Path + ": " + e.Err.Error() } type FileMode uint32 @@ -155,12 +201,12 @@ type FileInfo interface { // Stat is a stub, not yet implemented func Stat(name string) (FileInfo, error) { - return nil, notImplemented + return nil, &PathError{"stat", name, ErrNotImplemented} } // Lstat is a stub, not yet implemented func Lstat(name string) (FileInfo, error) { - return nil, notImplemented + return nil, &PathError{"lstat", name, ErrNotImplemented} } // Getwd is a stub (for now), always returning an empty string @@ -178,11 +224,6 @@ func TempDir() string { return "/tmp" } -// Mkdir is a stub, not yet implemented -func Mkdir(name string, perm FileMode) error { - return notImplemented -} - // IsExist is a stub (for now), always returning false func IsExist(err error) bool { return false diff --git a/src/os/file_other.go b/src/os/file_other.go index 50bfa226f8..be98cc693a 100644 --- a/src/os/file_other.go +++ b/src/os/file_other.go @@ -6,28 +6,44 @@ import ( _ "unsafe" ) +// Stdin, Stdout, and Stderr are open Files pointing to the standard input, +// standard output, and standard error file descriptors. +var ( + Stdin = &File{stdioFileHandle(0), "/dev/stdin"} + Stdout = &File{stdioFileHandle(1), "/dev/stdout"} + Stderr = &File{stdioFileHandle(2), "/dev/stderr"} +) + +// isOS indicates whether we're running on a real operating system with +// filesystem support. +const isOS = false + +// stdioFileHandle represents one of stdin, stdout, or stderr depending on the +// number. It implements the FileHandle interface. +type stdioFileHandle uint8 + // Read is unsupported on this system. -func (f *File) Read(b []byte) (n int, err error) { - return 0, errUnsupported +func (f stdioFileHandle) Read(b []byte) (n int, err error) { + return 0, ErrUnsupported } // Write writes len(b) bytes to the output. It returns the number of bytes // written or an error if this file is not stdout or stderr. -func (f *File) Write(b []byte) (n int, err error) { - switch f.fd { - case Stdout.fd, Stderr.fd: +func (f stdioFileHandle) Write(b []byte) (n int, err error) { + switch f { + case 1, 2: // stdout, stderr for _, c := range b { putchar(c) } return len(b), nil default: - return 0, errUnsupported + return 0, ErrUnsupported } } // Close is unsupported on this system. -func (f *File) Close() error { - return errUnsupported +func (f stdioFileHandle) Close() error { + return ErrUnsupported } //go:linkname putchar runtime.putchar diff --git a/src/os/file_unix.go b/src/os/file_unix.go index 9f2a31e9d7..e77d6773fb 100644 --- a/src/os/file_unix.go +++ b/src/os/file_unix.go @@ -6,19 +6,105 @@ import ( "syscall" ) +func init() { + // Mount the host filesystem at the root directory. This is what most + // programs will be expecting. + Mount("/", unixFilesystem{}) +} + +// Stdin, Stdout, and Stderr are open Files pointing to the standard input, +// standard output, and standard error file descriptors. +var ( + Stdin = &File{unixFileHandle(0), "/dev/stdin"} + Stdout = &File{unixFileHandle(1), "/dev/stdout"} + Stderr = &File{unixFileHandle(2), "/dev/stderr"} +) + +// isOS indicates whether we're running on a real operating system with +// filesystem support. +const isOS = true + +// unixFilesystem is an empty handle for a Unix/Linux filesystem. All operations +// are relative to the current working directory. +type unixFilesystem struct { +} + +func (fs unixFilesystem) Mkdir(path string, perm FileMode) error { + return handleSyscallError(syscall.Mkdir(path, uint32(perm))) +} + +func (fs unixFilesystem) Remove(path string) error { + return handleSyscallError(syscall.Unlink(path)) +} + +func (fs unixFilesystem) OpenFile(path string, flag int, perm FileMode) (FileHandle, error) { + // Map os package flags to syscall flags. + syscallFlag := 0 + if flag&O_RDONLY != 0 { + syscallFlag |= syscall.O_RDONLY + } + if flag&O_WRONLY != 0 { + syscallFlag |= syscall.O_WRONLY + } + if flag&O_RDWR != 0 { + syscallFlag |= syscall.O_RDWR + } + if flag&O_APPEND != 0 { + syscallFlag |= syscall.O_APPEND + } + if flag&O_CREATE != 0 { + syscallFlag |= syscall.O_CREAT + } + if flag&O_EXCL != 0 { + syscallFlag |= syscall.O_EXCL + } + if flag&O_SYNC != 0 { + syscallFlag |= syscall.O_SYNC + } + if flag&O_TRUNC != 0 { + syscallFlag |= syscall.O_TRUNC + } + fp, err := syscall.Open(path, syscallFlag, uint32(perm)) + return unixFileHandle(fp), handleSyscallError(err) +} + +// unixFileHandle is a Unix file pointer with associated methods that implement +// the FileHandle interface. +type unixFileHandle uintptr + // Read reads up to len(b) bytes from the File. It returns the number of bytes // read and any error encountered. At end of file, Read returns 0, io.EOF. -func (f *File) Read(b []byte) (n int, err error) { - return syscall.Read(int(f.fd), b) +func (f unixFileHandle) Read(b []byte) (n int, err error) { + n, err = syscall.Read(int(f), b) + err = handleSyscallError(err) + return } // Write writes len(b) bytes to the File. It returns the number of bytes written // and an error, if any. Write returns a non-nil error when n != len(b). -func (f *File) Write(b []byte) (n int, err error) { - return syscall.Write(int(f.fd), b) +func (f unixFileHandle) Write(b []byte) (n int, err error) { + n, err = syscall.Write(int(f), b) + err = handleSyscallError(err) + return } // Close closes the File, rendering it unusable for I/O. -func (f *File) Close() error { - return syscall.Close(int(f.fd)) +func (f unixFileHandle) Close() error { + return handleSyscallError(syscall.Close(int(f))) +} + +// handleSyscallError converts syscall errors into regular os package errors. +// The err parameter must be either nil or of type syscall.Errno. +func handleSyscallError(err error) error { + if err == nil { + return nil + } + switch err.(syscall.Errno) { + case syscall.EEXIST: + return ErrExist + case syscall.ENOENT: + return ErrNotExist + default: + return err + } } diff --git a/src/os/filesystem.go b/src/os/filesystem.go new file mode 100644 index 0000000000..b0719c6cd2 --- /dev/null +++ b/src/os/filesystem.go @@ -0,0 +1,85 @@ +package os + +import ( + "strings" +) + +// mounts lists the mount points currently mounted in the filesystem provided by +// the os package. To resolve a path to a mount point, it is scanned from top to +// bottom looking for the first prefix match. +var mounts []mountPoint + +type mountPoint struct { + // prefix is a filesystem prefix, that always starts and ends with a forward + // slash. To denote the root filesystem, use a single slash: "/". + // This allows fast checking whether a path lies within a mount point. + prefix string + + // filesystem is the Filesystem implementation that is mounted at this mount + // point. + filesystem Filesystem +} + +// Filesystem provides an interface for generic filesystem drivers mounted in +// the os package. The errors returned must be one of the os.Err* errors, or a +// custom error if one doesn't exist. It should not be a *PathError because +// errors will be wrapped with a *PathError by the filesystem abstraction. +// +// WARNING: this interface is not finalized and may change in a future version. +type Filesystem interface { + // OpenFile opens the named file. + OpenFile(name string, flag int, perm FileMode) (FileHandle, error) + + // Mkdir creates a new directoy with the specified permission (before + // umask). Some filesystems may not support directories or permissions. + Mkdir(name string, perm FileMode) error + + // Remove removes the named file or (empty) directory. + Remove(name string) error +} + +// FileHandle is an interface that should be implemented by filesystems +// implementing the Filesystem interface. +// +// WARNING: this interface is not finalized and may change in a future version. +type FileHandle interface { + // Read reads up to len(b) bytes from the file. + Read(b []byte) (n int, err error) + + // Write writes up to len(b) bytes to the file. + Write(b []byte) (n int, err error) + + // Close closes the file, making it unusable for further writes. + Close() (err error) +} + +// findMount returns the appropriate (mounted) filesystem to use for a given +// filename plus the path relative to that filesystem. +func findMount(path string) (Filesystem, string) { + for i := len(mounts) - 1; i >= 0; i-- { + mount := mounts[i] + if strings.HasPrefix(path, mount.prefix) { + return mount.filesystem, path[len(mount.prefix)-1:] + } + } + if isOS { + // Assume that the first entry in the mounts slice is the OS filesystem + // at the root of the directory tree. Use it as-is, to support relative + // paths. + return mounts[0].filesystem, path + } + return nil, path +} + +// Mount mounts the given filesystem in the filesystem abstraction layer of the +// os package. It is not possible to unmount filesystems. Filesystems added +// later will override earlier filesystems. +// +// The provided prefix must start and end with a forward slash. This is true for +// the root directory ("/") for example. +func Mount(prefix string, filesystem Filesystem) { + if prefix[0] != '/' || prefix[len(prefix)-1] != '/' { + panic("os.Mount: invalid prefix") + } + mounts = append(mounts, mountPoint{prefix, filesystem}) +} diff --git a/src/syscall/syscall_darwin.go b/src/syscall/syscall_darwin.go index 176fe61dde..e2e7f408d4 100644 --- a/src/syscall/syscall_darwin.go +++ b/src/syscall/syscall_darwin.go @@ -1,8 +1,7 @@ package syscall // This file defines errno and constants to match the darwin libsystem ABI. -// Values have been determined experimentally by compiling some C code on macOS -// with Clang and looking at the resulting LLVM IR. +// Values have been copied from src/syscall/zerrors_darwin_amd64.go. // This function returns the error location in the darwin ABI. // Discovered by compiling the following code using Clang: @@ -24,28 +23,34 @@ func getErrno() Errno { } const ( - ENOENT Errno = 2 - EINTR Errno = 4 - EMFILE Errno = 24 - EAGAIN Errno = 35 - ETIMEDOUT Errno = 60 - ENOSYS Errno = 78 + ENOENT Errno = 0x2 + EEXIST Errno = 0x11 + EINTR Errno = 0x4 + EMFILE Errno = 0x18 + EAGAIN Errno = 0x23 + ETIMEDOUT Errno = 0x3c + ENOSYS Errno = 0x4e EWOULDBLOCK Errno = EAGAIN ) type Signal int const ( - SIGCHLD Signal = 20 - SIGINT Signal = 2 - SIGKILL Signal = 9 - SIGTRAP Signal = 5 - SIGQUIT Signal = 3 - SIGTERM Signal = 15 + SIGCHLD Signal = 0x14 + SIGINT Signal = 0x2 + SIGKILL Signal = 0x9 + SIGTRAP Signal = 0x5 + SIGQUIT Signal = 0x3 + SIGTERM Signal = 0xf ) const ( - O_RDONLY = 0 - O_WRONLY = 1 - O_RDWR = 2 + O_RDONLY = 0x0 + O_WRONLY = 0x1 + O_RDWR = 0x2 + O_APPEND = 0x8 + O_SYNC = 0x80 + O_CREAT = 0x200 + O_TRUNC = 0x400 + O_EXCL = 0x800 ) diff --git a/src/syscall/syscall_libc.go b/src/syscall/syscall_libc.go index 7838880492..3f24ddb70c 100644 --- a/src/syscall/syscall_libc.go +++ b/src/syscall/syscall_libc.go @@ -31,6 +31,14 @@ func Open(path string, mode int, perm uint32) (fd int, err error) { return 0, ENOSYS // TODO } +func Mkdir(path string, mode uint32) (err error) { + return ENOSYS // TODO +} + +func Unlink(path string) (err error) { + return ENOSYS // TODO +} + func Kill(pid int, sig Signal) (err error) { return ENOSYS // TODO } diff --git a/testdata/stdlib.go b/testdata/stdlib.go index b0f2ce396c..b380e80398 100644 --- a/testdata/stdlib.go +++ b/testdata/stdlib.go @@ -9,9 +9,9 @@ import ( func main() { // package os, fmt - fmt.Println("stdin: ", os.Stdin.Fd()) - fmt.Println("stdout:", os.Stdout.Fd()) - fmt.Println("stderr:", os.Stderr.Fd()) + fmt.Println("stdin: ", os.Stdin.Name()) + fmt.Println("stdout:", os.Stdout.Name()) + fmt.Println("stderr:", os.Stderr.Name()) // package math/rand fmt.Println("pseudorandom number:", rand.Int31()) diff --git a/testdata/stdlib.txt b/testdata/stdlib.txt index ecf4402dd8..1862d0d220 100644 --- a/testdata/stdlib.txt +++ b/testdata/stdlib.txt @@ -1,6 +1,6 @@ -stdin: 0 -stdout: 1 -stderr: 2 +stdin: /dev/stdin +stdout: /dev/stdout +stderr: /dev/stderr pseudorandom number: 1298498081 strings.IndexByte: 2 strings.Replace: An-example-string From aa40ddc48bc32bf5178edecc5c2ff83afa808f17 Mon Sep 17 00:00:00 2001 From: Ayke van Laethem Date: Wed, 13 May 2020 02:11:04 +0200 Subject: [PATCH 27/82] ci: do not install the SiFive toolchain We don't need it anymore since we use lld to link RISC-V binaries. --- .circleci/config.yml | 5 ----- 1 file changed, 5 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 4988610277..37c6d23043 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -294,11 +294,6 @@ commands: tar -C /usr/local/opt -xf /tmp/tinygo.darwin-amd64.tar.gz ln -s /usr/local/opt/tinygo/bin/tinygo /usr/local/bin/tinygo tinygo version - - run: - name: "Download SiFive GNU toolchain" - command: | - curl -O https://static.dev.sifive.com/dev-tools/riscv64-unknown-elf-gcc-8.2.0-2019.05.3-x86_64-apple-darwin.tar.gz - sudo tar -C /usr/local --strip-components=1 -xf riscv64-unknown-elf-gcc-8.2.0-2019.05.3-x86_64-apple-darwin.tar.gz - run: make smoketest AVR=0 - save_cache: key: go-cache-macos-v2-{{ checksum "go.mod" }}-{{ .Environment.CIRCLE_BUILD_NUM }} From b5f028e1f7ecb645069193a5278526a081a23ff6 Mon Sep 17 00:00:00 2001 From: Ayke van Laethem Date: Tue, 12 May 2020 23:59:21 +0100 Subject: [PATCH 28/82] ci: build .deb files along with .tar.gz files for Debian --- .circleci/config.yml | 10 +++++++++- Makefile | 11 ++++++++++- azure-pipelines.yml | 2 +- 3 files changed, 20 insertions(+), 3 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 37c6d23043..e311b6a1c4 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -204,13 +204,21 @@ commands: - run: name: "Test TinyGo" command: make test + - run: + name: "Install fpm" + command: | + sudo apt-get install ruby ruby-dev + sudo gem install --no-document fpm - run: name: "Build TinyGo release" command: | - make release -j3 + make release deb -j3 cp -p build/release.tar.gz /tmp/tinygo.linux-amd64.tar.gz + cp -p build/release.deb /tmp/tinygo_amd64.deb - store_artifacts: path: /tmp/tinygo.linux-amd64.tar.gz + - store_artifacts: + path: /tmp/tinygo_amd64.deb - save_cache: key: go-cache-v2-{{ checksum "go.mod" }}-{{ .Environment.CIRCLE_BUILD_NUM }} paths: diff --git a/Makefile b/Makefile index b027d9c0de..fcdc72f7c3 100644 --- a/Makefile +++ b/Makefile @@ -310,7 +310,7 @@ endif $(TINYGO) build -o wasm.wasm -target=wasm examples/wasm/export $(TINYGO) build -o wasm.wasm -target=wasm examples/wasm/main -release: tinygo gen-device wasi-libc +build/release: tinygo gen-device wasi-libc @mkdir -p build/release/tinygo/bin @mkdir -p build/release/tinygo/lib/clang/include @mkdir -p build/release/tinygo/lib/CMSIS/CMSIS @@ -345,4 +345,13 @@ release: tinygo gen-device wasi-libc ./build/tinygo build-library -target=armv6m-none-eabi -o build/release/tinygo/pkg/armv6m-none-eabi/picolibc.a picolibc ./build/tinygo build-library -target=armv7m-none-eabi -o build/release/tinygo/pkg/armv7m-none-eabi/picolibc.a picolibc ./build/tinygo build-library -target=armv7em-none-eabi -o build/release/tinygo/pkg/armv7em-none-eabi/picolibc.a picolibc + +release: build/release tar -czf build/release.tar.gz -C build/release tinygo + +deb: build/release + @mkdir -p build/release-deb/usr/local/bin + @mkdir -p build/release-deb/usr/local/lib + cp -ar build/release/tinygo build/release-deb/usr/local/lib/tinygo + ln -sf ../lib/tinygo/bin/tinygo build/release-deb/usr/local/bin/tinygo + fpm -f -s dir -t deb -n tinygo -v $(shell grep "version = " version.go | awk '{print $$NF}') -m '@tinygo-org' --description='TinyGo is a Go compiler for small places.' --license='BSD 3-Clause' --url=https://tinygo.org/ --deb-changelog CHANGELOG.md -p build/release.deb -C ./build/release-deb diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 40a222a16c..346ce4fdae 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -69,7 +69,7 @@ jobs: script: | export PATH="$PATH:./llvm-build/bin:/c/Program Files/qemu" unset GOROOT - make release -j4 + make build/release -j4 - publish: $(System.DefaultWorkingDirectory)/build/release/tinygo displayName: Publish zip as artifact artifact: tinygo From 38fc340802b5d02392528d110358fc0e9074b7e8 Mon Sep 17 00:00:00 2001 From: Ayke van Laethem Date: Mon, 11 May 2020 16:02:00 +0200 Subject: [PATCH 29/82] sam: return an error when an incorrect PWM pin is used Previously it would trigger a nil pointer panic. --- src/examples/pwm/pwm.go | 16 ++++++-- src/machine/machine_atmega328p.go | 3 +- src/machine/machine_atsamd21.go | 40 ++++++++++++------- src/machine/machine_atsamd51.go | 66 ++++++++++++++++++------------- src/machine/machine_generic.go | 3 +- src/machine/machine_nrf52.go | 3 +- src/machine/machine_nrf52840.go | 4 +- 7 files changed, 84 insertions(+), 51 deletions(-) diff --git a/src/examples/pwm/pwm.go b/src/examples/pwm/pwm.go index 96220ee57b..380fbaa2e6 100644 --- a/src/examples/pwm/pwm.go +++ b/src/examples/pwm/pwm.go @@ -28,13 +28,16 @@ func main() { machine.InitPWM() red := machine.PWM{redPin} - red.Configure() + err := red.Configure() + checkError(err, "failed to configure red pin") green := machine.PWM{greenPin} - green.Configure() + err = green.Configure() + checkError(err, "failed to configure green pin") blue := machine.PWM{bluePin} - blue.Configure() + err = blue.Configure() + checkError(err, "failed to configure blue pin") var rc uint8 var gc uint8 = 20 @@ -52,3 +55,10 @@ func main() { time.Sleep(time.Millisecond * 500) } } + +func checkError(err error, msg string) { + if err != nil { + print(msg, ": ", err.Error()) + println() + } +} diff --git a/src/machine/machine_atmega328p.go b/src/machine/machine_atmega328p.go index 2e7926a974..58265ed1c9 100644 --- a/src/machine/machine_atmega328p.go +++ b/src/machine/machine_atmega328p.go @@ -79,13 +79,14 @@ func InitPWM() { } // Configure configures a PWM pin for output. -func (pwm PWM) Configure() { +func (pwm PWM) Configure() error { switch pwm.Pin / 8 { case 0: // port B avr.DDRB.SetBits(1 << uint8(pwm.Pin)) case 2: // port D avr.DDRD.SetBits(1 << uint8(pwm.Pin-16)) } + return nil } // Set turns on the duty cycle for a PWM pin using the provided value. On the AVR this is normally a diff --git a/src/machine/machine_atsamd21.go b/src/machine/machine_atsamd21.go index cd83b7b4df..e96a7f4074 100644 --- a/src/machine/machine_atsamd21.go +++ b/src/machine/machine_atsamd21.go @@ -1090,9 +1090,12 @@ func InitPWM() { } // Configure configures a PWM pin for output. -func (pwm PWM) Configure() { +func (pwm PWM) Configure() error { // figure out which TCCX timer for this pin timer := pwm.getTimer() + if timer == nil { + return ErrInvalidOutputPin + } // disable timer timer.CTRLA.ClearBits(sam.TCC_CTRLA_ENABLE) @@ -1139,12 +1142,19 @@ func (pwm PWM) Configure() { val := pwm.getPMux() & sam.PORT_PMUX0_PMUXO_Msk pwm.setPMux(val | uint8(pwmConfig<CC[tcChannel].reg = (uint32_t) value; - pwm.setChannel(0) + pwm.setChannel(timer, 0) for timer.SYNCBUSY.HasBits(sam.TCC_SYNCBUSY_CC0) || timer.SYNCBUSY.HasBits(sam.TCC_SYNCBUSY_CC1) { @@ -1282,12 +1285,19 @@ func (pwm PWM) Configure() { // Wait for synchronization for timer.SYNCBUSY.HasBits(sam.TCC_SYNCBUSY_ENABLE) { } + + return nil } // Set turns on the duty cycle for a PWM pin using the provided value. func (pwm PWM) Set(value uint16) { // figure out which TCCX timer for this pin timer := pwm.getTimer() + if timer == nil { + // The Configure call above cannot have succeeded, so simply ignore this + // error. + return + } // Wait for synchronization for timer.SYNCBUSY.HasBits(sam.TCC_SYNCBUSY_CTRLB) { @@ -1297,7 +1307,7 @@ func (pwm PWM) Set(value uint16) { } // TCCx->CCBUF[tcChannel].reg = (uint32_t) value; - pwm.setChannelBuffer(uint32(value)) + pwm.setChannelBuffer(timer, uint32(value)) for timer.SYNCBUSY.HasBits(sam.TCC_SYNCBUSY_CC0) || timer.SYNCBUSY.HasBits(sam.TCC_SYNCBUSY_CC1) { @@ -1329,61 +1339,61 @@ func (pwm PWM) setPinCfg(val uint8) { pwm.Pin.setPinCfg(val) } -// setChannel sets the value for the correct channel for PWM on this pin -func (pwm PWM) setChannel(val uint32) { +// setChannel sets the value for the correct channel for PWM on this pin. +func (pwm PWM) setChannel(timer *sam.TCC_Type, val uint32) { switch pwm.Pin { case PA16: - pwm.getTimer().CC[0].Set(val) + timer.CC[0].Set(val) case PA17: - pwm.getTimer().CC[1].Set(val) + timer.CC[1].Set(val) case PA14: - pwm.getTimer().CC[0].Set(val) + timer.CC[0].Set(val) case PA15: - pwm.getTimer().CC[1].Set(val) + timer.CC[1].Set(val) case PA18: - pwm.getTimer().CC[2].Set(val) + timer.CC[2].Set(val) case PA19: - pwm.getTimer().CC[3].Set(val) + timer.CC[3].Set(val) case PA20: - pwm.getTimer().CC[0].Set(val) + timer.CC[0].Set(val) case PA21: - pwm.getTimer().CC[1].Set(val) + timer.CC[1].Set(val) case PA23: - pwm.getTimer().CC[3].Set(val) + timer.CC[3].Set(val) case PA22: - pwm.getTimer().CC[2].Set(val) + timer.CC[2].Set(val) case PB31: - pwm.getTimer().CC[1].Set(val) + timer.CC[1].Set(val) default: return // not supported on this pin } } // setChannelBuffer sets the value for the correct channel buffer for PWM on this pin -func (pwm PWM) setChannelBuffer(val uint32) { +func (pwm PWM) setChannelBuffer(timer *sam.TCC_Type, val uint32) { switch pwm.Pin { case PA16: - pwm.getTimer().CCBUF[0].Set(val) + timer.CCBUF[0].Set(val) case PA17: - pwm.getTimer().CCBUF[1].Set(val) + timer.CCBUF[1].Set(val) case PA14: - pwm.getTimer().CCBUF[0].Set(val) + timer.CCBUF[0].Set(val) case PA15: - pwm.getTimer().CCBUF[1].Set(val) + timer.CCBUF[1].Set(val) case PA18: - pwm.getTimer().CCBUF[2].Set(val) + timer.CCBUF[2].Set(val) case PA19: - pwm.getTimer().CCBUF[3].Set(val) + timer.CCBUF[3].Set(val) case PA20: - pwm.getTimer().CCBUF[0].Set(val) + timer.CCBUF[0].Set(val) case PA21: - pwm.getTimer().CCBUF[1].Set(val) + timer.CCBUF[1].Set(val) case PA23: - pwm.getTimer().CCBUF[3].Set(val) + timer.CCBUF[3].Set(val) case PA22: - pwm.getTimer().CCBUF[2].Set(val) + timer.CCBUF[2].Set(val) case PB31: - pwm.getTimer().CCBUF[1].Set(val) + timer.CCBUF[1].Set(val) default: return // not supported on this pin } diff --git a/src/machine/machine_generic.go b/src/machine/machine_generic.go index a0b47837e4..ace5b1cb4f 100644 --- a/src/machine/machine_generic.go +++ b/src/machine/machine_generic.go @@ -90,7 +90,8 @@ func InitPWM() { } // Configure configures a PWM pin for output. -func (pwm PWM) Configure() { +func (pwm PWM) Configure() error { + return nil } // Set turns on the duty cycle for a PWM pin using the provided value. diff --git a/src/machine/machine_nrf52.go b/src/machine/machine_nrf52.go index caa0c4e78f..003be7a69d 100644 --- a/src/machine/machine_nrf52.go +++ b/src/machine/machine_nrf52.go @@ -159,7 +159,8 @@ func InitPWM() { } // Configure configures a PWM pin for output. -func (pwm PWM) Configure() { +func (pwm PWM) Configure() error { + return nil } // Set turns on the duty cycle for a PWM pin using the provided value. diff --git a/src/machine/machine_nrf52840.go b/src/machine/machine_nrf52840.go index 2a76ac27bd..c7e608269d 100644 --- a/src/machine/machine_nrf52840.go +++ b/src/machine/machine_nrf52840.go @@ -104,8 +104,8 @@ func InitADC() { } // Configure configures an ADC pin to be able to read analog data. -func (a ADC) Configure() { - return // no pin specific setup on nrf52840 machine. +func (a ADC) Configure() error { + return nil // no pin specific setup on nrf52840 machine. } // Get returns the current value of a ADC pin in the range 0..0xffff. From 473644d9189c82e46061d72668416a437b74e416 Mon Sep 17 00:00:00 2001 From: Jaden Weiss Date: Tue, 7 Apr 2020 17:09:30 -0400 Subject: [PATCH 30/82] internal/bytealg: reimplement bytealg in pure Go Previously, we implemented individual bytealg functions via linknaming, and had to update them every once in a while when we hit linker errors. Instead, this change reimplements the bytealg package in pure Go. If something is missing, it will cause a compiler error rather than a linker error. This is easier to test and maintain. --- compiler/compiler.go | 2 +- src/internal/bytealg/bytealg.go | 126 ++++++++++++++++++++++++++++++++ src/runtime/bytes.go | 11 --- src/runtime/string.go | 12 --- src/runtime/string_count.go | 23 ------ src/runtime/strings_go111.go | 4 +- 6 files changed, 130 insertions(+), 48 deletions(-) create mode 100644 src/internal/bytealg/bytealg.go delete mode 100644 src/runtime/bytes.go delete mode 100644 src/runtime/string_count.go diff --git a/compiler/compiler.go b/compiler/compiler.go index f5678887ad..e21f4a4e61 100644 --- a/compiler/compiler.go +++ b/compiler/compiler.go @@ -180,7 +180,7 @@ func Compile(pkgName string, machine llvm.TargetMachine, config *compileopts.Con path = path[len(tinygoPath+"/src/"):] } switch path { - case "machine", "os", "reflect", "runtime", "runtime/interrupt", "runtime/volatile", "sync", "testing", "internal/reflectlite", "internal/task": + case "machine", "os", "reflect", "runtime", "runtime/interrupt", "runtime/volatile", "sync", "testing", "internal/reflectlite", "internal/bytealg", "internal/task": return path default: if strings.HasPrefix(path, "device/") || strings.HasPrefix(path, "examples/") { diff --git a/src/internal/bytealg/bytealg.go b/src/internal/bytealg/bytealg.go new file mode 100644 index 0000000000..f52b507b70 --- /dev/null +++ b/src/internal/bytealg/bytealg.go @@ -0,0 +1,126 @@ +package bytealg + +const ( + // Index can search any valid length of string. + + MaxLen = int(-1) >> 31 + MaxBruteForce = MaxLen +) + +// Compare two byte slices. +// Returns -1 if the first differing byte is lower in a, or 1 if the first differing byte is greater in b. +// If the byte slices are equal, returns 0. +// If the lengths are different and there are no differing bytes, compares based on length. +func Compare(a, b []byte) int { + // Compare for differing bytes. + for i := 0; i < len(a) && i < len(b); i++ { + switch { + case a[0] < b[0]: + return -1 + case a[0] > b[0]: + return 1 + } + } + + // Compare lengths. + switch { + case len(a) > len(b): + return 1 + case len(a) < len(b): + return -1 + default: + return 0 + } +} + +// Count the number of instances of a byte in a slice. +func Count(b []byte, c byte) int { + // Use a simple implementation, as there is no intrinsic that does this like we want. + n := 0 + for _, v := range b { + if v == c { + n++ + } + } + return n +} + +// Count the number of instances of a byte in a string. +func CountString(s string, c byte) int { + // Use a simple implementation, as there is no intrinsic that does this like we want. + // Currently, the compiler does not generate zero-copy byte-string conversions, so this needs to be seperate from Count. + n := 0 + for i := 0; i < len(s); i++ { + if s[i] == c { + n++ + } + } + return n +} + +// Cutover is not reachable in TinyGo, but must exist as it is referenced. +func Cutover(n int) int { + // Setting MaxLen and MaxBruteForce should force a different path to be taken. + // This should never be called. + panic("cutover is unreachable") +} + +// Equal checks if two byte slices are equal. +// It is equivalent to bytes.Equal. +func Equal(a, b []byte) bool { + if len(a) != len(b) { + return false + } + + for i, v := range a { + if v != b[i] { + return false + } + } + + return true +} + +// Index finds the base index of the first instance of the byte sequence b in a. +// If a does not contain b, this returns -1. +func Index(a, b []byte) int { + for i := 0; i <= len(a)-len(b); i++ { + if Equal(a[i:i+len(b)], b) { + return i + } + } + return -1 +} + +// Index finds the index of the first instance of the specified byte in the slice. +// If the byte is not found, this returns -1. +func IndexByte(b []byte, c byte) int { + for i, v := range b { + if v == c { + return i + } + } + return -1 +} + +// Index finds the index of the first instance of the specified byte in the string. +// If the byte is not found, this returns -1. +func IndexByteString(s string, c byte) int { + for i := 0; i < len(s); i++ { + if s[i] == c { + return i + } + } + return -1 +} + +// Index finds the base index of the first instance of a substring in a string. +// If the substring is not found, this returns -1. +func IndexString(str, sub string) int { + for i := 0; i <= len(str)-len(sub); i++ { + if str[i:i+len(sub)] == sub { + return i + } + } + return -1 +} diff --git a/src/runtime/bytes.go b/src/runtime/bytes.go deleted file mode 100644 index 6b3d6be73c..0000000000 --- a/src/runtime/bytes.go +++ /dev/null @@ -1,11 +0,0 @@ -package runtime - -//go:linkname indexBytePortable internal/bytealg.IndexByte -func indexBytePortable(s []byte, c byte) int { - for i, b := range s { - if b == c { - return i - } - } - return -1 -} diff --git a/src/runtime/string.go b/src/runtime/string.go index 30736d3056..e0d0a226eb 100644 --- a/src/runtime/string.go +++ b/src/runtime/string.go @@ -204,15 +204,3 @@ func decodeUTF8(s string, index uintptr) (rune, uintptr) { return 0xfffd, 1 } } - -// indexByteString returns the index of the first instance of c in s, or -1 if c -// is not present in s. -//go:linkname indexByteString internal/bytealg.IndexByteString -func indexByteString(s string, c byte) int { - for i := 0; i < len(s); i++ { - if s[i] == c { - return i - } - } - return -1 -} diff --git a/src/runtime/string_count.go b/src/runtime/string_count.go deleted file mode 100644 index 6b4f8d42f4..0000000000 --- a/src/runtime/string_count.go +++ /dev/null @@ -1,23 +0,0 @@ -// +build amd64 arm,go1.13 arm64 ppc64le ppc64 s390x - -package runtime - -// This file implements the string counting functions used by the strings -// package, for example. It must be reimplemented here as a replacement for the -// Go stdlib asm implementations, but only when the asm implementations are used -// (this varies by Go version). -// Track this file for updates: -// https://github.com/golang/go/blob/master/src/internal/bytealg/count_native.go - -// countString copies the implementation from -// https://github.com/golang/go/blob/67f181bfd84dfd5942fe9a29d8a20c9ce5eb2fea/src/internal/bytealg/count_generic.go#L1 -//go:linkname countString internal/bytealg.CountString -func countString(s string, c byte) int { - n := 0 - for i := 0; i < len(s); i++ { - if s[i] == c { - n++ - } - } - return n -} diff --git a/src/runtime/strings_go111.go b/src/runtime/strings_go111.go index b13ae7c088..866cb92a88 100644 --- a/src/runtime/strings_go111.go +++ b/src/runtime/strings_go111.go @@ -2,11 +2,13 @@ package runtime +import "internal/bytealg" + // indexByte provides compatibility with Go 1.11. // See the following: // https://github.com/tinygo-org/tinygo/issues/351 // https://github.com/golang/go/commit/ad4a58e31501bce5de2aad90a620eaecdc1eecb8 //go:linkname indexByte strings.IndexByte func indexByte(s string, c byte) int { - return indexByteString(s, c) + return bytealg.IndexByteString(s, c) } From 29ca1147f5f5acdd17825c7bf052f709ed4510ad Mon Sep 17 00:00:00 2001 From: Brad Peabody Date: Mon, 11 May 2020 17:29:04 -0700 Subject: [PATCH 31/82] call scheduler from resume --- src/runtime/runtime_wasm.go | 1 + 1 file changed, 1 insertion(+) diff --git a/src/runtime/runtime_wasm.go b/src/runtime/runtime_wasm.go index 02d34ffda8..0b955c7567 100644 --- a/src/runtime/runtime_wasm.go +++ b/src/runtime/runtime_wasm.go @@ -58,6 +58,7 @@ func resume() { go func() { handleEvent() }() + scheduler() } //export go_scheduler From 76fb3bd1775f64a72911df66f776266f3ab44a2e Mon Sep 17 00:00:00 2001 From: cebernardi Date: Sat, 16 May 2020 22:15:44 +0100 Subject: [PATCH 32/82] compileopts: improve error reporting of unsupported flags --- compileopts/config.go | 4 +- compileopts/options.go | 62 ++++++++++++++++ compileopts/options_test.go | 138 ++++++++++++++++++++++++++++++++++++ main.go | 16 +++-- 4 files changed, 211 insertions(+), 9 deletions(-) create mode 100644 compileopts/options_test.go diff --git a/compileopts/config.go b/compileopts/config.go index 435dda80a3..f9fc01fe4b 100644 --- a/compileopts/config.go +++ b/compileopts/config.go @@ -129,8 +129,8 @@ func (c *Config) NeedsStackObjects() bool { } } -// Scheduler returns the scheduler implementation. Valid values are "coroutines" -// and "tasks". +// Scheduler returns the scheduler implementation. Valid values are "none", +//"coroutines" and "tasks". func (c *Config) Scheduler() string { if c.Options.Scheduler != "" { return c.Options.Scheduler diff --git a/compileopts/options.go b/compileopts/options.go index 7336ee3fc3..ae2c2cecd3 100644 --- a/compileopts/options.go +++ b/compileopts/options.go @@ -1,5 +1,17 @@ package compileopts +import ( + "fmt" + "strings" +) + +var ( + validGCOptions = []string{"none", "leaking", "extalloc", "conservative"} + validSchedulerOptions = []string{"none", "tasks", "coroutines"} + validPrintSizeOptions = []string{"none", "short", "full"} + validPanicStrategyOptions = []string{"print", "trap"} +) + // Options contains extra options to give to the compiler. These options are // usually passed from the command line. type Options struct { @@ -21,3 +33,53 @@ type Options struct { TestConfig TestConfig Programmer string } + +// Verify performs a validation on the given options, raising an error if options are not valid. +func (o *Options) Verify() error { + if o.GC != "" { + valid := isInArray(validGCOptions, o.GC) + if !valid { + return fmt.Errorf(`invalid gc option '%s': valid values are %s`, + o.GC, + strings.Join(validGCOptions, ", ")) + } + } + + if o.Scheduler != "" { + valid := isInArray(validSchedulerOptions, o.Scheduler) + if !valid { + return fmt.Errorf(`invalid scheduler option '%s': valid values are %s`, + o.Scheduler, + strings.Join(validSchedulerOptions, ", ")) + } + } + + if o.PrintSizes != "" { + valid := isInArray(validPrintSizeOptions, o.PrintSizes) + if !valid { + return fmt.Errorf(`invalid size option '%s': valid values are %s`, + o.PrintSizes, + strings.Join(validPrintSizeOptions, ", ")) + } + } + + if o.PanicStrategy != "" { + valid := isInArray(validPanicStrategyOptions, o.PanicStrategy) + if !valid { + return fmt.Errorf(`invalid panic option '%s': valid values are %s`, + o.PanicStrategy, + strings.Join(validPanicStrategyOptions, ", ")) + } + } + + return nil +} + +func isInArray(arr []string, item string) bool { + for _, i := range arr { + if i == item { + return true + } + } + return false +} diff --git a/compileopts/options_test.go b/compileopts/options_test.go new file mode 100644 index 0000000000..1ff532cc4f --- /dev/null +++ b/compileopts/options_test.go @@ -0,0 +1,138 @@ +package compileopts_test + +import ( + "errors" + "testing" + + "github.com/tinygo-org/tinygo/compileopts" +) + +func TestVerifyOptions(t *testing.T) { + + expectedGCError := errors.New(`invalid gc option 'incorrect': valid values are none, leaking, extalloc, conservative`) + expectedSchedulerError := errors.New(`invalid scheduler option 'incorrect': valid values are none, tasks, coroutines`) + expectedPrintSizeError := errors.New(`invalid size option 'incorrect': valid values are none, short, full`) + expectedPanicStrategyError := errors.New(`invalid panic option 'incorrect': valid values are print, trap`) + + testCases := []struct { + name string + opts compileopts.Options + expectedError error + }{ + { + name: "OptionsEmpty", + opts: compileopts.Options{}, + }, + { + name: "InvalidGCOption", + opts: compileopts.Options{ + GC: "incorrect", + }, + expectedError: expectedGCError, + }, + { + name: "GCOptionNone", + opts: compileopts.Options{ + GC: "none", + }, + }, + { + name: "GCOptionLeaking", + opts: compileopts.Options{ + GC: "leaking", + }, + }, + { + name: "GCOptionExtalloc", + opts: compileopts.Options{ + GC: "extalloc", + }, + }, + { + name: "GCOptionConservative", + opts: compileopts.Options{ + GC: "conservative", + }, + }, + { + name: "InvalidSchedulerOption", + opts: compileopts.Options{ + Scheduler: "incorrect", + }, + expectedError: expectedSchedulerError, + }, + { + name: "SchedulerOptionNone", + opts: compileopts.Options{ + Scheduler: "none", + }, + }, + { + name: "SchedulerOptionTasks", + opts: compileopts.Options{ + Scheduler: "tasks", + }, + }, + { + name: "SchedulerOptionCoroutines", + opts: compileopts.Options{ + Scheduler: "coroutines", + }, + }, + { + name: "InvalidPrintSizeOption", + opts: compileopts.Options{ + PrintSizes: "incorrect", + }, + expectedError: expectedPrintSizeError, + }, + { + name: "PrintSizeOptionNone", + opts: compileopts.Options{ + PrintSizes: "none", + }, + }, + { + name: "PrintSizeOptionShort", + opts: compileopts.Options{ + PrintSizes: "short", + }, + }, + { + name: "PrintSizeOptionFull", + opts: compileopts.Options{ + PrintSizes: "full", + }, + }, + { + name: "InvalidPanicOption", + opts: compileopts.Options{ + PanicStrategy: "incorrect", + }, + expectedError: expectedPanicStrategyError, + }, + { + name: "PanicOptionPrint", + opts: compileopts.Options{ + PanicStrategy: "print", + }, + }, + { + name: "PanicOptionTrap", + opts: compileopts.Options{ + PanicStrategy: "trap", + }, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + err := tc.opts.Verify() + if tc.expectedError != err { + if tc.expectedError.Error() != err.Error() { + t.Errorf("expected %v, got %v", tc.expectedError, err) + } + } + }) + } +} diff --git a/main.go b/main.go index 976591a93e..1d2032129d 100644 --- a/main.go +++ b/main.go @@ -699,7 +699,7 @@ func main() { opt := flag.String("opt", "z", "optimization level: 0, 1, 2, s, z") gc := flag.String("gc", "", "garbage collector to use (none, leaking, extalloc, conservative)") panicStrategy := flag.String("panic", "print", "panic strategy (print, trap)") - scheduler := flag.String("scheduler", "", "which scheduler to use (coroutines, tasks)") + scheduler := flag.String("scheduler", "", "which scheduler to use (none, coroutines, tasks)") printIR := flag.Bool("printir", false, "print LLVM IR") dumpSSA := flag.Bool("dumpssa", false, "dump internal Go SSA") verifyIR := flag.Bool("verifyir", false, "run extra verification steps on LLVM IR") @@ -759,12 +759,6 @@ func main() { options.LDFlags = strings.Split(*ldFlags, " ") } - if *panicStrategy != "print" && *panicStrategy != "trap" { - fmt.Fprintln(os.Stderr, "Panic strategy must be either print or trap.") - usage() - os.Exit(1) - } - var err error if options.HeapSize, err = parseSize(*heapSize); err != nil { fmt.Fprintln(os.Stderr, "Could not read heap size:", *heapSize) @@ -774,6 +768,13 @@ func main() { os.Setenv("CC", "clang -target="+*target) + err = options.Verify() + if err != nil { + fmt.Fprintln(os.Stderr, err.Error()) + usage() + os.Exit(1) + } + switch command { case "build": if *outpath == "" { @@ -792,6 +793,7 @@ func main() { if options.Target == "" && filepath.Ext(*outpath) == ".wasm" { options.Target = "wasm" } + err := Build(pkgName, *outpath, options) handleCompilerError(err) case "build-library": From b9fd6cee6f0e427d1f2317734f49411e190cec75 Mon Sep 17 00:00:00 2001 From: deadprogram Date: Wed, 13 May 2020 22:30:40 +0200 Subject: [PATCH 33/82] build: add webhook notifier orb for circleci Signed-off-by: deadprogram --- .circleci/config.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.circleci/config.yml b/.circleci/config.yml index e311b6a1c4..3b7c13f49f 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -1,4 +1,6 @@ version: 2.1 +orbs: + webhook: eddiewebb/webhook@0.0.4 commands: submodules: @@ -219,6 +221,8 @@ commands: path: /tmp/tinygo.linux-amd64.tar.gz - store_artifacts: path: /tmp/tinygo_amd64.deb + - webhook/notify: + endpoint: "${WEBHOOK_ENDPOINT}" - save_cache: key: go-cache-v2-{{ checksum "go.mod" }}-{{ .Environment.CIRCLE_BUILD_NUM }} paths: From 726d735ad35f1c1ea0ffc59a25a44e2d167a8ccf Mon Sep 17 00:00:00 2001 From: Lucas Teske Date: Wed, 20 May 2020 13:05:25 -0300 Subject: [PATCH 34/82] cgo: Add LDFlags support --- builder/build.go | 6 +++++- cgo/cgo.go | 20 +++++++++++++++++--- cgo/cgo_test.go | 2 +- cgo/testdata/flags.go | 7 +++++++ cgo/testdata/flags.out.go | 1 + compiler/compiler.go | 16 ++++++++-------- loader/loader.go | 4 +++- src/testing/testing.go | 8 ++++---- 8 files changed, 46 insertions(+), 18 deletions(-) diff --git a/builder/build.go b/builder/build.go index e67cb2bff9..f8247f8b50 100644 --- a/builder/build.go +++ b/builder/build.go @@ -32,7 +32,7 @@ func Build(pkgName, outpath string, config *compileopts.Config, action func(stri if err != nil { return err } - mod, extraFiles, errs := compiler.Compile(pkgName, machine, config) + mod, extraFiles, extraLDFlags, errs := compiler.Compile(pkgName, machine, config) if errs != nil { return newMultiError(errs) } @@ -187,6 +187,10 @@ func Build(pkgName, outpath string, config *compileopts.Config, action func(stri ldflags = append(ldflags, outpath) } + if len(extraLDFlags) > 0 { + ldflags = append(ldflags, extraLDFlags...) + } + // Link the object files together. err = link(config.Target.Linker, ldflags...) if err != nil { diff --git a/cgo/cgo.go b/cgo/cgo.go index d6470a5229..7cc61b9ce2 100644 --- a/cgo/cgo.go +++ b/cgo/cgo.go @@ -41,6 +41,7 @@ type cgoPackage struct { elaboratedTypes map[string]*elaboratedTypeInfo enums map[string]enumInfo anonStructNum int + ldflags []string } // constantInfo stores some information about a CGo constant found by libclang @@ -156,7 +157,7 @@ typedef unsigned long long _Cgo_ulonglong; // newly created *ast.File that should be added to the list of to-be-parsed // files. If there is one or more error, it returns these in the []error slice // but still modifies the AST. -func Process(files []*ast.File, dir string, fset *token.FileSet, cflags []string) (*ast.File, []error) { +func Process(files []*ast.File, dir string, fset *token.FileSet, cflags []string) (*ast.File, []string, []error) { p := &cgoPackage{ dir: dir, fset: fset, @@ -183,7 +184,7 @@ func Process(files []*ast.File, dir string, fset *token.FileSet, cflags []string // Find the absolute path for this package. packagePath, err := filepath.Abs(fset.File(files[0].Pos()).Name()) if err != nil { - return nil, []error{ + return nil, nil, []error{ scanner.Error{ Pos: fset.Position(files[0].Pos()), Msg: "cgo: cannot find absolute path: " + err.Error(), // TODO: wrap this error @@ -359,6 +360,19 @@ func Process(files []*ast.File, dir string, fset *token.FileSet, cflags []string } makePathsAbsolute(flags, packagePath) cflags = append(cflags, flags...) + case "LDFLAGS": + flags, err := shlex.Split(value) + if err != nil { + // TODO: find the exact location where the error happened. + p.addErrorAfter(comment.Slash, comment.Text[:lineStart+colon+1], "failed to parse flags in #cgo line: "+err.Error()) + continue + } + if err := checkLinkerFlags(name, flags); err != nil { + p.addErrorAfter(comment.Slash, comment.Text[:lineStart+colon+1], err.Error()) + continue + } + makePathsAbsolute(flags, packagePath) + p.ldflags = append(p.ldflags, flags...) default: startPos := strings.LastIndex(line[4:colon], name) + 4 p.addErrorAfter(comment.Slash, comment.Text[:lineStart+startPos], "invalid #cgo line: "+name) @@ -412,7 +426,7 @@ func Process(files []*ast.File, dir string, fset *token.FileSet, cflags []string // Print the newly generated in-memory AST, for debugging. //ast.Print(fset, p.generated) - return p.generated, p.errors + return p.generated, p.ldflags, p.errors } // makePathsAbsolute converts some common path compiler flags (-I, -L) from diff --git a/cgo/cgo_test.go b/cgo/cgo_test.go index 5673ebad8d..745824154b 100644 --- a/cgo/cgo_test.go +++ b/cgo/cgo_test.go @@ -50,7 +50,7 @@ func TestCGo(t *testing.T) { } // Process the AST with CGo. - cgoAST, cgoErrors := Process([]*ast.File{f}, "testdata", fset, cflags) + cgoAST, _, cgoErrors := Process([]*ast.File{f}, "testdata", fset, cflags) // Check the AST for type errors. var typecheckErrors []error diff --git a/cgo/testdata/flags.go b/cgo/testdata/flags.go index d52bbc5b96..012fad78da 100644 --- a/cgo/testdata/flags.go +++ b/cgo/testdata/flags.go @@ -21,6 +21,13 @@ package main #if defined(NOTDEFINED) #warning flag must not be defined #endif + +// Check Compiler flags +#cgo LDFLAGS: -lc + +// This flag is not valid ldflags +#cgo LDFLAGS: -does-not-exists + */ import "C" diff --git a/cgo/testdata/flags.out.go b/cgo/testdata/flags.out.go index 0bcad0d5a7..4eb70112a1 100644 --- a/cgo/testdata/flags.out.go +++ b/cgo/testdata/flags.out.go @@ -1,6 +1,7 @@ // CGo errors: // testdata/flags.go:5:7: invalid #cgo line: NOFLAGS // testdata/flags.go:8:13: invalid flag: -fdoes-not-exist +// testdata/flags.go:29:14: invalid flag: -does-not-exists package main diff --git a/compiler/compiler.go b/compiler/compiler.go index e21f4a4e61..5ed85c0538 100644 --- a/compiler/compiler.go +++ b/compiler/compiler.go @@ -103,7 +103,7 @@ func NewTargetMachine(config *compileopts.Config) (llvm.TargetMachine, error) { // violation. Eventually, this Compile function should only compile a single // package and not the whole program, and loading of the program (including CGo // processing) should be moved outside the compiler package. -func Compile(pkgName string, machine llvm.TargetMachine, config *compileopts.Config) (llvm.Module, []string, []error) { +func Compile(pkgName string, machine llvm.TargetMachine, config *compileopts.Config) (mod llvm.Module, extrafiles []string, extraldflags []string, errors []error) { c := &compilerContext{ Config: config, difiles: make(map[string]llvm.Metadata), @@ -148,7 +148,7 @@ func Compile(pkgName string, machine llvm.TargetMachine, config *compileopts.Con wd, err := os.Getwd() if err != nil { - return c.mod, nil, []error{err} + return c.mod, nil, nil, []error{err} } lprogram := &loader.Program{ Build: &build.Context{ @@ -211,14 +211,14 @@ func Compile(pkgName string, machine llvm.TargetMachine, config *compileopts.Con if strings.HasSuffix(pkgName, ".go") { _, err = lprogram.ImportFile(pkgName) if err != nil { - return c.mod, nil, []error{err} + return c.mod, nil, nil, []error{err} } } else { _, err = lprogram.Import(pkgName, wd, token.Position{ Filename: "build command-line-arguments", }) if err != nil { - return c.mod, nil, []error{err} + return c.mod, nil, nil, []error{err} } } @@ -226,12 +226,12 @@ func Compile(pkgName string, machine llvm.TargetMachine, config *compileopts.Con Filename: "build default import", }) if err != nil { - return c.mod, nil, []error{err} + return c.mod, nil, nil, []error{err} } err = lprogram.Parse(c.TestConfig.CompileTestBinary) if err != nil { - return c.mod, nil, []error{err} + return c.mod, nil, nil, []error{err} } c.ir = ir.NewProgram(lprogram, pkgName) @@ -239,7 +239,7 @@ func Compile(pkgName string, machine llvm.TargetMachine, config *compileopts.Con // Run a simple dead code elimination pass. err = c.ir.SimpleDCE() if err != nil { - return c.mod, nil, []error{err} + return c.mod, nil, nil, []error{err} } // Initialize debug information. @@ -383,7 +383,7 @@ func Compile(pkgName string, machine llvm.TargetMachine, config *compileopts.Con } } - return c.mod, extraFiles, c.diagnostics + return c.mod, extraFiles, lprogram.LDFlags, c.diagnostics } // getLLVMRuntimeType obtains a named type from the runtime package and returns diff --git a/loader/loader.go b/loader/loader.go index 5682bfa4a8..28ade8d2ef 100644 --- a/loader/loader.go +++ b/loader/loader.go @@ -31,6 +31,7 @@ type Program struct { Dir string // current working directory (for error reporting) TINYGOROOT string // root of the TinyGo installation or root of the source code CFlags []string + LDFlags []string ClangHeaders string } @@ -425,11 +426,12 @@ func (p *Package) parseFiles(includeTests bool) ([]*ast.File, error) { if p.ClangHeaders != "" { cflags = append(cflags, "-Xclang", "-internal-isystem", "-Xclang", p.ClangHeaders) } - generated, errs := cgo.Process(files, p.Program.Dir, p.fset, cflags) + generated, ldflags, errs := cgo.Process(files, p.Program.Dir, p.fset, cflags) if errs != nil { fileErrs = append(fileErrs, errs...) } files = append(files, generated) + p.LDFlags = append(p.LDFlags, ldflags...) } if len(fileErrs) != 0 { return nil, Errors{p, fileErrs} diff --git a/src/testing/testing.go b/src/testing/testing.go index f476514f66..ca353c6085 100644 --- a/src/testing/testing.go +++ b/src/testing/testing.go @@ -20,10 +20,10 @@ import ( type common struct { output io.Writer - failed bool // Test or benchmark has failed. - skipped bool // Test of benchmark has been skipped. - finished bool // Test function has completed. - name string // Name of test or benchmark. + failed bool // Test or benchmark has failed. + skipped bool // Test of benchmark has been skipped. + finished bool // Test function has completed. + name string // Name of test or benchmark. } // TB is the interface common to T and B. From 424d775bf4a4be9b29dc793dedd95e4e23a13d0c Mon Sep 17 00:00:00 2001 From: deadprogram Date: Thu, 21 May 2020 20:27:40 +0200 Subject: [PATCH 35/82] build: remove CircleCI orb, now using different integration Signed-off-by: deadprogram --- .circleci/config.yml | 4 ---- 1 file changed, 4 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 3b7c13f49f..e311b6a1c4 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -1,6 +1,4 @@ version: 2.1 -orbs: - webhook: eddiewebb/webhook@0.0.4 commands: submodules: @@ -221,8 +219,6 @@ commands: path: /tmp/tinygo.linux-amd64.tar.gz - store_artifacts: path: /tmp/tinygo_amd64.deb - - webhook/notify: - endpoint: "${WEBHOOK_ENDPOINT}" - save_cache: key: go-cache-v2-{{ checksum "go.mod" }}-{{ .Environment.CIRCLE_BUILD_NUM }} paths: From da505a6b170a27826eb3d18d3be41237dc066ca6 Mon Sep 17 00:00:00 2001 From: Ayke van Laethem Date: Thu, 14 May 2020 23:18:15 +0200 Subject: [PATCH 36/82] avr: unify GPIO pin/port code All the AVRs that I've looked at had the same pin/port structure, with the possible states being input/floating, input/pullup, low, and high (with the same PORT/DDR registers). The main difference is the number of available ports and pins. To reduce the amount of code and avoid duplication (and thus errors) I decided to centralize this, following the design used by the atmega2560 but while using a trick to save tracking a few registers. In the process, I noticed that the Pin.Get() function was incorrect on the atmega2560 implementation. It is now fixed in the unified code. --- src/machine/machine_atmega1284p.go | 58 ++++++++++++++++++++++++++---- src/machine/machine_atmega2560.go | 47 +++++++----------------- src/machine/machine_atmega328p.go | 52 +++++---------------------- src/machine/machine_attiny85.go | 17 ++------- src/machine/machine_avr.go | 36 +++++++++++++++++++ 5 files changed, 111 insertions(+), 99 deletions(-) diff --git a/src/machine/machine_atmega1284p.go b/src/machine/machine_atmega1284p.go index bc25f6743d..c80b0e3a8d 100644 --- a/src/machine/machine_atmega1284p.go +++ b/src/machine/machine_atmega1284p.go @@ -14,12 +14,58 @@ func CPUFrequency() uint32 { return 20000000 } +const ( + portA Pin = iota * 8 + portB + portC + portD +) + +const ( + PA0 = portA + 0 + PA1 = portA + 1 + PA2 = portA + 2 + PA3 = portA + 3 + PA4 = portA + 4 + PA5 = portA + 5 + PA6 = portA + 6 + PA7 = portA + 7 + PB0 = portB + 0 + PB1 = portB + 1 + PB2 = portB + 2 + PB3 = portB + 3 + PB4 = portB + 4 + PB5 = portB + 5 + PB6 = portB + 6 + PB7 = portB + 7 + PC0 = portC + 0 + PC1 = portC + 1 + PC2 = portC + 2 + PC3 = portC + 3 + PC4 = portC + 4 + PC5 = portC + 5 + PC6 = portC + 6 + PC7 = portC + 7 + PD0 = portD + 0 + PD1 = portD + 1 + PD2 = portD + 2 + PD3 = portD + 3 + PD4 = portD + 4 + PD5 = portD + 5 + PD6 = portD + 6 + PD7 = portD + 7 +) + +// getPortMask returns the PORTx register and mask for the pin. func (p Pin) getPortMask() (*volatile.Register8, uint8) { - if p < 8 { - return avr.PORTD, 1 << uint8(p) - } else if p < 14 { - return avr.PORTB, 1 << uint8(p-8) - } else { - return avr.PORTC, 1 << uint8(p-14) + switch { + case p >= PA0 && p <= PA7: + return avr.PORTA, 1 << uint8(p-portA) + case p >= PB0 && p <= PB7: + return avr.PORTB, 1 << uint8(p-portB) + case p >= PC0 && p <= PC7: + return avr.PORTC, 1 << uint8(p-portC) + default: + return avr.PORTD, 1 << uint8(p-portD) } } diff --git a/src/machine/machine_atmega2560.go b/src/machine/machine_atmega2560.go index e4c3f01077..74a83f02f8 100644 --- a/src/machine/machine_atmega2560.go +++ b/src/machine/machine_atmega2560.go @@ -96,53 +96,32 @@ const ( PL7 = portE + 7 ) -// Configure sets the pin to input or output. -func (p Pin) Configure(config PinConfig) { - register, _, mask := p.getRegisterPortMask() - if config.Mode == PinOutput { // set output bit - register.SetBits(mask) - } else { // configure input: clear output bit - register.ClearBits(mask) - } -} - -// Get returns the current value of a GPIO pin. -func (p Pin) Get() bool { - _, port, mask := p.getRegisterPortMask() - return (port.Get() & mask) > 0 -} - +// getPortMask returns the PORTx register and mask for the pin. func (p Pin) getPortMask() (*volatile.Register8, uint8) { - _, port, mask := p.getRegisterPortMask() - return port, mask -} - -// getRegisterPortMask returns the register, port, and mask for the pin -func (p Pin) getRegisterPortMask() (*volatile.Register8, *volatile.Register8, uint8) { switch { case p >= PA0 && p <= PA7: - return avr.DDRA, avr.PORTA, 1 << uint8(p-portA) + return avr.PORTA, 1 << uint8(p-portA) case p >= PB0 && p <= PB7: - return avr.DDRB, avr.PORTB, 1 << uint8(p-portB) + return avr.PORTB, 1 << uint8(p-portB) case p >= PC0 && p <= PC7: - return avr.DDRC, avr.PORTC, 1 << uint8(p-portC) + return avr.PORTC, 1 << uint8(p-portC) case p >= PD0 && p <= PD7: - return avr.DDRD, avr.PORTD, 1 << uint8(p-portD) + return avr.PORTD, 1 << uint8(p-portD) case p >= PE0 && p <= PE6: - return avr.DDRE, avr.PORTE, 1 << uint8(p-portE) + return avr.PORTE, 1 << uint8(p-portE) case p >= PF0 && p <= PF7: - return avr.DDRF, avr.PORTF, 1 << uint8(p-portF) + return avr.PORTF, 1 << uint8(p-portF) case p >= PG0 && p <= PG5: - return avr.DDRG, avr.PORTG, 1 << uint8(p-portG) + return avr.PORTG, 1 << uint8(p-portG) case p >= PH0 && p <= PH6: - return avr.DDRH, avr.PORTH, 1 << uint8(p-portH) + return avr.PORTH, 1 << uint8(p-portH) case p >= PJ0 && p <= PJ1: - return avr.DDRJ, avr.PORTJ, 1 << uint8(p-portJ) + return avr.PORTJ, 1 << uint8(p-portJ) case p >= PK0 && p <= PK7: - return avr.DDRK, avr.PORTK, 1 << uint8(p-portK) + return avr.PORTK, 1 << uint8(p-portK) case p >= PL0 && p <= PL7: - return avr.DDRL, avr.PORTL, 1 << uint8(p-portL) + return avr.PORTL, 1 << uint8(p-portL) default: - return avr.DDRB, avr.PORTA, 255 + return avr.PORTA, 255 } } diff --git a/src/machine/machine_atmega328p.go b/src/machine/machine_atmega328p.go index 58265ed1c9..ae1356e8ce 100644 --- a/src/machine/machine_atmega328p.go +++ b/src/machine/machine_atmega328p.go @@ -9,51 +9,15 @@ import ( const irq_USART0_RX = avr.IRQ_USART_RX -// Configure sets the pin to input or output. -func (p Pin) Configure(config PinConfig) { - if config.Mode == PinOutput { // set output bit - switch p / 8 { - case 0: // port B - avr.DDRB.SetBits(1 << uint8(p)) - case 1: // port C - avr.DDRC.SetBits(1 << uint8(p-8)) - case 2: // port D - avr.DDRD.SetBits(1 << uint8(p-16)) - } - } else { // configure input: clear output bit - switch p / 8 { - case 0: // port B - avr.DDRB.ClearBits(1 << uint8(p)) - case 1: // port C - avr.DDRC.ClearBits(1 << uint8(p-8)) - case 2: // port D - avr.DDRD.ClearBits(1 << uint8(p-16)) - } - } -} - -// Get returns the current value of a GPIO pin. -func (p Pin) Get() bool { - var val uint8 - switch p / 8 { - case 0: // port B - val = avr.PINB.Get() & (1 << uint8(p)) - case 1: // port C - val = avr.PINC.Get() & (1 << uint8(p-8)) - case 2: // port D - val = avr.PIND.Get() & (1 << uint8(p-16)) - } - return val != 0 -} - +// getPortMask returns the PORTx register and mask for the pin. func (p Pin) getPortMask() (*volatile.Register8, uint8) { - switch p / 8 { - case 0: // port B - return avr.PORTB, 1 << uint8(p) - case 1: - return avr.PORTC, 1 << uint8(p-8) - default: - return avr.PORTD, 1 << uint8(p-16) + switch { + case p >= PB0 && p <= PB7: // port B + return avr.PORTB, 1 << uint8(p-portB) + case p >= PC0 && p <= PC7: // port C + return avr.PORTC, 1 << uint8(p-portC) + default: // port D + return avr.PORTD, 1 << uint8(p-portD) } } diff --git a/src/machine/machine_attiny85.go b/src/machine/machine_attiny85.go index b3de12fc3b..5a6c37c7b5 100644 --- a/src/machine/machine_attiny85.go +++ b/src/machine/machine_attiny85.go @@ -16,21 +16,8 @@ const ( PB5 ) -// Configure sets the pin to input or output. -func (p Pin) Configure(config PinConfig) { - if config.Mode == PinOutput { // set output bit - avr.DDRB.SetBits(1 << uint8(p)) - } else { // configure input: clear output bit - avr.DDRB.ClearBits(1 << uint8(p)) - } -} - +// getPortMask returns the PORTx register and mask for the pin. func (p Pin) getPortMask() (*volatile.Register8, uint8) { + // Very simple for the attiny85, which only has a single port. return avr.PORTB, 1 << uint8(p) } - -// Get returns the current value of a GPIO pin. -func (p Pin) Get() bool { - val := avr.PINB.Get() & (1 << uint8(p)) - return (val > 0) -} diff --git a/src/machine/machine_avr.go b/src/machine/machine_avr.go index 7d63b04061..a1c6b069d2 100644 --- a/src/machine/machine_avr.go +++ b/src/machine/machine_avr.go @@ -5,6 +5,7 @@ package machine import ( "device/avr" "runtime/volatile" + "unsafe" ) type PinMode uint8 @@ -14,6 +15,41 @@ const ( PinOutput ) +// In all the AVRs I've looked at, the PIN/DDR/PORT registers followed a regular +// pattern: PINx, DDRx, PORTx in this order without registers in between. +// Therefore, if you know any of them, you can calculate the other two. +// +// For now, I've chosen to let the PORTx register be the one that is returned +// for each specific chip and to calculate the others from that one. Setting an +// output port (done using PORTx) is likely the most common operation and the +// one that is the most time critical. For others, the PINx and DDRx register +// can trivially be calculated using a subtraction. + +// Configure sets the pin to input or output. +func (p Pin) Configure(config PinConfig) { + port, mask := p.getPortMask() + // The DDRx register can be found by subtracting one from the PORTx + // register, as this appears to be the case for many (most? all?) AVR chips. + ddr := (*volatile.Register8)(unsafe.Pointer(uintptr(unsafe.Pointer(port)) - 1)) + if config.Mode == PinOutput { + // set output bit + ddr.SetBits(mask) + } else { + // configure input: clear output bit + ddr.ClearBits(mask) + } +} + +// Get returns the current value of a GPIO pin. +func (p Pin) Get() bool { + port, mask := p.getPortMask() + // As noted above, the PINx register is always two registers below the PORTx + // register, so we can find it simply by subtracting two from the PORTx + // register address. + pin := (*volatile.Register8)(unsafe.Pointer(uintptr(unsafe.Pointer(port)) - 2)) // PINA, PINB, etc + return (pin.Get() & mask) > 0 +} + // Set changes the value of the GPIO pin. The pin must be configured as output. func (p Pin) Set(value bool) { if value { // set bits From dda576e80bb4217ba67700e377b441783edee62d Mon Sep 17 00:00:00 2001 From: Ayke van Laethem Date: Thu, 14 May 2020 23:39:27 +0200 Subject: [PATCH 37/82] avr: add support for PinInputPullup --- src/examples/button/button.go | 2 +- src/machine/machine_avr.go | 25 +++++++++++++++++++++++++ 2 files changed, 26 insertions(+), 1 deletion(-) diff --git a/src/examples/button/button.go b/src/examples/button/button.go index ad028ea0d0..698aa7a5d0 100644 --- a/src/examples/button/button.go +++ b/src/examples/button/button.go @@ -12,7 +12,7 @@ const ( func main() { led.Configure(machine.PinConfig{Mode: machine.PinOutput}) - button.Configure(machine.PinConfig{Mode: machine.PinInput}) + button.Configure(machine.PinConfig{Mode: machine.PinInputPullup}) for { if button.Get() { diff --git a/src/machine/machine_avr.go b/src/machine/machine_avr.go index a1c6b069d2..f1dbf89d10 100644 --- a/src/machine/machine_avr.go +++ b/src/machine/machine_avr.go @@ -12,6 +12,7 @@ type PinMode uint8 const ( PinInput PinMode = iota + PinInputPullup PinOutput ) @@ -34,9 +35,33 @@ func (p Pin) Configure(config PinConfig) { if config.Mode == PinOutput { // set output bit ddr.SetBits(mask) + + // Note: if the pin was PinInputPullup before, it'll now be high. + // Otherwise it will be low. } else { // configure input: clear output bit ddr.ClearBits(mask) + + if config.Mode == PinInput { + // No pullup (floating). + // The transition may be one of the following: + // output high -> input pullup -> input (safe: output high and input pullup are similar) + // output low -> input -> input (safe: no extra transition) + port.ClearBits(mask) + } else { + // Pullup. + // The transition may be one of the following: + // output high -> input pullup -> input pullup (safe: no extra transition) + // output low -> input -> input pullup (possibly problematic) + // For the last transition (output low -> input -> input pullup), + // the transition may be problematic in some cases because there is + // an intermediate floating state (which may cause irratic + // interrupts, for example). If this is a problem, the application + // should set the pin high before configuring it as PinInputPullup. + // We can't do that here because setting it to high as an + // intermediate state may have other problems. + port.SetBits(mask) + } } } From 95f509b109d4936bec9b6020cb34fbb4bd5cba16 Mon Sep 17 00:00:00 2001 From: Brad Peabody Date: Sat, 23 May 2020 05:12:01 -0700 Subject: [PATCH 38/82] wasm test suite (#1116) * wasm: add test suite using headlless chrome --- .circleci/config.yml | 9 ++ Makefile | 3 + go.mod | 2 + go.sum | 15 +++ tests/wasm/chan_test.go | 34 ++++++ tests/wasm/event_test.go | 47 ++++++++ tests/wasm/fmt_test.go | 43 ++++++++ tests/wasm/fmtprint_test.go | 39 +++++++ tests/wasm/setup_test.go | 189 ++++++++++++++++++++++++++++++++ tests/wasm/testdata/chan.go | 16 +++ tests/wasm/testdata/event.go | 31 ++++++ tests/wasm/testdata/fmt.go | 8 ++ tests/wasm/testdata/fmtprint.go | 11 ++ 13 files changed, 447 insertions(+) create mode 100644 tests/wasm/chan_test.go create mode 100644 tests/wasm/event_test.go create mode 100644 tests/wasm/fmt_test.go create mode 100644 tests/wasm/fmtprint_test.go create mode 100644 tests/wasm/setup_test.go create mode 100644 tests/wasm/testdata/chan.go create mode 100644 tests/wasm/testdata/event.go create mode 100644 tests/wasm/testdata/fmt.go create mode 100644 tests/wasm/testdata/fmtprint.go diff --git a/.circleci/config.yml b/.circleci/config.yml index e311b6a1c4..060d665028 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -37,6 +37,13 @@ commands: sudo tar -C /usr/local -xf node-v10.15.1-linux-x64.tar.xz sudo ln -s /usr/local/node-v10.15.1-linux-x64/bin/node /usr/bin/node rm node-v10.15.1-linux-x64.tar.xz + install-chrome: + steps: + - run: + name: "Install Chrome" + command: | + wget https://dl.google.com/linux/direct/google-chrome-stable_current_amd64.deb + sudo apt install ./google-chrome-stable_current_amd64.deb llvm-source-linux: steps: - restore_cache: @@ -71,6 +78,7 @@ commands: - apt-dependencies: llvm: "<>" - install-node + - install-chrome - restore_cache: keys: - go-cache-v2-{{ checksum "go.mod" }}-{{ .Environment.CIRCLE_PREVIOUS_BUILD_NUM }} @@ -88,6 +96,7 @@ commands: - run: go test -v -tags=llvm<> ./cgo ./compileopts ./interp ./transform . - run: make gen-device -j4 - run: make smoketest + - run: make wasmtest - save_cache: key: go-cache-v2-{{ checksum "go.mod" }}-{{ .Environment.CIRCLE_BUILD_NUM }} paths: diff --git a/Makefile b/Makefile index fcdc72f7c3..82307e128e 100644 --- a/Makefile +++ b/Makefile @@ -310,6 +310,9 @@ endif $(TINYGO) build -o wasm.wasm -target=wasm examples/wasm/export $(TINYGO) build -o wasm.wasm -target=wasm examples/wasm/main +wasmtest: + $(GO) test ./tests/wasm + build/release: tinygo gen-device wasi-libc @mkdir -p build/release/tinygo/bin @mkdir -p build/release/tinygo/lib/clang/include diff --git a/go.mod b/go.mod index a2cbe4a787..a35767d3b0 100644 --- a/go.mod +++ b/go.mod @@ -4,6 +4,8 @@ go 1.11 require ( github.com/blakesmith/ar v0.0.0-20150311145944-8bd4349a67f2 + github.com/chromedp/cdproto v0.0.0-20200116234248-4da64dd111ac + github.com/chromedp/chromedp v0.5.3 github.com/google/shlex v0.0.0-20181106134648-c34317bd91bf github.com/marcinbor85/gohex v0.0.0-20180128172054-7a43cd876e46 go.bug.st/serial v1.0.0 diff --git a/go.sum b/go.sum index 09327350e7..60fdd4db1a 100644 --- a/go.sum +++ b/go.sum @@ -1,11 +1,25 @@ github.com/blakesmith/ar v0.0.0-20150311145944-8bd4349a67f2 h1:oMCHnXa6CCCafdPDbMh/lWRhRByN0VFLvv+g+ayx1SI= github.com/blakesmith/ar v0.0.0-20150311145944-8bd4349a67f2/go.mod h1:PkYb9DJNAwrSvRx5DYA+gUcOIgTGVMNkfSCbZM8cWpI= +github.com/chromedp/cdproto v0.0.0-20200116234248-4da64dd111ac h1:T7V5BXqnYd55Hj/g5uhDYumg9Fp3rMTS6bykYtTIFX4= +github.com/chromedp/cdproto v0.0.0-20200116234248-4da64dd111ac/go.mod h1:PfAWWKJqjlGFYJEidUM6aVIWPr0EpobeyVWEEmplX7g= +github.com/chromedp/chromedp v0.5.3 h1:F9LafxmYpsQhWQBdCs+6Sret1zzeeFyHS5LkRF//Ffg= +github.com/chromedp/chromedp v0.5.3/go.mod h1:YLdPtndaHQ4rCpSpBG+IPpy9JvX0VD+7aaLxYgYj28w= github.com/creack/goselect v0.1.1 h1:tiSSgKE1eJtxs1h/VgGQWuXUP0YS4CDIFMp6vaI1ls0= github.com/creack/goselect v0.1.1/go.mod h1:a/NhLweNvqIYMuxcMOuWY516Cimucms3DglDzQP3hKY= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/gobwas/httphead v0.0.0-20180130184737-2c6c146eadee h1:s+21KNqlpePfkah2I+gwHF8xmJWRjooY+5248k6m4A0= +github.com/gobwas/httphead v0.0.0-20180130184737-2c6c146eadee/go.mod h1:L0fX3K22YWvt/FAX9NnzrNzcI4wNYi9Yku4O0LKYflo= +github.com/gobwas/pool v0.2.0 h1:QEmUOlnSjWtnpRGHF3SauEiOsy82Cup83Vf2LcMlnc8= +github.com/gobwas/pool v0.2.0/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw= +github.com/gobwas/ws v1.0.2 h1:CoAavW/wd/kulfZmSIBt6p24n4j7tHgNVCjsfHVNUbo= +github.com/gobwas/ws v1.0.2/go.mod h1:szmBTxLgaFppYjEmNtny/v3w89xOydFnnZMcgRRu/EM= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/google/shlex v0.0.0-20181106134648-c34317bd91bf h1:7+FW5aGwISbqUtkfmIpZJGRgNFg2ioYPvFaUxdqpDsg= github.com/google/shlex v0.0.0-20181106134648-c34317bd91bf/go.mod h1:RpwtwJQFrIEPstU94h88MWPXP2ektJZ8cZ0YntAmXiE= +github.com/knq/sysutil v0.0.0-20191005231841-15668db23d08 h1:V0an7KRw92wmJysvFvtqtKMAPmvS5O0jtB0nYo6t+gs= +github.com/knq/sysutil v0.0.0-20191005231841-15668db23d08/go.mod h1:dFWs1zEqDjFtnBXsd1vPOZaLsESovai349994nHx3e0= +github.com/mailru/easyjson v0.7.0 h1:aizVhC/NAAcKWb+5QsU1iNOZb4Yws5UO2I+aIprQITM= +github.com/mailru/easyjson v0.7.0/go.mod h1:KAzv3t3aY1NaHWoQz1+4F1ccyAH66Jk7yos7ldAVICs= github.com/marcinbor85/gohex v0.0.0-20180128172054-7a43cd876e46 h1:wXG2bA8fO7Vv7lLk2PihFMTqmbT173Tje39oKzQ50Mo= github.com/marcinbor85/gohex v0.0.0-20180128172054-7a43cd876e46/go.mod h1:Pb6XcsXyropB9LNHhnqaknG/vEwYztLkQzVCHv8sQ3M= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= @@ -27,6 +41,7 @@ golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5h 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/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/tools v0.0.0-20190227180812-8dcc6e70cdef h1:ymc9FeDom3RIEA3coKokSllBB1hRcMT0tZ1W3Jf9Ids= golang.org/x/tools v0.0.0-20190227180812-8dcc6e70cdef/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= diff --git a/tests/wasm/chan_test.go b/tests/wasm/chan_test.go new file mode 100644 index 0000000000..1cd08e664d --- /dev/null +++ b/tests/wasm/chan_test.go @@ -0,0 +1,34 @@ +package wasm + +import ( + "testing" + "time" + + "github.com/chromedp/chromedp" +) + +func TestChan(t *testing.T) { + + t.Parallel() + + err := run("tinygo build -o " + wasmTmpDir + "/chan.wasm -target wasm testdata/chan.go") + if err != nil { + t.Fatal(err) + } + + ctx, cancel := chromectx(5 * time.Second) + defer cancel() + + err = chromedp.Run(ctx, + chromedp.Navigate("http://localhost:8826/run?file=chan.wasm"), + waitLog(`1 +2 +4 +3 +true`), + ) + if err != nil { + t.Fatal(err) + } + +} diff --git a/tests/wasm/event_test.go b/tests/wasm/event_test.go new file mode 100644 index 0000000000..d2b8340ce2 --- /dev/null +++ b/tests/wasm/event_test.go @@ -0,0 +1,47 @@ +// +build go1.14 + +package wasm + +import ( + "testing" + "time" + + "github.com/chromedp/chromedp" +) + +func TestEvent(t *testing.T) { + + t.Parallel() + + err := run("tinygo build -o " + wasmTmpDir + "/event.wasm -target wasm testdata/event.go") + if err != nil { + t.Fatal(err) + } + + ctx, cancel := chromectx(5 * time.Second) + defer cancel() + + var log1, log2 string + err = chromedp.Run(ctx, + chromedp.Navigate("http://localhost:8826/run?file=event.wasm"), + chromedp.WaitVisible("#log"), + chromedp.Sleep(time.Second), + chromedp.InnerHTML("#log", &log1), + waitLog(`1 +4`), + chromedp.Click("#testbtn"), + chromedp.Sleep(time.Second), + chromedp.InnerHTML("#log", &log2), + waitLog(`1 +4 +2 +3 +true`), + ) + t.Logf("log1: %s", log1) + t.Logf("log2: %s", log2) + if err != nil { + t.Fatal(err) + } + +} diff --git a/tests/wasm/fmt_test.go b/tests/wasm/fmt_test.go new file mode 100644 index 0000000000..8b4fe8c751 --- /dev/null +++ b/tests/wasm/fmt_test.go @@ -0,0 +1,43 @@ +// +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" + + "github.com/chromedp/chromedp" +) + +func TestFmt(t *testing.T) { + + t.Parallel() + + err := run("tinygo build -o " + wasmTmpDir + "/fmt.wasm -target wasm testdata/fmt.go") + if err != nil { + t.Fatal(err) + } + + ctx, cancel := chromectx(5 * time.Second) + defer cancel() + + var log1 string + err = chromedp.Run(ctx, + chromedp.Navigate("http://localhost:8826/run?file=fmt.wasm"), + chromedp.Sleep(time.Second), + chromedp.InnerHTML("#log", &log1), + waitLog(`did not panic`), + ) + t.Logf("log1: %s", log1) + if err != nil { + t.Fatal(err) + } + +} diff --git a/tests/wasm/fmtprint_test.go b/tests/wasm/fmtprint_test.go new file mode 100644 index 0000000000..ebd1ffa997 --- /dev/null +++ b/tests/wasm/fmtprint_test.go @@ -0,0 +1,39 @@ +// +build go1.14 + +package wasm + +import ( + "testing" + "time" + + "github.com/chromedp/chromedp" +) + +func TestFmtprint(t *testing.T) { + + t.Parallel() + + err := run("tinygo build -o " + wasmTmpDir + "/fmtprint.wasm -target wasm testdata/fmtprint.go") + if err != nil { + t.Fatal(err) + } + + ctx, cancel := chromectx(5 * time.Second) + defer cancel() + + var log1 string + err = chromedp.Run(ctx, + chromedp.Navigate("http://localhost:8826/run?file=fmtprint.wasm"), + chromedp.Sleep(time.Second), + chromedp.InnerHTML("#log", &log1), + waitLog(`test from fmtprint 1 +test from fmtprint 2 +test from fmtprint 3 +test from fmtprint 4`), + ) + t.Logf("log1: %s", log1) + if err != nil { + t.Fatal(err) + } + +} diff --git a/tests/wasm/setup_test.go b/tests/wasm/setup_test.go new file mode 100644 index 0000000000..7d5ffa675f --- /dev/null +++ b/tests/wasm/setup_test.go @@ -0,0 +1,189 @@ +package wasm + +import ( + "context" + "errors" + "flag" + "fmt" + "io/ioutil" + "log" + "net/http" + "os" + "os/exec" + "strings" + "testing" + "time" + + "github.com/chromedp/cdproto/cdp" + "github.com/chromedp/chromedp" +) + +var addr = flag.String("addr", ":8826", "Host:port to listen on for wasm test server") + +var wasmTmpDir string // set in TestMain to a temp directory for build output + +func TestMain(m *testing.M) { + flag.Parse() + + os.Exit(func() int { + + var err error + wasmTmpDir, err = ioutil.TempDir("", "wasm_test") + if err != nil { + log.Fatalf("unable to create temp dir: %v", err) + } + defer os.RemoveAll(wasmTmpDir) // cleanup even on panic and before os.Exit + + startServer(wasmTmpDir) + + return m.Run() + }()) + +} + +func run(cmdline string) error { + args := strings.Fields(cmdline) + return runargs(args...) +} + +func runargs(args ...string) error { + cmd := exec.Command(args[0], args[1:]...) + b, err := cmd.CombinedOutput() + log.Printf("Command: %s; err=%v; full output:\n%s", strings.Join(args, " "), err, b) + if err != nil { + return err + } + return nil +} + +func chromectx(timeout time.Duration) (context.Context, context.CancelFunc) { + + var ctx context.Context + + // looks for locally installed Chrome + ctx, _ = chromedp.NewContext(context.Background()) + + ctx, cancel := context.WithTimeout(ctx, timeout) + + return ctx, cancel +} + +func startServer(tmpDir string) { + + fsh := http.FileServer(http.Dir(tmpDir)) + h := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + + if r.URL.Path == "/wasm_exec.js" { + http.ServeFile(w, r, "../../targets/wasm_exec.js") + return + } + + if r.URL.Path == "/run" { + fmt.Fprintf(w, ` + + +Test + + + +
+

+
+
+
+
+`, r.FormValue("file"))
+			return
+		}
+
+		fsh.ServeHTTP(w, r)
+	})
+
+	log.Printf("Starting server at %q for dir: %s", *addr, tmpDir)
+	go func() {
+		log.Fatal(http.ListenAndServe(*addr, h))
+	}()
+
+}
+
+func waitLog(logText string) chromedp.QueryAction {
+	return waitInnerTextTrimEq("#log", logText)
+}
+
+// waitInnerTextTrimEq will wait for the innerText of the specified element to match a specific string after whitespace trimming.
+func waitInnerTextTrimEq(sel, innerText string) chromedp.QueryAction {
+
+	return chromedp.Query(sel, func(s *chromedp.Selector) {
+
+		chromedp.WaitFunc(func(ctx context.Context, cur *cdp.Frame, ids ...cdp.NodeID) ([]*cdp.Node, error) {
+
+			nodes := make([]*cdp.Node, len(ids))
+			cur.RLock()
+			for i, id := range ids {
+				nodes[i] = cur.Nodes[id]
+				if nodes[i] == nil {
+					cur.RUnlock()
+					// not yet ready
+					return nil, nil
+				}
+			}
+			cur.RUnlock()
+
+			var ret string
+			err := chromedp.EvaluateAsDevTools("document.querySelector('"+sel+"').innerText", &ret).Do(ctx)
+			if err != nil {
+				return nodes, err
+			}
+			if strings.TrimSpace(ret) != innerText {
+				// log.Printf("found text: %s", ret)
+				return nodes, errors.New("unexpected value: " + ret)
+			}
+
+			// log.Printf("NodeValue: %#v", nodes[0])
+
+			// return nil, errors.New("not ready yet")
+			return nodes, nil
+		})(s)
+
+	})
+
+}
diff --git a/tests/wasm/testdata/chan.go b/tests/wasm/testdata/chan.go
new file mode 100644
index 0000000000..8f10d1f8d8
--- /dev/null
+++ b/tests/wasm/testdata/chan.go
@@ -0,0 +1,16 @@
+package main
+
+func main() {
+
+	ch := make(chan bool, 1)
+	println("1")
+	go func() {
+		println("2")
+		ch <- true
+		println("3")
+	}()
+	println("4")
+	v := <-ch
+	println(v)
+
+}
diff --git a/tests/wasm/testdata/event.go b/tests/wasm/testdata/event.go
new file mode 100644
index 0000000000..4153774ff1
--- /dev/null
+++ b/tests/wasm/testdata/event.go
@@ -0,0 +1,31 @@
+package main
+
+import "syscall/js"
+
+func main() {
+
+	ch := make(chan bool, 1)
+
+	println("1")
+
+	js.Global().
+		Get("document").
+		Call("querySelector", "#main").
+		Set("innerHTML", ``)
+
+	js.Global().
+		Get("document").
+		Call("querySelector", "#testbtn").
+		Call("addEventListener", "click",
+			js.FuncOf(func(this js.Value, args []js.Value) interface{} {
+				println("2")
+				ch <- true
+				println("3")
+				return nil
+			}))
+
+	println("4")
+	v := <-ch
+	println(v)
+
+}
diff --git a/tests/wasm/testdata/fmt.go b/tests/wasm/testdata/fmt.go
new file mode 100644
index 0000000000..b51c564cd5
--- /dev/null
+++ b/tests/wasm/testdata/fmt.go
@@ -0,0 +1,8 @@
+package main
+
+import "fmt"
+
+func main() {
+	var _ fmt.Stringer
+	println("did not panic")
+}
diff --git a/tests/wasm/testdata/fmtprint.go b/tests/wasm/testdata/fmtprint.go
new file mode 100644
index 0000000000..1bad3361c7
--- /dev/null
+++ b/tests/wasm/testdata/fmtprint.go
@@ -0,0 +1,11 @@
+package main
+
+import "fmt"
+
+func main() {
+	fmt.Println("test from fmtprint 1")
+	fmt.Print("test from fmtprint 2\n")
+	fmt.Print("test from fmtp")
+	fmt.Print("rint 3\n")
+	fmt.Printf("test from fmtprint %d\n", 4)
+}

From 3c55689566153047f1d2e5403188557481f90d42 Mon Sep 17 00:00:00 2001
From: Ayke van Laethem 
Date: Thu, 21 May 2020 23:43:08 +0200
Subject: [PATCH 39/82] runtime: refactor time handling

This commit refactors both determining the current time and sleeping for
a given time. It also improves precision for many chips.

  * The nrf chips had a long-standing TODO comment about a slightly
    inaccurate clock. This should now be fixed.
  * The SAM D2x/D5x chips may have a slightly more accurate clock,
    although probably within the error margin of the RTC. Also, by
    working with RTC ticks and converting in the least number of places,
    code size is often slightly reduced (usually just a few bytes, up to
    around 1kB in some cases).
  * I believe the HiFive1 rev B timer was slightly wrong (32768Hz vs
    30517.6Hz). Because the datasheet says the clock runs at 32768Hz,
    I've used the same conversion code here as in the nrf and sam cases.
  * I couldn't test both stm32 timers, so I kept them as they currently
    are. It may be possible to make them more efficient by using the
    native tick frequency instead of using microseconds everywhere.
---
 src/runtime/runtime.go                  |  2 +-
 src/runtime/runtime_arm7tdmi.go         | 10 +++++--
 src/runtime/runtime_atsamd21.go         | 35 +++++++++++++++++--------
 src/runtime/runtime_atsamd51.go         | 31 +++++++++++++++-------
 src/runtime/runtime_avr.go              | 12 +++++++--
 src/runtime/runtime_cortexm_qemu.go     | 10 +++++--
 src/runtime/runtime_fe310_baremetal.go  | 16 ++++++++++-
 src/runtime/runtime_fe310_qemu.go       | 12 ++++++---
 src/runtime/runtime_nrf.go              | 22 +++++++++++++---
 src/runtime/runtime_stm32f103xx.go      | 10 +++++--
 src/runtime/runtime_stm32f407.go        | 10 +++++--
 src/runtime/runtime_tinygoriscv_qemu.go | 10 +++++--
 src/runtime/runtime_unix.go             | 13 +++++++--
 src/runtime/runtime_wasm.go             | 15 +++++++++--
 src/runtime/scheduler.go                |  6 ++---
 src/runtime/scheduler_any.go            |  2 +-
 src/runtime/scheduler_none.go           |  2 +-
 17 files changed, 168 insertions(+), 50 deletions(-)

diff --git a/src/runtime/runtime.go b/src/runtime/runtime.go
index 41d0b9ace3..3a4a8cd8d6 100644
--- a/src/runtime/runtime.go
+++ b/src/runtime/runtime.go
@@ -61,7 +61,7 @@ func memequal(x, y unsafe.Pointer, n uintptr) bool {
 }
 
 func nanotime() int64 {
-	return int64(ticks()) * tickMicros
+	return ticksToNanoseconds(ticks())
 }
 
 // timeOffset is how long the monotonic clock started after the Unix epoch. It
diff --git a/src/runtime/runtime_arm7tdmi.go b/src/runtime/runtime_arm7tdmi.go
index 0c36be540f..70b764042b 100644
--- a/src/runtime/runtime_arm7tdmi.go
+++ b/src/runtime/runtime_arm7tdmi.go
@@ -9,8 +9,6 @@ import (
 
 type timeUnit int64
 
-const tickMicros = 1
-
 func putchar(c byte) {
 	// dummy, TODO
 }
@@ -60,6 +58,14 @@ func preinit() {
 	}
 }
 
+func ticksToNanoseconds(ticks timeUnit) int64 {
+	return int64(ticks)
+}
+
+func nanosecondsToTicks(ns int64) timeUnit {
+	return timeUnit(ns)
+}
+
 func ticks() timeUnit {
 	// TODO
 	return 0
diff --git a/src/runtime/runtime_atsamd21.go b/src/runtime/runtime_atsamd21.go
index 17bba38025..1055db0c66 100644
--- a/src/runtime/runtime_atsamd21.go
+++ b/src/runtime/runtime_atsamd21.go
@@ -231,9 +231,6 @@ func waitForSync() {
 	}
 }
 
-// treat all ticks params coming from runtime as being in microseconds
-const tickMicros = 1000
-
 var (
 	timestamp        timeUnit // ticks since boottime
 	timerLastCounter uint64
@@ -243,6 +240,22 @@ var timerWakeup volatile.Register8
 
 const asyncScheduler = false
 
+// ticksToNanoseconds converts RTC ticks (at 32768Hz) to nanoseconds.
+func ticksToNanoseconds(ticks timeUnit) int64 {
+	// The following calculation is actually the following, but with both sides
+	// reduced to reduce the risk of overflow:
+	//     ticks * 1e9 / 32768
+	return int64(ticks) * 1953125 / 64
+}
+
+// nanosecondsToTicks converts nanoseconds to RTC ticks (running at 32768Hz).
+func nanosecondsToTicks(ns int64) timeUnit {
+	// The following calculation is actually the following, but with both sides
+	// reduced to reduce the risk of overflow:
+	//     ns * 32768 / 1e9
+	return timeUnit(ns * 64 / 1953125)
+}
+
 // sleepTicks should sleep for d number of microseconds.
 func sleepTicks(d timeUnit) {
 	for d != 0 {
@@ -259,22 +272,22 @@ func ticks() timeUnit {
 	sam.RTC_MODE0.READREQ.Set(sam.RTC_MODE0_READREQ_RREQ)
 	waitForSync()
 
-	rtcCounter := (uint64(sam.RTC_MODE0.COUNT.Get()) * 305) / 10 // each counter tick == 30.5us
-	offset := (rtcCounter - timerLastCounter)                    // change since last measurement
+	rtcCounter := uint64(sam.RTC_MODE0.COUNT.Get()) // each counter tick == 30.5us
+	offset := (rtcCounter - timerLastCounter)       // change since last measurement
 	timerLastCounter = rtcCounter
-	timestamp += timeUnit(offset) // TODO: not precise
+	timestamp += timeUnit(offset)
 	return timestamp
 }
 
 // ticks are in microseconds
 func timerSleep(ticks uint32) {
 	timerWakeup.Set(0)
-	if ticks < 214 {
-		// due to around 183us delay waiting for the register value to sync, the minimum sleep value
-		// for the SAMD21 is 214us.
+	if ticks < 7 {
+		// Due to around 6 clock ticks delay waiting for the register value to
+		// sync, the minimum sleep value for the SAMD21 is 214us.
 		// For related info, see:
 		// https://community.atmel.com/comment/2507091#comment-2507091
-		ticks = 214
+		ticks = 7
 	}
 
 	// request read of count
@@ -283,7 +296,7 @@ func timerSleep(ticks uint32) {
 
 	// set compare value
 	cnt := sam.RTC_MODE0.COUNT.Get()
-	sam.RTC_MODE0.COMP0.Set(uint32(cnt) + (ticks * 10 / 305)) // each counter tick == 30.5us
+	sam.RTC_MODE0.COMP0.Set(uint32(cnt) + ticks)
 	waitForSync()
 
 	// enable IRQ for CMP0 compare
diff --git a/src/runtime/runtime_atsamd51.go b/src/runtime/runtime_atsamd51.go
index 93ae1216a8..dd9da121f4 100644
--- a/src/runtime/runtime_atsamd51.go
+++ b/src/runtime/runtime_atsamd51.go
@@ -219,9 +219,6 @@ func waitForSync() {
 	}
 }
 
-// treat all ticks params coming from runtime as being in microseconds
-const tickMicros = 1000
-
 var (
 	timestamp        timeUnit // ticks since boottime
 	timerLastCounter uint64
@@ -231,6 +228,22 @@ var timerWakeup volatile.Register8
 
 const asyncScheduler = false
 
+// ticksToNanoseconds converts RTC ticks (at 32768Hz) to nanoseconds.
+func ticksToNanoseconds(ticks timeUnit) int64 {
+	// The following calculation is actually the following, but with both sides
+	// reduced to reduce the risk of overflow:
+	//     ticks * 1e9 / 32768
+	return int64(ticks) * 1953125 / 64
+}
+
+// nanosecondsToTicks converts nanoseconds to RTC ticks (running at 32768Hz).
+func nanosecondsToTicks(ns int64) timeUnit {
+	// The following calculation is actually the following, but with both sides
+	// reduced to reduce the risk of overflow:
+	//     ns * 32768 / 1e9
+	return timeUnit(ns * 64 / 1953125)
+}
+
 // sleepTicks should sleep for d number of microseconds.
 func sleepTicks(d timeUnit) {
 	for d != 0 {
@@ -245,22 +258,22 @@ func sleepTicks(d timeUnit) {
 func ticks() timeUnit {
 	waitForSync()
 
-	rtcCounter := (uint64(sam.RTC_MODE0.COUNT.Get()) * 305) / 10 // each counter tick == 30.5us
-	offset := (rtcCounter - timerLastCounter)                    // change since last measurement
+	rtcCounter := uint64(sam.RTC_MODE0.COUNT.Get())
+	offset := (rtcCounter - timerLastCounter) // change since last measurement
 	timerLastCounter = rtcCounter
-	timestamp += timeUnit(offset) // TODO: not precise
+	timestamp += timeUnit(offset)
 	return timestamp
 }
 
 // ticks are in microseconds
 func timerSleep(ticks uint32) {
 	timerWakeup.Set(0)
-	if ticks < 260 {
+	if ticks < 8 {
 		// due to delay waiting for the register value to sync, the minimum sleep value
 		// for the SAMD51 is 260us.
 		// For related info for SAMD21, see:
 		// https://community.atmel.com/comment/2507091#comment-2507091
-		ticks = 260
+		ticks = 8
 	}
 
 	// request read of count
@@ -269,7 +282,7 @@ func timerSleep(ticks uint32) {
 	// set compare value
 	cnt := sam.RTC_MODE0.COUNT.Get()
 
-	sam.RTC_MODE0.COMP[0].Set(uint32(cnt) + (ticks * 10 / 305)) // each counter tick == 30.5us
+	sam.RTC_MODE0.COMP[0].Set(uint32(cnt) + ticks)
 
 	// enable IRQ for CMP0 compare
 	sam.RTC_MODE0.INTENSET.SetBits(sam.RTC_MODE0_INTENSET_CMP0)
diff --git a/src/runtime/runtime_avr.go b/src/runtime/runtime_avr.go
index 18a7639660..ae8a4d41e6 100644
--- a/src/runtime/runtime_avr.go
+++ b/src/runtime/runtime_avr.go
@@ -14,8 +14,6 @@ type timeUnit uint32
 
 var currentTime timeUnit
 
-const tickMicros = 1024 * 16384
-
 // Watchdog timer periods. These can be off by a large margin (hence the jump
 // between 64ms and 125ms which is not an exact double), so don't rely on this
 // for accurate time keeping.
@@ -71,6 +69,16 @@ func putchar(c byte) {
 
 const asyncScheduler = false
 
+const tickNanos = 1024 * 16384 // roughly 16ms in nanoseconds
+
+func ticksToNanoseconds(ticks timeUnit) int64 {
+	return int64(ticks) * tickNanos
+}
+
+func nanosecondsToTicks(ns int64) timeUnit {
+	return timeUnit(ns / tickNanos)
+}
+
 // Sleep this number of ticks of 16ms.
 //
 // TODO: not very accurate. Improve accuracy by calibrating on startup and every
diff --git a/src/runtime/runtime_cortexm_qemu.go b/src/runtime/runtime_cortexm_qemu.go
index 1686c2d8e3..eed2015637 100644
--- a/src/runtime/runtime_cortexm_qemu.go
+++ b/src/runtime/runtime_cortexm_qemu.go
@@ -13,8 +13,6 @@ import (
 
 type timeUnit int64
 
-const tickMicros = 1
-
 var timestamp timeUnit
 
 func postinit() {}
@@ -29,6 +27,14 @@ func main() {
 
 const asyncScheduler = false
 
+func ticksToNanoseconds(ticks timeUnit) int64 {
+	return int64(ticks)
+}
+
+func nanosecondsToTicks(ns int64) timeUnit {
+	return timeUnit(ns)
+}
+
 func sleepTicks(d timeUnit) {
 	// TODO: actually sleep here for the given time.
 	timestamp += d
diff --git a/src/runtime/runtime_fe310_baremetal.go b/src/runtime/runtime_fe310_baremetal.go
index e04560c4a2..4fa62e69ae 100644
--- a/src/runtime/runtime_fe310_baremetal.go
+++ b/src/runtime/runtime_fe310_baremetal.go
@@ -6,7 +6,21 @@ import (
 	"device/riscv"
 )
 
-const tickMicros = 32768 // RTC clock runs at 32.768kHz
+// ticksToNanoseconds converts RTC ticks (at 32768Hz) to nanoseconds.
+func ticksToNanoseconds(ticks timeUnit) int64 {
+	// The following calculation is actually the following, but with both sides
+	// reduced to reduce the risk of overflow:
+	//     ticks * 1e9 / 32768
+	return int64(ticks) * 1953125 / 64
+}
+
+// nanosecondsToTicks converts nanoseconds to RTC ticks (running at 32768Hz).
+func nanosecondsToTicks(ns int64) timeUnit {
+	// The following calculation is actually the following, but with both sides
+	// reduced to reduce the risk of overflow:
+	//     ns * 32768 / 1e9
+	return timeUnit(ns * 64 / 1953125)
+}
 
 func abort() {
 	// lock up forever
diff --git a/src/runtime/runtime_fe310_qemu.go b/src/runtime/runtime_fe310_qemu.go
index 132515e795..ce14abfdd1 100644
--- a/src/runtime/runtime_fe310_qemu.go
+++ b/src/runtime/runtime_fe310_qemu.go
@@ -7,12 +7,18 @@ import (
 	"unsafe"
 )
 
-const tickMicros = 100 // CLINT.MTIME increments every 100ns
-
 // Special memory-mapped device to exit tests, created by SiFive.
 var testExit = (*volatile.Register32)(unsafe.Pointer(uintptr(0x100000)))
 
-var timestamp timeUnit
+// ticksToNanoseconds converts CLINT ticks (at 100ns per tick) to nanoseconds.
+func ticksToNanoseconds(ticks timeUnit) int64 {
+	return int64(ticks) * 100
+}
+
+// nanosecondsToTicks converts nanoseconds to CLINT ticks (at 100ns per tick).
+func nanosecondsToTicks(ns int64) timeUnit {
+	return timeUnit(ns / 100)
+}
 
 func abort() {
 	// Signal a successful exit.
diff --git a/src/runtime/runtime_nrf.go b/src/runtime/runtime_nrf.go
index c3cde00264..9a0678c6d6 100644
--- a/src/runtime/runtime_nrf.go
+++ b/src/runtime/runtime_nrf.go
@@ -12,8 +12,6 @@ import (
 
 type timeUnit int64
 
-const tickMicros = 1024 * 32
-
 //go:linkname systemInit SystemInit
 func systemInit()
 
@@ -64,7 +62,7 @@ func sleepTicks(d timeUnit) {
 	for d != 0 {
 		ticks()                       // update timestamp
 		ticks := uint32(d) & 0x7fffff // 23 bits (to be on the safe side)
-		rtc_sleep(ticks)              // TODO: not accurate (must be d / 30.5175...)
+		rtc_sleep(ticks)
 		d -= timeUnit(ticks)
 	}
 }
@@ -74,6 +72,22 @@ var (
 	rtcLastCounter uint32   // 24 bits ticks
 )
 
+// ticksToNanoseconds converts RTC ticks (at 32768Hz) to nanoseconds.
+func ticksToNanoseconds(ticks timeUnit) int64 {
+	// The following calculation is actually the following, but with both sides
+	// reduced to reduce the risk of overflow:
+	//     ticks * 1e9 / 32768
+	return int64(ticks) * 1953125 / 64
+}
+
+// nanosecondsToTicks converts nanoseconds to RTC ticks (running at 32768Hz).
+func nanosecondsToTicks(ns int64) timeUnit {
+	// The following calculation is actually the following, but with both sides
+	// reduced to reduce the risk of overflow:
+	//     ns * 32768 / 1e9
+	return timeUnit(ns * 64 / 1953125)
+}
+
 // Monotonically increasing numer of ticks since start.
 //
 // Note: very long pauses between measurements (more than 8 minutes) may
@@ -83,7 +97,7 @@ func ticks() timeUnit {
 	rtcCounter := uint32(nrf.RTC1.COUNTER.Get())
 	offset := (rtcCounter - rtcLastCounter) & 0xffffff // change since last measurement
 	rtcLastCounter = rtcCounter
-	timestamp += timeUnit(offset) // TODO: not precise
+	timestamp += timeUnit(offset)
 	return timestamp
 }
 
diff --git a/src/runtime/runtime_stm32f103xx.go b/src/runtime/runtime_stm32f103xx.go
index 8c29c264b8..7407fa738a 100644
--- a/src/runtime/runtime_stm32f103xx.go
+++ b/src/runtime/runtime_stm32f103xx.go
@@ -53,8 +53,6 @@ func initCLK() {
 	}
 }
 
-const tickMicros = 1000
-
 var (
 	timestamp        timeUnit // microseconds since boottime
 	timerLastCounter uint64
@@ -109,6 +107,14 @@ func initTIM() {
 
 const asyncScheduler = false
 
+func ticksToNanoseconds(ticks timeUnit) int64 {
+	return int64(ticks) * 1000
+}
+
+func nanosecondsToTicks(ns int64) timeUnit {
+	return timeUnit(ns / 1000)
+}
+
 // sleepTicks should sleep for specific number of microseconds.
 func sleepTicks(d timeUnit) {
 	for d != 0 {
diff --git a/src/runtime/runtime_stm32f407.go b/src/runtime/runtime_stm32f407.go
index fd1be5b43d..626dabd94c 100644
--- a/src/runtime/runtime_stm32f407.go
+++ b/src/runtime/runtime_stm32f407.go
@@ -109,8 +109,6 @@ func initCLK() {
 
 }
 
-const tickMicros = 1000
-
 var (
 	// tick in milliseconds
 	tickCount timeUnit
@@ -118,6 +116,14 @@ var (
 
 var timerWakeup volatile.Register8
 
+func ticksToNanoseconds(ticks timeUnit) int64 {
+	return int64(ticks) * 1000
+}
+
+func nanosecondsToTicks(ns int64) timeUnit {
+	return timeUnit(ns / 1000)
+}
+
 // Enable the TIM3 clock.(sleep count)
 func initTIM3() {
 	stm32.RCC.APB1ENR.SetBits(stm32.RCC_APB1ENR_TIM3EN)
diff --git a/src/runtime/runtime_tinygoriscv_qemu.go b/src/runtime/runtime_tinygoriscv_qemu.go
index 1155d553eb..27caa398ad 100644
--- a/src/runtime/runtime_tinygoriscv_qemu.go
+++ b/src/runtime/runtime_tinygoriscv_qemu.go
@@ -13,8 +13,6 @@ import (
 
 type timeUnit int64
 
-const tickMicros = 1
-
 var timestamp timeUnit
 
 func postinit() {}
@@ -28,6 +26,14 @@ func main() {
 
 const asyncScheduler = false
 
+func ticksToNanoseconds(ticks timeUnit) int64 {
+	return int64(ticks)
+}
+
+func nanosecondsToTicks(ns int64) timeUnit {
+	return timeUnit(ns)
+}
+
 func sleepTicks(d timeUnit) {
 	// TODO: actually sleep here for the given time.
 	timestamp += d
diff --git a/src/runtime/runtime_unix.go b/src/runtime/runtime_unix.go
index a23d9e0e6c..e1adeb3428 100644
--- a/src/runtime/runtime_unix.go
+++ b/src/runtime/runtime_unix.go
@@ -26,8 +26,6 @@ func clock_gettime(clk_id int32, ts *timespec)
 
 type timeUnit int64
 
-const tickMicros = 1
-
 // Note: tv_sec and tv_nsec vary in size by platform. They are 32-bit on 32-bit
 // systems and 64-bit on 64-bit systems (at least on macOS/Linux), so we can
 // simply use the 'int' type which does the same.
@@ -57,7 +55,18 @@ func putchar(c byte) {
 
 const asyncScheduler = false
 
+func ticksToNanoseconds(ticks timeUnit) int64 {
+	// The OS API works in nanoseconds so no conversion necessary.
+	return int64(ticks)
+}
+
+func nanosecondsToTicks(ns int64) timeUnit {
+	// The OS API works in nanoseconds so no conversion necessary.
+	return timeUnit(ns)
+}
+
 func sleepTicks(d timeUnit) {
+	// timeUnit is in nanoseconds, so need to convert to microseconds here.
 	usleep(uint(d) / 1000)
 }
 
diff --git a/src/runtime/runtime_wasm.go b/src/runtime/runtime_wasm.go
index 0b955c7567..3cddd9d8e7 100644
--- a/src/runtime/runtime_wasm.go
+++ b/src/runtime/runtime_wasm.go
@@ -6,8 +6,6 @@ import "unsafe"
 
 type timeUnit float64 // time in milliseconds, just like Date.now() in JavaScript
 
-const tickMicros = 1000000
-
 // Implements __wasi_ciovec_t and __wasi_iovec_t.
 type wasiIOVec struct {
 	buf    unsafe.Pointer
@@ -68,6 +66,19 @@ func go_scheduler() {
 
 const asyncScheduler = true
 
+func ticksToNanoseconds(ticks timeUnit) int64 {
+	// The JavaScript API works in float64 milliseconds, so convert to
+	// nanoseconds first before converting to a timeUnit (which is a float64),
+	// to avoid precision loss.
+	return int64(ticks * 1e6)
+}
+
+func nanosecondsToTicks(ns int64) timeUnit {
+	// The JavaScript API works in float64 milliseconds, so convert to timeUnit
+	// (which is a float64) first before dividing, to avoid precision loss.
+	return timeUnit(ns) / 1e6
+}
+
 // This function is called by the scheduler.
 // Schedule a call to runtime.scheduler, do not actually sleep.
 //export runtime.sleepTicks
diff --git a/src/runtime/scheduler.go b/src/runtime/scheduler.go
index 49cf8e4027..a8c9f16b1f 100644
--- a/src/runtime/scheduler.go
+++ b/src/runtime/scheduler.go
@@ -75,14 +75,14 @@ func runqueuePushBack(t *task.Task) {
 }
 
 // Add this task to the sleep queue, assuming its state is set to sleeping.
-func addSleepTask(t *task.Task, duration int64) {
+func addSleepTask(t *task.Task, duration timeUnit) {
 	if schedulerDebug {
-		println("  set sleep:", t, uint(duration/tickMicros))
+		println("  set sleep:", t, duration)
 		if t.Next != nil {
 			panic("runtime: addSleepTask: expected next task to be nil")
 		}
 	}
-	t.Data = uint(duration / tickMicros) // TODO: longer durations
+	t.Data = uint(duration) // TODO: longer durations
 	now := ticks()
 	if sleepQueue == nil {
 		scheduleLog("  -> sleep new queue")
diff --git a/src/runtime/scheduler_any.go b/src/runtime/scheduler_any.go
index 41a904535d..fb7ca6e964 100644
--- a/src/runtime/scheduler_any.go
+++ b/src/runtime/scheduler_any.go
@@ -7,7 +7,7 @@ import "internal/task"
 // Pause the current task for a given time.
 //go:linkname sleep time.Sleep
 func sleep(duration int64) {
-	addSleepTask(task.Current(), duration)
+	addSleepTask(task.Current(), nanosecondsToTicks(duration))
 	task.Pause()
 }
 
diff --git a/src/runtime/scheduler_none.go b/src/runtime/scheduler_none.go
index d462ca15c5..e40615fe83 100644
--- a/src/runtime/scheduler_none.go
+++ b/src/runtime/scheduler_none.go
@@ -4,7 +4,7 @@ package runtime
 
 //go:linkname sleep time.Sleep
 func sleep(duration int64) {
-	sleepTicks(timeUnit(duration / tickMicros))
+	sleepTicks(nanosecondsToTicks(duration))
 }
 
 // getSystemStackPointer returns the current stack pointer of the system stack.

From 9f4459cee160b8bf7ca39b69d223035c5635bbd3 Mon Sep 17 00:00:00 2001
From: Ayke van Laethem 
Date: Tue, 12 May 2020 20:45:58 +0200
Subject: [PATCH 40/82] arm: make FPU configuraton consistent

Eventually we might want to start using the FPU, but the easy option
right now is to simply disable it everywhere. Previously, it depended on
whether Clang was built as part of TinyGo or it was an external binary.
By setting the floating point mode explicitly, such inconsistencies are
avoided.

This commit creates a new cortex-m4 target which can be the central
place for setting FPU-related settings across all Cortex-M4 chips.
---
 builder/library.go        | 2 +-
 targets/atsamd51g19a.json | 4 +---
 targets/atsamd51j19a.json | 4 +---
 targets/atsamd51j20a.json | 4 +---
 targets/cortex-m4.json    | 8 ++++++++
 targets/nrf52.json        | 5 +----
 targets/nrf52840.json     | 5 +----
 targets/stm32f4disco.json | 4 +---
 8 files changed, 15 insertions(+), 21 deletions(-)
 create mode 100644 targets/cortex-m4.json

diff --git a/builder/library.go b/builder/library.go
index d85e4d365e..7bb506c765 100644
--- a/builder/library.go
+++ b/builder/library.go
@@ -69,7 +69,7 @@ func (l *Library) Load(target string) (path string, err error) {
 	// Precalculate the flags to the compiler invocation.
 	args := append(l.cflags(), "-c", "-Oz", "-g", "-ffunction-sections", "-fdata-sections", "-Wno-macro-redefined", "--target="+target, "-fdebug-prefix-map="+dir+"="+remapDir)
 	if strings.HasPrefix(target, "arm") || strings.HasPrefix(target, "thumb") {
-		args = append(args, "-fshort-enums", "-fomit-frame-pointer")
+		args = append(args, "-fshort-enums", "-fomit-frame-pointer", "-mfloat-abi=soft")
 	}
 	if strings.HasPrefix(target, "riscv32-") {
 		args = append(args, "-march=rv32imac", "-mabi=ilp32", "-fforce-enable-int128")
diff --git a/targets/atsamd51g19a.json b/targets/atsamd51g19a.json
index 59e2cd58cf..c3ac816089 100644
--- a/targets/atsamd51g19a.json
+++ b/targets/atsamd51g19a.json
@@ -1,9 +1,7 @@
 {
-	"inherits": ["cortex-m"],
-	"llvm-target": "armv7em-none-eabi",
+	"inherits": ["cortex-m4"],
 	"build-tags": ["atsamd51g19", "atsamd51", "sam"],
 	"cflags": [
-		"--target=armv7em-none-eabi",
 		"-Qunused-arguments"
 	],
 	"linkerscript": "targets/atsamd51.ld",
diff --git a/targets/atsamd51j19a.json b/targets/atsamd51j19a.json
index 9ddde66fee..1bd42a0ad6 100644
--- a/targets/atsamd51j19a.json
+++ b/targets/atsamd51j19a.json
@@ -1,9 +1,7 @@
 {
-	"inherits": ["cortex-m"],
-	"llvm-target": "armv7em-none-eabi",
+	"inherits": ["cortex-m4"],
 	"build-tags": ["atsamd51j19", "atsamd51", "sam"],
 	"cflags": [
-		"--target=armv7em-none-eabi",
 		"-Qunused-arguments"
 	],
 	"linkerscript": "targets/atsamd51.ld",
diff --git a/targets/atsamd51j20a.json b/targets/atsamd51j20a.json
index cd8b431cb0..2089b5d6e9 100644
--- a/targets/atsamd51j20a.json
+++ b/targets/atsamd51j20a.json
@@ -1,9 +1,7 @@
 {
-	"inherits": ["cortex-m"],
-	"llvm-target": "armv7em-none-eabi",
+	"inherits": ["cortex-m4"],
 	"build-tags": ["sam", "atsamd51", "atsamd51j20", "atsamd51j20a"],
 	"cflags": [
-		"--target=armv7em-none-eabi",
 		"-Qunused-arguments"
 	],
 	"linkerscript": "targets/atsamd51j20a.ld",
diff --git a/targets/cortex-m4.json b/targets/cortex-m4.json
new file mode 100644
index 0000000000..1d5ce2b198
--- /dev/null
+++ b/targets/cortex-m4.json
@@ -0,0 +1,8 @@
+{
+	"inherits": ["cortex-m"],
+	"llvm-target": "armv7em-none-eabi",
+	"cflags": [
+		"--target=armv7em-none-eabi",
+		"-mfloat-abi=soft"
+	]
+}
diff --git a/targets/nrf52.json b/targets/nrf52.json
index d48c9d33c2..58f42c29e0 100644
--- a/targets/nrf52.json
+++ b/targets/nrf52.json
@@ -1,10 +1,7 @@
 {
-	"inherits": ["cortex-m"],
-	"llvm-target": "armv7em-none-eabi",
+	"inherits": ["cortex-m4"],
 	"build-tags": ["nrf52", "nrf"],
 	"cflags": [
-		"--target=armv7em-none-eabi",
-		"-mfloat-abi=soft",
 		"-Qunused-arguments",
 		"-DNRF52832_XXAA",
 		"-I{root}/lib/CMSIS/CMSIS/Include",
diff --git a/targets/nrf52840.json b/targets/nrf52840.json
index 2c2eff7ca5..4ab30a2082 100644
--- a/targets/nrf52840.json
+++ b/targets/nrf52840.json
@@ -1,10 +1,7 @@
 {
-	"inherits": ["cortex-m"],
-	"llvm-target": "armv7em-none-eabi",
+	"inherits": ["cortex-m4"],
 	"build-tags": ["nrf52840", "nrf"],
 	"cflags": [
-		"--target=armv7em-none-eabi",
-		"-mfloat-abi=soft",
 		"-Qunused-arguments",
 		"-DNRF52840_XXAA",
 		"-I{root}/lib/CMSIS/CMSIS/Include",
diff --git a/targets/stm32f4disco.json b/targets/stm32f4disco.json
index c142ab23ba..889d7c5135 100644
--- a/targets/stm32f4disco.json
+++ b/targets/stm32f4disco.json
@@ -1,9 +1,7 @@
 {
-  "inherits": ["cortex-m"],
-  "llvm-target": "armv7em-none-eabi",
+  "inherits": ["cortex-m4"],
   "build-tags": ["stm32f4disco", "stm32f407", "stm32"],
   "cflags": [
-    "--target=armv7em-none-eabi",
     "-Qunused-arguments"
   ],
   "linkerscript": "targets/stm32f407.ld",

From e69131c0d346e74e6f3e7f0dc2dd10d377b48cdd Mon Sep 17 00:00:00 2001
From: Ayke van Laethem 
Date: Sun, 24 May 2020 19:33:50 +0200
Subject: [PATCH 41/82] nrf: expose the RAM base address

The RAM base address is needed during SoftDevice initialization. So far,
the same magic value has been used in aykevl/go-bluetooth and in TinyGo,
but this should be configured in only one place.

This will have additional benefits in the future:

  * It is currently set to 0x39c0, which is around 14.5kB. Most nrf51822
    chips have only 16kB of RAM, so this is way too much for those
    chips.
  * LLD in LLVM 11 allows expressions in the MEMORY part of linker
    scripts, which will allow overriding the SoftDevice RAM area with a
    linker flag, which might come in handy.
---
 targets/nrf52-s132v6.ld    | 3 +++
 targets/nrf52840-s140v7.ld | 3 +++
 2 files changed, 6 insertions(+)

diff --git a/targets/nrf52-s132v6.ld b/targets/nrf52-s132v6.ld
index 2515fca8cb..febac4746e 100644
--- a/targets/nrf52-s132v6.ld
+++ b/targets/nrf52-s132v6.ld
@@ -7,4 +7,7 @@ MEMORY
 
 _stack_size = 4K;
 
+/* This value is needed by the Nordic SoftDevice. */
+__app_ram_base = ORIGIN(RAM);
+
 INCLUDE "targets/arm.ld"
diff --git a/targets/nrf52840-s140v7.ld b/targets/nrf52840-s140v7.ld
index dde80182bd..0ff135bf61 100644
--- a/targets/nrf52840-s140v7.ld
+++ b/targets/nrf52840-s140v7.ld
@@ -7,4 +7,7 @@ MEMORY
 
 _stack_size = 4K;
 
+/* This value is needed by the Nordic SoftDevice. */
+__app_ram_base = ORIGIN(RAM);
+
 INCLUDE "targets/arm.ld"

From 67ac4fdd8ebe0b95b0e31aa4e899b324b056b9e6 Mon Sep 17 00:00:00 2001
From: Ayke van Laethem 
Date: Sun, 24 May 2020 22:55:28 +0200
Subject: [PATCH 42/82] nrf: add microbit-s110v8 target

This makes it possible to use Bluetooth on the BBC micro:bit.

Note that you need to use -programmer=cmsis-dap otherwise the SoftDevice
will be erased while flashing something that uses Bluetooth.
---
 Makefile                     |  2 ++
 targets/microbit-s110v8.json |  3 +++
 targets/nrf51-s110v8.json    |  4 ++++
 targets/nrf51-s110v8.ld      | 12 ++++++++++++
 4 files changed, 21 insertions(+)
 create mode 100644 targets/microbit-s110v8.json
 create mode 100644 targets/nrf51-s110v8.json
 create mode 100644 targets/nrf51-s110v8.ld

diff --git a/Makefile b/Makefile
index 82307e128e..02ca260501 100644
--- a/Makefile
+++ b/Makefile
@@ -229,6 +229,8 @@ smoketest:
 	@$(MD5SUM) test.hex
 	$(TINYGO) build -size short -o test.hex -target=microbit            examples/echo
 	@$(MD5SUM) test.hex
+	$(TINYGO) build -size short -o test.hex -target=microbit-s110v8     examples/echo
+	@$(MD5SUM) test.hex
 	$(TINYGO) build -size short -o test.hex -target=nrf52840-mdk        examples/blinky1
 	@$(MD5SUM) test.hex
 	$(TINYGO) build -size short -o test.hex -target=pca10031            examples/blinky1
diff --git a/targets/microbit-s110v8.json b/targets/microbit-s110v8.json
new file mode 100644
index 0000000000..02c90153ca
--- /dev/null
+++ b/targets/microbit-s110v8.json
@@ -0,0 +1,3 @@
+{
+	"inherits": ["microbit", "nrf51-s110v8"]
+}
diff --git a/targets/nrf51-s110v8.json b/targets/nrf51-s110v8.json
new file mode 100644
index 0000000000..5f3ad3ff93
--- /dev/null
+++ b/targets/nrf51-s110v8.json
@@ -0,0 +1,4 @@
+{
+	"build-tags": ["softdevice", "s110v8"],
+	"linkerscript": "targets/nrf51-s110v8.ld"
+}
diff --git a/targets/nrf51-s110v8.ld b/targets/nrf51-s110v8.ld
new file mode 100644
index 0000000000..44b4082874
--- /dev/null
+++ b/targets/nrf51-s110v8.ld
@@ -0,0 +1,12 @@
+
+MEMORY
+{
+    /* This SoftDevice requires 96K flash and 8K RAM according to the release
+     * notes of version 8.0.0 */
+    FLASH_TEXT (rw) : ORIGIN = 0x00000000 + 96K, LENGTH = 256K - 96K
+    RAM (xrw)       : ORIGIN = 0x20000000 + 8K,  LENGTH = 16K  - 8K
+}
+
+_stack_size = 2K;
+
+INCLUDE "targets/arm.ld"

From 4918395f88d8f1a38fc421566754dd2acc6e161d Mon Sep 17 00:00:00 2001
From: Brad Peabody 
Date: Sat, 23 May 2020 11:35:37 -0700
Subject: [PATCH 43/82] added test for wasm log output

callback case for log test
---
 tests/wasm/log_test.go     | 44 ++++++++++++++++++++++++++++++++++++++
 tests/wasm/setup_test.go   | 20 +++++++++++++----
 tests/wasm/testdata/log.go | 41 +++++++++++++++++++++++++++++++++++
 3 files changed, 101 insertions(+), 4 deletions(-)
 create mode 100644 tests/wasm/log_test.go
 create mode 100644 tests/wasm/testdata/log.go

diff --git a/tests/wasm/log_test.go b/tests/wasm/log_test.go
new file mode 100644
index 0000000000..a9cc1befaa
--- /dev/null
+++ b/tests/wasm/log_test.go
@@ -0,0 +1,44 @@
+// +build go1.14
+
+package wasm
+
+import (
+	"testing"
+	"time"
+
+	"github.com/chromedp/chromedp"
+)
+
+func TestLog(t *testing.T) {
+
+	t.Parallel()
+
+	err := run("tinygo build -o " + wasmTmpDir + "/log.wasm -target wasm testdata/log.go")
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	ctx, cancel := chromectx(5 * time.Second)
+	defer cancel()
+
+	var log1 string
+	err = chromedp.Run(ctx,
+		chromedp.Navigate("http://localhost:8826/run?file=log.wasm"),
+		chromedp.Sleep(time.Second),
+		chromedp.InnerHTML("#log", &log1),
+		waitLogRe(`^..../../.. ..:..:.. log 1
+..../../.. ..:..:.. log 2
+..../../.. ..:..:.. log 3
+println 4
+fmt.Println 5
+..../../.. ..:..:.. log 6
+in func 1
+..../../.. ..:..:.. in func 2
+$`),
+	)
+	t.Logf("log1: %s", log1)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+}
diff --git a/tests/wasm/setup_test.go b/tests/wasm/setup_test.go
index 7d5ffa675f..a38b426545 100644
--- a/tests/wasm/setup_test.go
+++ b/tests/wasm/setup_test.go
@@ -10,6 +10,7 @@ import (
 	"net/http"
 	"os"
 	"os/exec"
+	"regexp"
 	"strings"
 	"testing"
 	"time"
@@ -145,12 +146,23 @@ if (wasmSupported) {
 
 }
 
+// waitLog blocks until the log output equals the text provided (ignoring whitespace before and after)
 func waitLog(logText string) chromedp.QueryAction {
-	return waitInnerTextTrimEq("#log", logText)
+	return waitInnerTextTrimEq("#log", strings.TrimSpace(logText))
 }
 
-// waitInnerTextTrimEq will wait for the innerText of the specified element to match a specific string after whitespace trimming.
-func waitInnerTextTrimEq(sel, innerText string) chromedp.QueryAction {
+// waitLogRe blocks until the log output matches this regular expression
+func waitLogRe(restr string) chromedp.QueryAction {
+	return waitInnerTextMatch("#log", regexp.MustCompile(restr))
+}
+
+// waitInnerTextTrimEq will wait for the innerText of the specified element to match a specific text pattern (ignoring whitespace before and after)
+func waitInnerTextTrimEq(sel string, innerText string) chromedp.QueryAction {
+	return waitInnerTextMatch(sel, regexp.MustCompile(`^\s*`+regexp.QuoteMeta(innerText)+`\s*$`))
+}
+
+// waitInnerTextMatch will wait for the innerText of the specified element to match a specific regexp pattern
+func waitInnerTextMatch(sel string, re *regexp.Regexp) chromedp.QueryAction {
 
 	return chromedp.Query(sel, func(s *chromedp.Selector) {
 
@@ -173,7 +185,7 @@ func waitInnerTextTrimEq(sel, innerText string) chromedp.QueryAction {
 			if err != nil {
 				return nodes, err
 			}
-			if strings.TrimSpace(ret) != innerText {
+			if !re.MatchString(ret) {
 				// log.Printf("found text: %s", ret)
 				return nodes, errors.New("unexpected value: " + ret)
 			}
diff --git a/tests/wasm/testdata/log.go b/tests/wasm/testdata/log.go
new file mode 100644
index 0000000000..c16fe1d015
--- /dev/null
+++ b/tests/wasm/testdata/log.go
@@ -0,0 +1,41 @@
+package main
+
+import (
+	"fmt"
+	"log"
+	"syscall/js"
+)
+
+func main() {
+
+	// try various log and other output directly
+	log.Println("log 1")
+	log.Print("log 2")
+	log.Printf("log %d\n", 3)
+	println("println 4")
+	fmt.Println("fmt.Println 5")
+	log.Printf("log %s", "6")
+
+	// now set up some log output in a button click callback
+	js.Global().
+		Get("document").
+		Call("querySelector", "#main").
+		Set("innerHTML", ``)
+
+	js.Global().
+		Get("document").
+		Call("querySelector", "#testbtn").
+		Call("addEventListener", "click",
+			js.FuncOf(func(this js.Value, args []js.Value) interface{} {
+				println("in func 1")
+				log.Printf("in func 2")
+				return nil
+			}))
+
+	// click the button
+	js.Global().
+		Get("document").
+		Call("querySelector", "#testbtn").
+		Call("click")
+
+}

From 2a98433c8ee779d16ffa260cc0798e2ad038d0ad Mon Sep 17 00:00:00 2001
From: Ayke van Laethem 
Date: Mon, 4 May 2020 16:18:24 +0200
Subject: [PATCH 44/82] builder: move Go version code to goenv package

This is necessary to avoid a circular dependency in the loader (which
soon will need to read the Go version) and because it seems like a
better place anyway.
---
 builder/config.go |  2 +-
 builder/env.go    | 58 ------------------------------------------
 goenv/version.go  | 64 +++++++++++++++++++++++++++++++++++++++++++++++
 main.go           |  2 +-
 main_test.go      |  2 +-
 5 files changed, 67 insertions(+), 61 deletions(-)
 create mode 100644 goenv/version.go

diff --git a/builder/config.go b/builder/config.go
index aabf82ac5a..3a83f7f754 100644
--- a/builder/config.go
+++ b/builder/config.go
@@ -21,7 +21,7 @@ func NewConfig(options *compileopts.Options) (*compileopts.Config, error) {
 	if goroot == "" {
 		return nil, errors.New("cannot locate $GOROOT, please set it manually")
 	}
-	major, minor, err := getGorootVersion(goroot)
+	major, minor, err := goenv.GetGorootVersion(goroot)
 	if err != nil {
 		return nil, fmt.Errorf("could not read version from GOROOT (%v): %v", goroot, err)
 	}
diff --git a/builder/env.go b/builder/env.go
index 995ad2adcf..c59f5df44d 100644
--- a/builder/env.go
+++ b/builder/env.go
@@ -1,71 +1,13 @@
 package builder
 
 import (
-	"errors"
-	"fmt"
-	"io"
 	"io/ioutil"
 	"os"
 	"os/exec"
 	"path/filepath"
-	"regexp"
 	"sort"
-	"strings"
 )
 
-// getGorootVersion returns the major and minor version for a given GOROOT path.
-// If the goroot cannot be determined, (0, 0) is returned.
-func getGorootVersion(goroot string) (major, minor int, err error) {
-	s, err := GorootVersionString(goroot)
-	if err != nil {
-		return 0, 0, err
-	}
-
-	if s == "" || s[:2] != "go" {
-		return 0, 0, errors.New("could not parse Go version: version does not start with 'go' prefix")
-	}
-
-	parts := strings.Split(s[2:], ".")
-	if len(parts) < 2 {
-		return 0, 0, errors.New("could not parse Go version: version has less than two parts")
-	}
-
-	// Ignore the errors, we don't really handle errors here anyway.
-	var trailing string
-	n, err := fmt.Sscanf(s, "go%d.%d%s", &major, &minor, &trailing)
-	if n == 2 && err == io.EOF {
-		// Means there were no trailing characters (i.e., not an alpha/beta)
-		err = nil
-	}
-	if err != nil {
-		return 0, 0, fmt.Errorf("failed to parse version: %s", err)
-	}
-	return
-}
-
-// GorootVersionString returns the version string as reported by the Go
-// 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(
-		goroot, "src", "runtime", "internal", "sys", "zversion.go")); err == nil {
-
-		r := regexp.MustCompile("const TheVersion = `(.*)`")
-		matches := r.FindSubmatch(data)
-		if len(matches) != 2 {
-			return "", errors.New("Invalid go version output:\n" + string(data))
-		}
-
-		return string(matches[1]), nil
-
-	} else if data, err := ioutil.ReadFile(filepath.Join(goroot, "VERSION")); err == nil {
-		return string(data), nil
-
-	} else {
-		return "", err
-	}
-}
-
 // getClangHeaderPath returns the path to the built-in Clang headers. It tries
 // multiple locations, which should make it find the directory when installed in
 // various ways.
diff --git a/goenv/version.go b/goenv/version.go
new file mode 100644
index 0000000000..7c0d10f675
--- /dev/null
+++ b/goenv/version.go
@@ -0,0 +1,64 @@
+package goenv
+
+import (
+	"errors"
+	"fmt"
+	"io"
+	"io/ioutil"
+	"path/filepath"
+	"regexp"
+	"strings"
+)
+
+// GetGorootVersion returns the major and minor version for a given GOROOT path.
+// If the goroot cannot be determined, (0, 0) is returned.
+func GetGorootVersion(goroot string) (major, minor int, err error) {
+	s, err := GorootVersionString(goroot)
+	if err != nil {
+		return 0, 0, err
+	}
+
+	if s == "" || s[:2] != "go" {
+		return 0, 0, errors.New("could not parse Go version: version does not start with 'go' prefix")
+	}
+
+	parts := strings.Split(s[2:], ".")
+	if len(parts) < 2 {
+		return 0, 0, errors.New("could not parse Go version: version has less than two parts")
+	}
+
+	// Ignore the errors, we don't really handle errors here anyway.
+	var trailing string
+	n, err := fmt.Sscanf(s, "go%d.%d%s", &major, &minor, &trailing)
+	if n == 2 && err == io.EOF {
+		// Means there were no trailing characters (i.e., not an alpha/beta)
+		err = nil
+	}
+	if err != nil {
+		return 0, 0, fmt.Errorf("failed to parse version: %s", err)
+	}
+	return
+}
+
+// GorootVersionString returns the version string as reported by the Go
+// 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(
+		goroot, "src", "runtime", "internal", "sys", "zversion.go")); err == nil {
+
+		r := regexp.MustCompile("const TheVersion = `(.*)`")
+		matches := r.FindSubmatch(data)
+		if len(matches) != 2 {
+			return "", errors.New("Invalid go version output:\n" + string(data))
+		}
+
+		return string(matches[1]), nil
+
+	} else if data, err := ioutil.ReadFile(filepath.Join(goroot, "VERSION")); err == nil {
+		return string(data), nil
+
+	} else {
+		return "", err
+	}
+}
diff --git a/main.go b/main.go
index 1d2032129d..27836d1a5e 100644
--- a/main.go
+++ b/main.go
@@ -897,7 +897,7 @@ func main() {
 		usage()
 	case "version":
 		goversion := ""
-		if s, err := builder.GorootVersionString(goenv.Get("GOROOT")); err == nil {
+		if s, err := goenv.GorootVersionString(goenv.Get("GOROOT")); err == nil {
 			goversion = s
 		}
 		fmt.Printf("tinygo version %s %s/%s (using go version %s and LLVM version %s)\n", version, runtime.GOOS, runtime.GOARCH, goversion, llvm.Version)
diff --git a/main_test.go b/main_test.go
index ec7f708542..f3f10fd258 100644
--- a/main_test.go
+++ b/main_test.go
@@ -73,7 +73,7 @@ func TestCompiler(t *testing.T) {
 		t.Run("ARM64Linux", func(t *testing.T) {
 			runPlatTests("aarch64--linux-gnu", matches, t)
 		})
-		goVersion, err := builder.GorootVersionString(goenv.Get("GOROOT"))
+		goVersion, err := goenv.GorootVersionString(goenv.Get("GOROOT"))
 		if err != nil {
 			t.Error("could not get Go version:", err)
 			return

From ab2a81cc5202e6fe130badf4ee4806614b21cba3 Mon Sep 17 00:00:00 2001
From: Ayke van Laethem 
Date: Thu, 21 May 2020 00:34:01 +0200
Subject: [PATCH 45/82] main: move TinyGo version to goenv

This is needed to make it available to more packages, for caching
purposes.

For caching, the version itself may not be enough during development.
But for regular releases, the version provides some protection against
accidentally using a cache entry that is invalid in a newer version.
---
 Makefile         | 2 +-
 goenv/version.go | 4 ++++
 main.go          | 4 ++--
 version.go       | 5 -----
 4 files changed, 7 insertions(+), 8 deletions(-)
 delete mode 100644 version.go

diff --git a/Makefile b/Makefile
index 02ca260501..02d6a099c9 100644
--- a/Makefile
+++ b/Makefile
@@ -359,4 +359,4 @@ deb: build/release
 	@mkdir -p build/release-deb/usr/local/lib
 	cp -ar build/release/tinygo build/release-deb/usr/local/lib/tinygo
 	ln -sf ../lib/tinygo/bin/tinygo build/release-deb/usr/local/bin/tinygo
-	fpm -f -s dir -t deb -n tinygo -v $(shell grep "version = " version.go | awk '{print $$NF}') -m '@tinygo-org' --description='TinyGo is a Go compiler for small places.' --license='BSD 3-Clause' --url=https://tinygo.org/ --deb-changelog CHANGELOG.md -p build/release.deb -C ./build/release-deb
+	fpm -f -s dir -t deb -n tinygo -v $(shell grep "const Version = " goenv/version.go | awk '{print $$NF}') -m '@tinygo-org' --description='TinyGo is a Go compiler for small places.' --license='BSD 3-Clause' --url=https://tinygo.org/ --deb-changelog CHANGELOG.md -p build/release.deb -C ./build/release-deb
diff --git a/goenv/version.go b/goenv/version.go
index 7c0d10f675..5114470536 100644
--- a/goenv/version.go
+++ b/goenv/version.go
@@ -10,6 +10,10 @@ import (
 	"strings"
 )
 
+// Version of TinyGo.
+// Update this value before release of new version of software.
+const Version = "0.14.0-dev"
+
 // GetGorootVersion returns the major and minor version for a given GOROOT path.
 // If the goroot cannot be determined, (0, 0) is returned.
 func GetGorootVersion(goroot string) (major, minor int, err error) {
diff --git a/main.go b/main.go
index 27836d1a5e..9fe0b24002 100644
--- a/main.go
+++ b/main.go
@@ -632,7 +632,7 @@ func getDefaultPort() (port string, err error) {
 
 func usage() {
 	fmt.Fprintln(os.Stderr, "TinyGo is a Go compiler for small places.")
-	fmt.Fprintln(os.Stderr, "version:", version)
+	fmt.Fprintln(os.Stderr, "version:", goenv.Version)
 	fmt.Fprintf(os.Stderr, "usage: %s command [-printir] [-target=] -o  \n", os.Args[0])
 	fmt.Fprintln(os.Stderr, "\ncommands:")
 	fmt.Fprintln(os.Stderr, "  build: compile packages and dependencies")
@@ -900,7 +900,7 @@ func main() {
 		if s, err := goenv.GorootVersionString(goenv.Get("GOROOT")); err == nil {
 			goversion = s
 		}
-		fmt.Printf("tinygo version %s %s/%s (using go version %s and LLVM version %s)\n", version, runtime.GOOS, runtime.GOARCH, goversion, llvm.Version)
+		fmt.Printf("tinygo version %s %s/%s (using go version %s and LLVM version %s)\n", goenv.Version, runtime.GOOS, runtime.GOARCH, goversion, llvm.Version)
 	case "env":
 		if flag.NArg() == 0 {
 			// Show all environment variables.
diff --git a/version.go b/version.go
deleted file mode 100644
index 0d35efa1f8..0000000000
--- a/version.go
+++ /dev/null
@@ -1,5 +0,0 @@
-package main
-
-// version of this package.
-// Update this value before release of new version of software.
-const version = "0.14.0-dev"

From bde73fc214f996b3ecae318972f2e2263e5ddb25 Mon Sep 17 00:00:00 2001
From: Ayke van Laethem 
Date: Mon, 4 May 2020 22:44:24 +0200
Subject: [PATCH 46/82] main: fix test subcommand

It was broken for quite some time without anybody noticing...
---
 main.go | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/main.go b/main.go
index 9fe0b24002..64f7036c37 100644
--- a/main.go
+++ b/main.go
@@ -124,6 +124,7 @@ func Build(pkgName, outpath string, options *compileopts.Options) error {
 
 // Test runs the tests in the given package.
 func Test(pkgName string, options *compileopts.Options) error {
+	options.TestConfig.CompileTestBinary = true
 	config, err := builder.NewConfig(options)
 	if err != nil {
 		return err
@@ -135,7 +136,6 @@ func Test(pkgName string, options *compileopts.Options) error {
 	// For details: https://github.com/golang/go/issues/21360
 	config.Target.BuildTags = append(config.Target.BuildTags, "test")
 
-	options.TestConfig.CompileTestBinary = true
 	return builder.Build(pkgName, ".elf", config, func(tmppath string) error {
 		cmd := exec.Command(tmppath)
 		cmd.Stdout = os.Stdout

From 35015a791862c5afb5c929988ed7f6862dd3086b Mon Sep 17 00:00:00 2001
From: Ayke van Laethem 
Date: Thu, 5 Mar 2020 16:23:23 +0100
Subject: [PATCH 47/82] loader: merge roots from both Go and TinyGo in a cached
 directory

This commit changes the way that packages are looked up. Instead of
working around the loader package by modifying the GOROOT variable for
specific packages, create a new GOROOT using symlinks. This GOROOT is
cached for the specified configuration (Go version, underlying GOROOT
path, TinyGo path, whether to override the syscall package).

This will also enable go module support in the future.

Windows is a bit harder to support, because it only allows the creation
of symlinks when developer mode is enabled. This is worked around by
using symlinks and if that fails, using directory junctions or hardlinks
instead. This should work in the vast majority of cases. The only case
it doesn't work, is if developer mode is disabled and TinyGo, the Go
toolchain, and the cache directory are not all on the same filesystem.
If this is a problem, it is still possible to improve the code by using
file copies instead.

As a side effect, this also makes diagnostics use a relative file path
only when the file is not in GOROOT or in TINYGOROOT.
---
 compiler/compiler.go |  49 +--------
 loader/goroot.go     | 240 +++++++++++++++++++++++++++++++++++++++++++
 loader/loader.go     |  35 ++++---
 main.go              |  95 +++++++++++++++--
 4 files changed, 355 insertions(+), 64 deletions(-)
 create mode 100644 loader/goroot.go

diff --git a/compiler/compiler.go b/compiler/compiler.go
index 5ed85c0538..d4dedf7f71 100644
--- a/compiler/compiler.go
+++ b/compiler/compiler.go
@@ -137,64 +137,25 @@ func Compile(pkgName string, machine llvm.TargetMachine, config *compileopts.Con
 	c.funcPtrAddrSpace = dummyFunc.Type().PointerAddressSpace()
 	dummyFunc.EraseFromParentAsFunction()
 
-	// Prefix the GOPATH with the system GOROOT, as GOROOT is already set to
-	// the TinyGo root.
-	overlayGopath := goenv.Get("GOPATH")
-	if overlayGopath == "" {
-		overlayGopath = goenv.Get("GOROOT")
-	} else {
-		overlayGopath = goenv.Get("GOROOT") + string(filepath.ListSeparator) + overlayGopath
-	}
-
 	wd, err := os.Getwd()
 	if err != nil {
 		return c.mod, nil, nil, []error{err}
 	}
+	goroot, err := loader.GetCachedGoroot(c.Config)
+	if err != nil {
+		return c.mod, nil, []error{err}
+	}
 	lprogram := &loader.Program{
 		Build: &build.Context{
 			GOARCH:      c.GOARCH(),
 			GOOS:        c.GOOS(),
-			GOROOT:      goenv.Get("GOROOT"),
+			GOROOT:      goroot,
 			GOPATH:      goenv.Get("GOPATH"),
 			CgoEnabled:  c.CgoEnabled(),
 			UseAllFiles: false,
 			Compiler:    "gc", // must be one of the recognized compilers
 			BuildTags:   c.BuildTags(),
 		},
-		OverlayBuild: &build.Context{
-			GOARCH:      c.GOARCH(),
-			GOOS:        c.GOOS(),
-			GOROOT:      goenv.Get("TINYGOROOT"),
-			GOPATH:      overlayGopath,
-			CgoEnabled:  c.CgoEnabled(),
-			UseAllFiles: false,
-			Compiler:    "gc", // must be one of the recognized compilers
-			BuildTags:   c.BuildTags(),
-		},
-		OverlayPath: func(path string) string {
-			// Return the (overlay) import path when it should be overlaid, and
-			// "" if it should not.
-			if strings.HasPrefix(path, tinygoPath+"/src/") {
-				// Avoid issues with packages that are imported twice, one from
-				// GOPATH and one from TINYGOPATH.
-				path = path[len(tinygoPath+"/src/"):]
-			}
-			switch path {
-			case "machine", "os", "reflect", "runtime", "runtime/interrupt", "runtime/volatile", "sync", "testing", "internal/reflectlite", "internal/bytealg", "internal/task":
-				return path
-			default:
-				if strings.HasPrefix(path, "device/") || strings.HasPrefix(path, "examples/") {
-					return path
-				} else if path == "syscall" {
-					for _, tag := range c.BuildTags() {
-						if tag == "baremetal" || tag == "darwin" {
-							return path
-						}
-					}
-				}
-			}
-			return ""
-		},
 		TypeChecker: types.Config{
 			Sizes: &stdSizes{
 				IntSize:  int64(c.targetData.TypeAllocSize(c.intType)),
diff --git a/loader/goroot.go b/loader/goroot.go
new file mode 100644
index 0000000000..7947d15e21
--- /dev/null
+++ b/loader/goroot.go
@@ -0,0 +1,240 @@
+package loader
+
+// This file constructs a new temporary GOROOT directory by merging both the
+// standard Go GOROOT and the GOROOT from TinyGo using symlinks.
+
+import (
+	"crypto/sha512"
+	"encoding/hex"
+	"errors"
+	"fmt"
+	"io/ioutil"
+	"math/rand"
+	"os"
+	"os/exec"
+	"path"
+	"path/filepath"
+	"runtime"
+	"strconv"
+
+	"github.com/tinygo-org/tinygo/compileopts"
+	"github.com/tinygo-org/tinygo/goenv"
+)
+
+// GetCachedGoroot creates a new GOROOT by merging both the standard GOROOT and
+// the GOROOT from TinyGo using lots of symbolic links.
+func GetCachedGoroot(config *compileopts.Config) (string, error) {
+	goroot := goenv.Get("GOROOT")
+	if goroot == "" {
+		return "", errors.New("could not determine GOROOT")
+	}
+	tinygoroot := goenv.Get("TINYGOROOT")
+	if tinygoroot == "" {
+		return "", errors.New("could not determine TINYGOROOT")
+	}
+
+	// Determine the location of the cached GOROOT.
+	version, err := goenv.GorootVersionString(goroot)
+	if err != nil {
+		return "", err
+	}
+	// This hash is really a cache key, that contains (hopefully) enough
+	// information to make collisions unlikely during development.
+	// By including the Go version and TinyGo version, cache collisions should
+	// not happen outside of development.
+	hash := sha512.New512_256()
+	fmt.Fprintln(hash, goroot)
+	fmt.Fprintln(hash, version)
+	fmt.Fprintln(hash, goenv.Version)
+	fmt.Fprintln(hash, tinygoroot)
+	gorootsHash := hash.Sum(nil)
+	gorootsHashHex := hex.EncodeToString(gorootsHash[:])
+	cachedgoroot := filepath.Join(goenv.Get("GOCACHE"), "goroot-"+version+"-"+gorootsHashHex)
+	if needsSyscallPackage(config.BuildTags()) {
+		cachedgoroot += "-syscall"
+	}
+
+	if _, err := os.Stat(cachedgoroot); err == nil {
+		return cachedgoroot, nil
+	}
+	tmpgoroot := cachedgoroot + ".tmp" + strconv.Itoa(rand.Int())
+	err = os.MkdirAll(tmpgoroot, 0777)
+	if err != nil {
+		return "", err
+	}
+
+	// Remove the temporary directory if it wasn't moved to the right place
+	// (for example, when there was an error).
+	defer os.RemoveAll(tmpgoroot)
+
+	for _, name := range []string{"bin", "lib", "pkg"} {
+		err = symlink(filepath.Join(goroot, name), filepath.Join(tmpgoroot, name))
+		if err != nil {
+			return "", err
+		}
+	}
+	err = mergeDirectory(goroot, tinygoroot, tmpgoroot, "", pathsToOverride(needsSyscallPackage(config.BuildTags())))
+	if err != nil {
+		return "", err
+	}
+	err = os.Rename(tmpgoroot, cachedgoroot)
+	if err != nil {
+		if os.IsExist(err) {
+			// Another invocation of TinyGo also seems to have created a GOROOT.
+			// Use that one instead. Our new GOROOT will be automatically
+			// deleted by the defer above.
+			return cachedgoroot, nil
+		}
+		return "", err
+	}
+	return cachedgoroot, nil
+}
+
+// mergeDirectory merges two roots recursively. The tmpgoroot is the directory
+// that will be created by this call by either symlinking the directory from
+// goroot or tinygoroot, or by creating the directory and merging the contents.
+func mergeDirectory(goroot, tinygoroot, tmpgoroot, importPath string, overrides map[string]bool) error {
+	if mergeSubdirs, ok := overrides[importPath+"/"]; ok {
+		if !mergeSubdirs {
+			// This directory and all subdirectories should come from the TinyGo
+			// root, so simply make a symlink.
+			newname := filepath.Join(tmpgoroot, "src", importPath)
+			oldname := filepath.Join(tinygoroot, "src", importPath)
+			return symlink(oldname, newname)
+		}
+
+		// Merge subdirectories. Start by making the directory to merge.
+		err := os.Mkdir(filepath.Join(tmpgoroot, "src", importPath), 0777)
+		if err != nil {
+			return err
+		}
+
+		// Symlink all files from TinyGo, and symlink directories from TinyGo
+		// that need to be overridden.
+		tinygoEntries, err := ioutil.ReadDir(filepath.Join(tinygoroot, "src", importPath))
+		if err != nil {
+			return err
+		}
+		for _, e := range tinygoEntries {
+			if e.IsDir() {
+				// A directory, so merge this thing.
+				err := mergeDirectory(goroot, tinygoroot, tmpgoroot, path.Join(importPath, e.Name()), overrides)
+				if err != nil {
+					return err
+				}
+			} else {
+				// A file, so symlink this.
+				newname := filepath.Join(tmpgoroot, "src", importPath, e.Name())
+				oldname := filepath.Join(tinygoroot, "src", importPath, e.Name())
+				err := symlink(oldname, newname)
+				if err != nil {
+					return err
+				}
+			}
+		}
+
+		// Symlink all directories from $GOROOT that are not part of the TinyGo
+		// overrides.
+		gorootEntries, err := ioutil.ReadDir(filepath.Join(goroot, "src", importPath))
+		if err != nil {
+			return err
+		}
+		for _, e := range gorootEntries {
+			if !e.IsDir() {
+				// Don't merge in files from Go. Otherwise we'd end up with a
+				// weird syscall package with files from both roots.
+				continue
+			}
+			if _, ok := overrides[path.Join(importPath, e.Name())+"/"]; ok {
+				// Already included above, so don't bother trying to create this
+				// symlink.
+				continue
+			}
+			newname := filepath.Join(tmpgoroot, "src", importPath, e.Name())
+			oldname := filepath.Join(goroot, "src", importPath, e.Name())
+			err := symlink(oldname, newname)
+			if err != nil {
+				return err
+			}
+		}
+	}
+	return nil
+}
+
+// needsSyscallPackage returns whether the syscall package should be overriden
+// 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" {
+			return true
+		}
+	}
+	return false
+}
+
+// The boolean indicates whether to merge the subdirs. True means merge, false
+// means use the TinyGo version.
+func pathsToOverride(needsSyscallPackage bool) map[string]bool {
+	paths := map[string]bool{
+		"/":                     true,
+		"device/":               false,
+		"examples/":             false,
+		"internal/":             true,
+		"internal/bytealg/":     false,
+		"internal/reflectlite/": false,
+		"internal/task/":        false,
+		"machine/":              false,
+		"os/":                   true,
+		"reflect/":              false,
+		"runtime/":              false,
+		"sync/":                 true,
+		"testing/":              false,
+	}
+	if needsSyscallPackage {
+		paths["syscall/"] = true // include syscall/js
+	}
+	return paths
+}
+
+// symlink creates a symlink or something similar. On Unix-like systems, it
+// always creates a symlink. On Windows, it tries to create a symlink and if
+// that fails, creates a hardlink or directory junction instead.
+//
+// Note that while Windows 10 does support symlinks and allows them to be
+// created using os.Symlink, it requires developer mode to be enabled.
+// Therefore provide a fallback for when symlinking is not possible.
+// Unfortunately this fallback only works when TinyGo is installed on the same
+// filesystem as the TinyGo cache and the Go installation (which is usually the
+// C drive).
+func symlink(oldname, newname string) error {
+	symlinkErr := os.Symlink(oldname, newname)
+	if runtime.GOOS == "windows" && symlinkErr != nil {
+		// Fallback for when developer mode is disabled.
+		// Note that we return the symlink error even if something else fails
+		// later on. This is because symlinks are the easiest to support
+		// (they're also used on Linux and MacOS) and enabling them is easy:
+		// just enable developer mode.
+		st, err := os.Stat(oldname)
+		if err != nil {
+			return symlinkErr
+		}
+		if st.IsDir() {
+			// Make a directory junction. There may be a way to do this
+			// programmatically, but it involves a lot of magic. Use the mklink
+			// command built into cmd instead (mklink is a builtin, not an
+			// external command).
+			err := exec.Command("cmd", "/k", "mklink", "/J", newname, oldname).Run()
+			if err != nil {
+				return symlinkErr
+			}
+		} else {
+			// Make a hard link.
+			err := os.Link(oldname, newname)
+			if err != nil {
+				return symlinkErr
+			}
+		}
+		return nil // success
+	}
+	return symlinkErr
+}
diff --git a/loader/loader.go b/loader/loader.go
index 28ade8d2ef..e5de53c1fa 100644
--- a/loader/loader.go
+++ b/loader/loader.go
@@ -16,14 +16,13 @@ import (
 	"text/template"
 
 	"github.com/tinygo-org/tinygo/cgo"
+	"github.com/tinygo-org/tinygo/goenv"
 )
 
 // Program holds all packages and some metadata about the program as a whole.
 type Program struct {
 	mainPkg      string
 	Build        *build.Context
-	OverlayBuild *build.Context
-	OverlayPath  func(path string) string
 	Packages     map[string]*Package
 	sorted       []*Package
 	fset         *token.FileSet
@@ -55,10 +54,6 @@ func (p *Program) Import(path, srcDir string, pos token.Position) (*Package, err
 
 	// Load this package.
 	ctx := p.Build
-	if newPath := p.OverlayPath(path); newPath != "" {
-		ctx = p.OverlayBuild
-		path = newPath
-	}
 	buildPkg, err := ctx.Import(path, srcDir, build.ImportComment)
 	if err != nil {
 		return nil, scanner.Error{
@@ -320,14 +315,30 @@ func (p *Program) parseFile(path string, mode parser.Mode) (*ast.File, error) {
 		return nil, err
 	}
 	defer rd.Close()
-	relpath := path
-	if filepath.IsAbs(path) {
-		rp, err := filepath.Rel(p.Dir, path)
-		if err == nil {
-			relpath = rp
+	diagnosticPath := path
+	if strings.HasPrefix(path, p.Build.GOROOT+string(filepath.Separator)) {
+		// If this file is part of the synthetic GOROOT, try to infer the
+		// original path.
+		relpath := path[len(filepath.Join(p.Build.GOROOT, "src"))+1:]
+		realgorootPath := filepath.Join(goenv.Get("GOROOT"), "src", relpath)
+		if _, err := os.Stat(realgorootPath); err == nil {
+			diagnosticPath = realgorootPath
+		}
+		maybeInTinyGoRoot := false
+		for prefix := range pathsToOverride(needsSyscallPackage(p.Build.BuildTags)) {
+			if !strings.HasPrefix(relpath, prefix) {
+				continue
+			}
+			maybeInTinyGoRoot = true
+		}
+		if maybeInTinyGoRoot {
+			tinygoPath := filepath.Join(p.TINYGOROOT, "src", relpath)
+			if _, err := os.Stat(tinygoPath); err == nil {
+				diagnosticPath = tinygoPath
+			}
 		}
 	}
-	return parser.ParseFile(p.fset, relpath, rd, mode)
+	return parser.ParseFile(p.fset, diagnosticPath, rd, mode)
 }
 
 // Parse parses and typechecks this package.
diff --git a/main.go b/main.go
index 64f7036c37..0d711ab352 100644
--- a/main.go
+++ b/main.go
@@ -630,6 +630,36 @@ func getDefaultPort() (port string, err error) {
 	return d[0], nil
 }
 
+// runGoList runs the `go list` command but using the configuration used for
+// TinyGo.
+func runGoList(config *compileopts.Config, flagJSON, flagDeps bool, pkgs []string) error {
+	goroot, err := loader.GetCachedGoroot(config)
+	if err != nil {
+		return err
+	}
+	args := []string{"list"}
+	if flagJSON {
+		args = append(args, "-json")
+	}
+	if flagDeps {
+		args = append(args, "-deps")
+	}
+	if len(config.BuildTags()) != 0 {
+		args = append(args, "-tags", strings.Join(config.BuildTags(), " "))
+	}
+	args = append(args, pkgs...)
+	cgoEnabled := "0"
+	if config.CgoEnabled() {
+		cgoEnabled = "1"
+	}
+	cmd := exec.Command("go", args...)
+	cmd.Stdout = os.Stdout
+	cmd.Stderr = os.Stderr
+	cmd.Env = append(os.Environ(), "GOROOT="+goroot, "GOOS="+config.GOOS(), "GOARCH="+config.GOARCH(), "CGO_ENABLED="+cgoEnabled)
+	cmd.Run()
+	return nil
+}
+
 func usage() {
 	fmt.Fprintln(os.Stderr, "TinyGo is a Go compiler for small places.")
 	fmt.Fprintln(os.Stderr, "version:", goenv.Version)
@@ -641,12 +671,27 @@ func usage() {
 	fmt.Fprintln(os.Stderr, "  flash: compile and flash to the device")
 	fmt.Fprintln(os.Stderr, "  gdb:   run/flash and immediately enter GDB")
 	fmt.Fprintln(os.Stderr, "  env:   list environment variables used during build")
+	fmt.Fprintln(os.Stderr, "  list:  run go list using the TinyGo root")
 	fmt.Fprintln(os.Stderr, "  clean: empty cache directory ("+goenv.Get("GOCACHE")+")")
 	fmt.Fprintln(os.Stderr, "  help:  print this help text")
 	fmt.Fprintln(os.Stderr, "\nflags:")
 	flag.PrintDefaults()
 }
 
+// try to make the path relative to the current working directory. If any error
+// occurs, this error is ignored and the absolute path is returned instead.
+func tryToMakePathRelative(dir string) string {
+	wd, err := os.Getwd()
+	if err != nil {
+		return dir
+	}
+	relpath, err := filepath.Rel(wd, dir)
+	if err != nil {
+		return dir
+	}
+	return relpath
+}
+
 // printCompilerError prints compiler errors using the provided logger function
 // (similar to fmt.Println).
 //
@@ -654,8 +699,24 @@ func usage() {
 // to limitations in the LLVM bindings.
 func printCompilerError(logln func(...interface{}), err error) {
 	switch err := err.(type) {
-	case types.Error, scanner.Error:
+	case types.Error:
+		printCompilerError(logln, scanner.Error{
+			Pos: err.Fset.Position(err.Pos),
+			Msg: err.Msg,
+		})
+	case scanner.Error:
+		if !strings.HasPrefix(err.Pos.Filename, filepath.Join(goenv.Get("GOROOT"), "src")) && !strings.HasPrefix(err.Pos.Filename, filepath.Join(goenv.Get("TINYGOROOT"), "src")) {
+			// This file is not from the standard library (either the GOROOT or
+			// the TINYGOROOT). Make the path relative, for easier reading.
+			// Ignore any errors in the process (falling back to the absolute
+			// path).
+			err.Pos.Filename = tryToMakePathRelative(err.Pos.Filename)
+		}
 		logln(err)
+	case scanner.ErrorList:
+		for _, scannerErr := range err {
+			printCompilerError(logln, *scannerErr)
+		}
 	case *interp.Error:
 		logln("#", err.ImportPath)
 		logln(err.Error())
@@ -674,11 +735,11 @@ func printCompilerError(logln func(...interface{}), err error) {
 	case loader.Errors:
 		logln("#", err.Pkg.ImportPath)
 		for _, err := range err.Errs {
-			logln(err)
+			printCompilerError(logln, err)
 		}
 	case *builder.MultiError:
 		for _, err := range err.Errs {
-			logln(err)
+			printCompilerError(logln, err)
 		}
 	default:
 		logln("error:", err)
@@ -695,6 +756,13 @@ func handleCompilerError(err error) {
 }
 
 func main() {
+	if len(os.Args) < 2 {
+		fmt.Fprintln(os.Stderr, "No command-line arguments supplied.")
+		usage()
+		os.Exit(1)
+	}
+	command := os.Args[1]
+
 	outpath := flag.String("o", "", "output filename")
 	opt := flag.String("opt", "z", "optimization level: 0, 1, 2, s, z")
 	gc := flag.String("gc", "", "garbage collector to use (none, leaking, extalloc, conservative)")
@@ -715,12 +783,11 @@ func main() {
 	wasmAbi := flag.String("wasm-abi", "js", "WebAssembly ABI conventions: js (no i64 params) or generic")
 	heapSize := flag.String("heap-size", "1M", "default heap size in bytes (only supported by WebAssembly)")
 
-	if len(os.Args) < 2 {
-		fmt.Fprintln(os.Stderr, "No command-line arguments supplied.")
-		usage()
-		os.Exit(1)
+	var flagJSON, flagDeps *bool
+	if command == "list" {
+		flagJSON = flag.Bool("json", false, "print data in JSON format")
+		flagDeps = flag.Bool("deps", false, "")
 	}
-	command := os.Args[1]
 
 	// Early command processing, before commands are interpreted by the Go flag
 	// library.
@@ -886,6 +953,18 @@ func main() {
 		fmt.Printf("build tags:        %s\n", strings.Join(config.BuildTags(), " "))
 		fmt.Printf("garbage collector: %s\n", config.GC())
 		fmt.Printf("scheduler:         %s\n", config.Scheduler())
+	case "list":
+		config, err := builder.NewConfig(options)
+		if err != nil {
+			fmt.Fprintln(os.Stderr, err)
+			usage()
+			os.Exit(1)
+		}
+		err = runGoList(config, *flagJSON, *flagDeps, flag.Args())
+		if err != nil {
+			fmt.Fprintln(os.Stderr, "failed to run `go list`:", err)
+			os.Exit(1)
+		}
 	case "clean":
 		// remove cache directory
 		err := os.RemoveAll(goenv.Get("GOCACHE"))

From 4ca2d3f0cf7b7e7b08770bd94be6edc98b7d48dd Mon Sep 17 00:00:00 2001
From: Ayke van Laethem 
Date: Mon, 4 May 2020 23:15:02 +0200
Subject: [PATCH 48/82] loader: load packages using Go modules

This commit replaces the existing ad-hoc package loader with a package
loader that uses the x/tools/go/packages package to find all
to-be-loaded packages.
---
 compiler/compiler.go |  32 ++--
 ir/ir.go             |  67 +-------
 loader/errors.go     |  27 ---
 loader/loader.go     | 390 ++++++++++++++++++-------------------------
 main.go              |   2 +-
 5 files changed, 177 insertions(+), 341 deletions(-)

diff --git a/compiler/compiler.go b/compiler/compiler.go
index d4dedf7f71..37858ea305 100644
--- a/compiler/compiler.go
+++ b/compiler/compiler.go
@@ -143,7 +143,7 @@ func Compile(pkgName string, machine llvm.TargetMachine, config *compileopts.Con
 	}
 	goroot, err := loader.GetCachedGoroot(c.Config)
 	if err != nil {
-		return c.mod, nil, []error{err}
+		return c.mod, nil, nil, []error{err}
 	}
 	lprogram := &loader.Program{
 		Build: &build.Context{
@@ -156,6 +156,7 @@ func Compile(pkgName string, machine llvm.TargetMachine, config *compileopts.Con
 			Compiler:    "gc", // must be one of the recognized compilers
 			BuildTags:   c.BuildTags(),
 		},
+		Tests: c.TestConfig.CompileTestBinary,
 		TypeChecker: types.Config{
 			Sizes: &stdSizes{
 				IntSize:  int64(c.targetData.TypeAllocSize(c.intType)),
@@ -169,33 +170,17 @@ func Compile(pkgName string, machine llvm.TargetMachine, config *compileopts.Con
 		ClangHeaders: c.ClangHeaders,
 	}
 
-	if strings.HasSuffix(pkgName, ".go") {
-		_, err = lprogram.ImportFile(pkgName)
-		if err != nil {
-			return c.mod, nil, nil, []error{err}
-		}
-	} else {
-		_, err = lprogram.Import(pkgName, wd, token.Position{
-			Filename: "build command-line-arguments",
-		})
-		if err != nil {
-			return c.mod, nil, nil, []error{err}
-		}
-	}
-
-	_, err = lprogram.Import("runtime", "", token.Position{
-		Filename: "build default import",
-	})
+	err = lprogram.Load(pkgName)
 	if err != nil {
 		return c.mod, nil, nil, []error{err}
 	}
 
-	err = lprogram.Parse(c.TestConfig.CompileTestBinary)
+	err = lprogram.Parse()
 	if err != nil {
 		return c.mod, nil, nil, []error{err}
 	}
 
-	c.ir = ir.NewProgram(lprogram, pkgName)
+	c.ir = ir.NewProgram(lprogram)
 
 	// Run a simple dead code elimination pass.
 	err = c.ir.SimpleDCE()
@@ -339,8 +324,11 @@ func Compile(pkgName string, machine llvm.TargetMachine, config *compileopts.Con
 	// Gather the list of (C) file paths that should be included in the build.
 	var extraFiles []string
 	for _, pkg := range c.ir.LoaderProgram.Sorted() {
-		for _, file := range pkg.CFiles {
-			extraFiles = append(extraFiles, filepath.Join(pkg.Package.Dir, file))
+		for _, file := range pkg.OtherFiles {
+			switch strings.ToLower(filepath.Ext(file)) {
+			case ".c":
+				extraFiles = append(extraFiles, file)
+			}
 		}
 	}
 
diff --git a/ir/ir.go b/ir/ir.go
index 7506400dfa..24bdd0ff3a 100644
--- a/ir/ir.go
+++ b/ir/ir.go
@@ -63,73 +63,14 @@ const (
 )
 
 // Create and initialize a new *Program from a *ssa.Program.
-func NewProgram(lprogram *loader.Program, mainPath string) *Program {
+func NewProgram(lprogram *loader.Program) *Program {
 	program := lprogram.LoadSSA()
 	program.Build()
 
-	// Find the main package, which is a bit difficult when running a .go file
-	// directly.
-	mainPkg := program.ImportedPackage(mainPath)
-	if mainPkg == nil {
-		for _, pkgInfo := range program.AllPackages() {
-			if pkgInfo.Pkg.Name() == "main" {
-				if mainPkg != nil {
-					panic("more than one main package found")
-				}
-				mainPkg = pkgInfo
-			}
-		}
-	}
+	mainPkg := program.ImportedPackage(lprogram.MainPkg.PkgPath)
 	if mainPkg == nil {
 		panic("could not find main package")
 	}
-
-	// Make a list of packages in import order.
-	packageList := []*ssa.Package{}
-	packageSet := map[string]struct{}{}
-	worklist := []string{"runtime", mainPath}
-	for len(worklist) != 0 {
-		pkgPath := worklist[0]
-		var pkg *ssa.Package
-		if pkgPath == mainPath {
-			pkg = mainPkg // necessary for compiling individual .go files
-		} else {
-			pkg = program.ImportedPackage(pkgPath)
-		}
-		if pkg == nil {
-			// Non-SSA package (e.g. cgo).
-			packageSet[pkgPath] = struct{}{}
-			worklist = worklist[1:]
-			continue
-		}
-		if _, ok := packageSet[pkgPath]; ok {
-			// Package already in the final package list.
-			worklist = worklist[1:]
-			continue
-		}
-
-		unsatisfiedImports := make([]string, 0)
-		imports := pkg.Pkg.Imports()
-		for _, pkg := range imports {
-			if _, ok := packageSet[pkg.Path()]; ok {
-				continue
-			}
-			unsatisfiedImports = append(unsatisfiedImports, pkg.Path())
-		}
-		if len(unsatisfiedImports) == 0 {
-			// All dependencies of this package are satisfied, so add this
-			// package to the list.
-			packageList = append(packageList, pkg)
-			packageSet[pkgPath] = struct{}{}
-			worklist = worklist[1:]
-		} else {
-			// Prepend all dependencies to the worklist and reconsider this
-			// package (by not removing it from the worklist). At that point, it
-			// must be possible to add it to packageList.
-			worklist = append(unsatisfiedImports, worklist...)
-		}
-	}
-
 	p := &Program{
 		Program:       program,
 		LoaderProgram: lprogram,
@@ -137,8 +78,8 @@ func NewProgram(lprogram *loader.Program, mainPath string) *Program {
 		functionMap:   make(map[*ssa.Function]*Function),
 	}
 
-	for _, pkg := range packageList {
-		p.AddPackage(pkg)
+	for _, pkg := range lprogram.Sorted() {
+		p.AddPackage(program.ImportedPackage(pkg.PkgPath))
 	}
 
 	return p
diff --git a/loader/errors.go b/loader/errors.go
index c25652ee19..e0414204b8 100644
--- a/loader/errors.go
+++ b/loader/errors.go
@@ -1,10 +1,5 @@
 package loader
 
-import (
-	"go/token"
-	"strings"
-)
-
 // Errors contains a list of parser errors or a list of typechecker errors for
 // the given package.
 type Errors struct {
@@ -15,25 +10,3 @@ type Errors struct {
 func (e Errors) Error() string {
 	return "could not compile: " + e.Errs[0].Error()
 }
-
-// ImportCycleErrors is returned when encountering an import cycle. The list of
-// packages is a list from the root package to the leaf package that imports one
-// of the packages in the list.
-type ImportCycleError struct {
-	Packages        []string
-	ImportPositions []token.Position
-}
-
-func (e *ImportCycleError) Error() string {
-	var msg strings.Builder
-	msg.WriteString("import cycle:\n\t")
-	msg.WriteString(strings.Join(e.Packages, "\n\t"))
-	msg.WriteString("\n at ")
-	for i, pos := range e.ImportPositions {
-		if i > 0 {
-			msg.WriteString(", ")
-		}
-		msg.WriteString(pos.String())
-	}
-	return msg.String()
-}
diff --git a/loader/loader.go b/loader/loader.go
index e5de53c1fa..baeecc0416 100644
--- a/loader/loader.go
+++ b/loader/loader.go
@@ -3,6 +3,7 @@ package loader
 import (
 	"bytes"
 	"errors"
+	"fmt"
 	"go/ast"
 	"go/build"
 	"go/parser"
@@ -12,18 +13,21 @@ import (
 	"os"
 	"path/filepath"
 	"sort"
+	"strconv"
 	"strings"
 	"text/template"
 
 	"github.com/tinygo-org/tinygo/cgo"
 	"github.com/tinygo-org/tinygo/goenv"
+	"golang.org/x/tools/go/packages"
 )
 
 // Program holds all packages and some metadata about the program as a whole.
 type Program struct {
-	mainPkg      string
 	Build        *build.Context
+	Tests        bool
 	Packages     map[string]*Package
+	MainPkg      *Package
 	sorted       []*Package
 	fset         *token.FileSet
 	TypeChecker  types.Config
@@ -37,85 +41,163 @@ type Program struct {
 // Package holds a loaded package, its imports, and its parsed files.
 type Package struct {
 	*Program
-	*build.Package
-	Imports   map[string]*Package
-	Importing bool
-	Files     []*ast.File
-	Pkg       *types.Package
+	*packages.Package
+	Files []*ast.File
+	Pkg   *types.Package
 	types.Info
 }
 
-// Import loads the given package relative to srcDir (for the vendor directory).
-// It only loads the current package without recursion.
-func (p *Program) Import(path, srcDir string, pos token.Position) (*Package, error) {
+// Load loads the given package with all dependencies (including the runtime
+// package). Call .Parse() afterwards to parse all Go files (including CGo
+// processing, if necessary).
+func (p *Program) Load(importPath string) error {
 	if p.Packages == nil {
 		p.Packages = make(map[string]*Package)
 	}
 
-	// Load this package.
-	ctx := p.Build
-	buildPkg, err := ctx.Import(path, srcDir, build.ImportComment)
+	err := p.loadPackage(importPath)
 	if err != nil {
-		return nil, scanner.Error{
-			Pos: pos,
-			Msg: err.Error(), // TODO: define a new error type that will wrap the inner error
-		}
-	}
-	if existingPkg, ok := p.Packages[buildPkg.ImportPath]; ok {
-		// Already imported, or at least started the import.
-		return existingPkg, nil
+		return err
 	}
-	p.sorted = nil // invalidate the sorted order of packages
-	pkg := p.newPackage(buildPkg)
-	p.Packages[buildPkg.ImportPath] = pkg
-
-	if p.mainPkg == "" {
-		p.mainPkg = buildPkg.ImportPath
+	p.MainPkg = p.sorted[len(p.sorted)-1]
+	if _, ok := p.Packages["runtime"]; !ok {
+		// The runtime package wasn't loaded. Although `go list -deps` seems to
+		// return the full dependency list, there is no way to get those
+		// packages from the go/packages package. Therefore load the runtime
+		// manually and add it to the list of to-be-compiled packages
+		// (duplicates are already filtered).
+		return p.loadPackage("runtime")
 	}
-
-	return pkg, nil
+	return nil
 }
 
-// ImportFile loads and parses the import statements in the given path and
-// creates a pseudo-package out of it.
-func (p *Program) ImportFile(path string) (*Package, error) {
-	if p.Packages == nil {
-		p.Packages = make(map[string]*Package)
+func (p *Program) loadPackage(importPath string) error {
+	cgoEnabled := "0"
+	if p.Build.CgoEnabled {
+		cgoEnabled = "1"
+	}
+	pkgs, err := packages.Load(&packages.Config{
+		Mode:       packages.NeedName | packages.NeedFiles | packages.NeedImports | packages.NeedDeps,
+		Env:        append(os.Environ(), "GOROOT="+p.Build.GOROOT, "GOOS="+p.Build.GOOS, "GOARCH="+p.Build.GOARCH, "CGO_ENABLED="+cgoEnabled),
+		BuildFlags: []string{"-tags", strings.Join(p.Build.BuildTags, " ")},
+		Tests:      p.Tests,
+	}, importPath)
+	if err != nil {
+		return err
 	}
-	if _, ok := p.Packages[path]; ok {
-		// unlikely
-		return nil, errors.New("loader: cannot import file that is already imported as package: " + path)
+	var pkg *packages.Package
+	if p.Tests {
+		// We need the second package. Quoting from the docs:
+		// > For example, when using the go command, loading "fmt" with Tests=true
+		// > returns four packages, with IDs "fmt" (the standard package),
+		// > "fmt [fmt.test]" (the package as compiled for the test),
+		// > "fmt_test" (the test functions from source files in package fmt_test),
+		// > and "fmt.test" (the test binary).
+		pkg = pkgs[1]
+	} else {
+		if len(pkgs) != 1 {
+			return fmt.Errorf("expected exactly one package while importing %s, got %d", importPath, len(pkgs))
+		}
+		pkg = pkgs[0]
 	}
+	var importError *Errors
+	var addPackages func(pkg *packages.Package)
+	addPackages = func(pkg *packages.Package) {
+		if _, ok := p.Packages[pkg.PkgPath]; ok {
+			return
+		}
+		pkg2 := p.newPackage(pkg)
+		p.Packages[pkg.PkgPath] = pkg2
+		if len(pkg.Errors) != 0 {
+			if importError != nil {
+				// There was another error reported already. Do not report
+				// errors from multiple packages at once.
+				return
+			}
+			importError = &Errors{
+				Pkg: pkg2,
+			}
+			for _, err := range pkg.Errors {
+				pos := token.Position{}
+				fields := strings.Split(err.Pos, ":")
+				if len(fields) >= 2 {
+					// There is some file/line/column information.
+					if n, err := strconv.Atoi(fields[len(fields)-2]); err == nil {
+						// Format: filename.go:line:colum
+						pos.Filename = strings.Join(fields[:len(fields)-2], ":")
+						pos.Line = n
+						pos.Column, _ = strconv.Atoi(fields[len(fields)-1])
+					} else {
+						// Format: filename.go:line
+						pos.Filename = strings.Join(fields[:len(fields)-1], ":")
+						pos.Line, _ = strconv.Atoi(fields[len(fields)-1])
+					}
+					pos.Filename = p.getOriginalPath(pos.Filename)
+				}
+				importError.Errs = append(importError.Errs, scanner.Error{
+					Pos: pos,
+					Msg: err.Msg,
+				})
+			}
+			return
+		}
 
-	file, err := p.parseFile(path, parser.ImportsOnly)
-	if err != nil {
-		return nil, err
-	}
-	buildPkg := &build.Package{
-		Dir:        filepath.Dir(path),
-		ImportPath: path,
-		GoFiles:    []string{filepath.Base(path)},
+		// Get the list of imports (sorted alphabetically).
+		names := make([]string, 0, len(pkg.Imports))
+		for name := range pkg.Imports {
+			names = append(names, name)
+		}
+		sort.Strings(names)
+
+		// Add all the imports.
+		for _, name := range names {
+			addPackages(pkg.Imports[name])
+		}
+
+		p.sorted = append(p.sorted, pkg2)
 	}
-	for _, importSpec := range file.Imports {
-		buildPkg.Imports = append(buildPkg.Imports, importSpec.Path.Value[1:len(importSpec.Path.Value)-1])
+	addPackages(pkg)
+	if importError != nil {
+		return *importError
 	}
-	p.sorted = nil // invalidate the sorted order of packages
-	pkg := p.newPackage(buildPkg)
-	p.Packages[buildPkg.ImportPath] = pkg
+	return nil
+}
 
-	if p.mainPkg == "" {
-		p.mainPkg = buildPkg.ImportPath
+// getOriginalPath looks whether this path is in the generated GOROOT and if so,
+// replaces the path with the original path (in GOROOT or TINYGOROOT). Otherwise
+// the input path is returned.
+func (p *Program) getOriginalPath(path string) string {
+	originalPath := path
+	if strings.HasPrefix(path, p.Build.GOROOT+string(filepath.Separator)) {
+		// If this file is part of the synthetic GOROOT, try to infer the
+		// original path.
+		relpath := path[len(filepath.Join(p.Build.GOROOT, "src"))+1:]
+		realgorootPath := filepath.Join(goenv.Get("GOROOT"), "src", relpath)
+		if _, err := os.Stat(realgorootPath); err == nil {
+			originalPath = realgorootPath
+		}
+		maybeInTinyGoRoot := false
+		for prefix := range pathsToOverride(needsSyscallPackage(p.Build.BuildTags)) {
+			if !strings.HasPrefix(relpath, prefix) {
+				continue
+			}
+			maybeInTinyGoRoot = true
+		}
+		if maybeInTinyGoRoot {
+			tinygoPath := filepath.Join(p.TINYGOROOT, "src", relpath)
+			if _, err := os.Stat(tinygoPath); err == nil {
+				originalPath = tinygoPath
+			}
+		}
 	}
-
-	return pkg, nil
+	return originalPath
 }
 
 // newPackage instantiates a new *Package object with initialized members.
-func (p *Program) newPackage(pkg *build.Package) *Package {
+func (p *Program) newPackage(pkg *packages.Package) *Package {
 	return &Package{
 		Program: p,
 		Package: pkg,
-		Imports: make(map[string]*Package, len(pkg.Imports)),
 		Info: types.Info{
 			Types:      make(map[ast.Expr]types.TypeAndValue),
 			Defs:       make(map[*ast.Ident]types.Object),
@@ -130,87 +212,25 @@ func (p *Program) newPackage(pkg *build.Package) *Package {
 // Sorted returns a list of all packages, sorted in a way that no packages come
 // before the packages they depend upon.
 func (p *Program) Sorted() []*Package {
-	if p.sorted == nil {
-		p.sort()
-	}
 	return p.sorted
 }
 
-func (p *Program) sort() {
-	p.sorted = nil
-	packageList := make([]*Package, 0, len(p.Packages))
-	packageSet := make(map[string]struct{}, len(p.Packages))
-	worklist := make([]string, 0, len(p.Packages))
-	for path := range p.Packages {
-		worklist = append(worklist, path)
-	}
-	sort.Strings(worklist)
-	for len(worklist) != 0 {
-		pkgPath := worklist[0]
-		pkg := p.Packages[pkgPath]
-
-		if _, ok := packageSet[pkgPath]; ok {
-			// Package already in the final package list.
-			worklist = worklist[1:]
-			continue
-		}
-
-		unsatisfiedImports := make([]string, 0)
-		for _, pkg := range pkg.Imports {
-			if _, ok := packageSet[pkg.ImportPath]; ok {
-				continue
-			}
-			unsatisfiedImports = append(unsatisfiedImports, pkg.ImportPath)
-		}
-		sort.Strings(unsatisfiedImports)
-		if len(unsatisfiedImports) == 0 {
-			// All dependencies of this package are satisfied, so add this
-			// package to the list.
-			packageList = append(packageList, pkg)
-			packageSet[pkgPath] = struct{}{}
-			worklist = worklist[1:]
-		} else {
-			// Prepend all dependencies to the worklist and reconsider this
-			// package (by not removing it from the worklist). At that point, it
-			// must be possible to add it to packageList.
-			worklist = append(unsatisfiedImports, worklist...)
-		}
-	}
-
-	p.sorted = packageList
-}
-
-// Parse recursively imports all packages, parses them, and typechecks them.
+// Parse parses all packages and typechecks them.
 //
 // The returned error may be an Errors error, which contains a list of errors.
 //
 // Idempotent.
-func (p *Program) Parse(compileTestBinary bool) error {
-	includeTests := compileTestBinary
-
-	// Load all imports
-	for _, pkg := range p.Sorted() {
-		err := pkg.importRecursively(includeTests)
-		if err != nil {
-			if err, ok := err.(*ImportCycleError); ok {
-				if pkg.ImportPath != err.Packages[0] {
-					err.Packages = append([]string{pkg.ImportPath}, err.Packages...)
-				}
-			}
-			return err
-		}
-	}
-
+func (p *Program) Parse() error {
 	// Parse all packages.
 	for _, pkg := range p.Sorted() {
-		err := pkg.Parse(includeTests)
+		err := pkg.Parse()
 		if err != nil {
 			return err
 		}
 	}
 
-	if compileTestBinary {
-		err := p.SwapTestMain()
+	if p.Tests {
+		err := p.swapTestMain()
 		if err != nil {
 			return err
 		}
@@ -227,7 +247,7 @@ func (p *Program) Parse(compileTestBinary bool) error {
 	return nil
 }
 
-func (p *Program) SwapTestMain() error {
+func (p *Program) swapTestMain() error {
 	var tests []string
 
 	isTestFunc := func(f *ast.FuncDecl) bool {
@@ -237,8 +257,7 @@ func (p *Program) SwapTestMain() error {
 		}
 		return false
 	}
-	mainPkg := p.Packages[p.mainPkg]
-	for _, f := range mainPkg.Files {
+	for _, f := range p.MainPkg.Files {
 		for i, d := range f.Decls {
 			switch v := d.(type) {
 			case *ast.FuncDecl:
@@ -289,7 +308,7 @@ func main () {
 	if err != nil {
 		return err
 	}
-	path := filepath.Join(p.mainPkg, "$testmain.go")
+	path := filepath.Join(p.MainPkg.Dir, "$testmain.go")
 
 	if p.fset == nil {
 		p.fset = token.NewFileSet()
@@ -299,7 +318,7 @@ func main () {
 	if err != nil {
 		return err
 	}
-	mainPkg.Files = append(mainPkg.Files, newMain)
+	p.MainPkg.Files = append(p.MainPkg.Files, newMain)
 
 	return nil
 }
@@ -315,50 +334,27 @@ func (p *Program) parseFile(path string, mode parser.Mode) (*ast.File, error) {
 		return nil, err
 	}
 	defer rd.Close()
-	diagnosticPath := path
-	if strings.HasPrefix(path, p.Build.GOROOT+string(filepath.Separator)) {
-		// If this file is part of the synthetic GOROOT, try to infer the
-		// original path.
-		relpath := path[len(filepath.Join(p.Build.GOROOT, "src"))+1:]
-		realgorootPath := filepath.Join(goenv.Get("GOROOT"), "src", relpath)
-		if _, err := os.Stat(realgorootPath); err == nil {
-			diagnosticPath = realgorootPath
-		}
-		maybeInTinyGoRoot := false
-		for prefix := range pathsToOverride(needsSyscallPackage(p.Build.BuildTags)) {
-			if !strings.HasPrefix(relpath, prefix) {
-				continue
-			}
-			maybeInTinyGoRoot = true
-		}
-		if maybeInTinyGoRoot {
-			tinygoPath := filepath.Join(p.TINYGOROOT, "src", relpath)
-			if _, err := os.Stat(tinygoPath); err == nil {
-				diagnosticPath = tinygoPath
-			}
-		}
-	}
-	return parser.ParseFile(p.fset, diagnosticPath, rd, mode)
+	return parser.ParseFile(p.fset, p.getOriginalPath(path), rd, mode)
 }
 
 // Parse parses and typechecks this package.
 //
 // Idempotent.
-func (p *Package) Parse(includeTests bool) error {
+func (p *Package) Parse() error {
 	if len(p.Files) != 0 {
 		return nil
 	}
 
 	// Load the AST.
 	// TODO: do this in parallel.
-	if p.ImportPath == "unsafe" {
+	if p.PkgPath == "unsafe" {
 		// Special case for the unsafe package. Don't even bother loading
 		// the files.
 		p.Pkg = types.Unsafe
 		return nil
 	}
 
-	files, err := p.parseFiles(includeTests)
+	files, err := p.parseFiles()
 	if err != nil {
 		return err
 	}
@@ -385,7 +381,7 @@ func (p *Package) Check() error {
 	// Do typechecking of the package.
 	checker.Importer = p
 
-	typesPkg, err := checker.Check(p.ImportPath, p.fset, p.Files, &p.Info)
+	typesPkg, err := checker.Check(p.PkgPath, p.fset, p.Files, &p.Info)
 	if err != nil {
 		if err, ok := err.(Errors); ok {
 			return err
@@ -397,22 +393,14 @@ func (p *Package) Check() error {
 }
 
 // parseFiles parses the loaded list of files and returns this list.
-func (p *Package) parseFiles(includeTests bool) ([]*ast.File, error) {
+func (p *Package) parseFiles() ([]*ast.File, error) {
 	// TODO: do this concurrently.
 	var files []*ast.File
 	var fileErrs []error
 
-	var gofiles []string
-	if includeTests {
-		gofiles = make([]string, 0, len(p.GoFiles)+len(p.TestGoFiles))
-		gofiles = append(gofiles, p.GoFiles...)
-		gofiles = append(gofiles, p.TestGoFiles...)
-	} else {
-		gofiles = p.GoFiles
-	}
-
-	for _, file := range gofiles {
-		f, err := p.parseFile(filepath.Join(p.Package.Dir, file), parser.ParseComments)
+	var cgoFiles []*ast.File
+	for _, file := range p.GoFiles {
+		f, err := p.parseFile(file, parser.ParseComments)
 		if err != nil {
 			fileErrs = append(fileErrs, err)
 			continue
@@ -421,19 +409,15 @@ func (p *Package) parseFiles(includeTests bool) ([]*ast.File, error) {
 			fileErrs = append(fileErrs, err)
 			continue
 		}
-		files = append(files, f)
-	}
-	for _, file := range p.CgoFiles {
-		path := filepath.Join(p.Package.Dir, file)
-		f, err := p.parseFile(path, parser.ParseComments)
-		if err != nil {
-			fileErrs = append(fileErrs, err)
-			continue
+		for _, importSpec := range f.Imports {
+			if importSpec.Path.Value == `"C"` {
+				cgoFiles = append(cgoFiles, f)
+			}
 		}
 		files = append(files, f)
 	}
-	if len(p.CgoFiles) != 0 {
-		cflags := append(p.CFlags, "-I"+p.Package.Dir)
+	if len(cgoFiles) != 0 {
+		cflags := append(p.CFlags, "-I"+filepath.Dir(p.GoFiles[0]))
 		if p.ClangHeaders != "" {
 			cflags = append(cflags, "-Xclang", "-internal-isystem", "-Xclang", p.ClangHeaders)
 		}
@@ -458,58 +442,8 @@ func (p *Package) Import(to string) (*types.Package, error) {
 		return types.Unsafe, nil
 	}
 	if _, ok := p.Imports[to]; ok {
-		return p.Imports[to].Pkg, nil
+		return p.Packages[p.Imports[to].PkgPath].Pkg, nil
 	} else {
 		return nil, errors.New("package not imported: " + to)
 	}
 }
-
-// importRecursively calls Program.Import() on all imported packages, and calls
-// importRecursively() on the imported packages as well.
-//
-// Idempotent.
-func (p *Package) importRecursively(includeTests bool) error {
-	p.Importing = true
-
-	imports := p.Package.Imports
-	if includeTests {
-		imports = append(imports, p.Package.TestImports...)
-	}
-
-	for _, to := range imports {
-		if to == "C" {
-			// Do CGo processing in a later stage.
-			continue
-		}
-		if _, ok := p.Imports[to]; ok {
-			continue
-		}
-		// Find error location.
-		var pos token.Position
-		if len(p.Package.ImportPos[to]) > 0 {
-			pos = p.Package.ImportPos[to][0]
-		} else {
-			pos = token.Position{Filename: p.Package.ImportPath}
-		}
-		importedPkg, err := p.Program.Import(to, p.Package.Dir, pos)
-		if err != nil {
-			if err, ok := err.(*ImportCycleError); ok {
-				err.Packages = append([]string{p.ImportPath}, err.Packages...)
-			}
-			return err
-		}
-		if importedPkg.Importing {
-			return &ImportCycleError{[]string{p.ImportPath, importedPkg.ImportPath}, p.ImportPos[to]}
-		}
-		err = importedPkg.importRecursively(false)
-		if err != nil {
-			if err, ok := err.(*ImportCycleError); ok {
-				err.Packages = append([]string{p.ImportPath}, err.Packages...)
-			}
-			return err
-		}
-		p.Imports[to] = importedPkg
-	}
-	p.Importing = false
-	return nil
-}
diff --git a/main.go b/main.go
index 0d711ab352..feece40000 100644
--- a/main.go
+++ b/main.go
@@ -733,7 +733,7 @@ func printCompilerError(logln func(...interface{}), err error) {
 			}
 		}
 	case loader.Errors:
-		logln("#", err.Pkg.ImportPath)
+		logln("#", err.Pkg.PkgPath)
 		for _, err := range err.Errs {
 			printCompilerError(logln, err)
 		}

From c248418dbec8c31d81fd8f8fedcdbd1fb93a85f1 Mon Sep 17 00:00:00 2001
From: Ayke van Laethem 
Date: Wed, 27 May 2020 00:17:28 +0200
Subject: [PATCH 49/82] compiler: fix a few crashes due to named types

There were a few cases left where a named type would cause a crash in
the compiler. While going through enough code would have found them
eventually, I specifically looked for the `Type().(` pattern: a Type()
call that is then used in a type assert. Most of those were indeed bugs,
although for some I couldn't come up with a reproducer so I left them
as-is.
---
 compiler/channel.go    |  6 +++---
 compiler/compiler.go   |  8 ++++----
 testdata/channel.go    | 11 +++++++++++
 testdata/coroutines.go | 12 ++++++++++++
 testdata/slice.go      |  5 +++++
 5 files changed, 35 insertions(+), 7 deletions(-)

diff --git a/compiler/channel.go b/compiler/channel.go
index 3686c98f37..a2532e4898 100644
--- a/compiler/channel.go
+++ b/compiler/channel.go
@@ -12,7 +12,7 @@ import (
 )
 
 func (b *builder) createMakeChan(expr *ssa.MakeChan) llvm.Value {
-	elementSize := b.targetData.TypeAllocSize(b.getLLVMType(expr.Type().(*types.Chan).Elem()))
+	elementSize := b.targetData.TypeAllocSize(b.getLLVMType(expr.Type().Underlying().(*types.Chan).Elem()))
 	elementSizeValue := llvm.ConstInt(b.uintptrType, elementSize, false)
 	bufSize := b.getValue(expr.Size)
 	b.createChanBoundsCheck(elementSize, bufSize, expr.Size.Type().Underlying().(*types.Basic), expr.Pos())
@@ -47,7 +47,7 @@ func (b *builder) createChanSend(instr *ssa.Send) {
 // createChanRecv emits a pseudo chan receive operation. It is lowered to the
 // actual channel receive operation during goroutine lowering.
 func (b *builder) createChanRecv(unop *ssa.UnOp) llvm.Value {
-	valueType := b.getLLVMType(unop.X.Type().(*types.Chan).Elem())
+	valueType := b.getLLVMType(unop.X.Type().Underlying().(*types.Chan).Elem())
 	ch := b.getValue(unop.X)
 
 	// Allocate memory to receive into.
@@ -117,7 +117,7 @@ func (b *builder) createSelect(expr *ssa.Select) llvm.Value {
 		switch state.Dir {
 		case types.RecvOnly:
 			// Make sure the receive buffer is big enough and has the correct alignment.
-			llvmType := b.getLLVMType(state.Chan.Type().(*types.Chan).Elem())
+			llvmType := b.getLLVMType(state.Chan.Type().Underlying().(*types.Chan).Elem())
 			if size := b.targetData.TypeAllocSize(llvmType); size > recvbufSize {
 				recvbufSize = size
 			}
diff --git a/compiler/compiler.go b/compiler/compiler.go
index 37858ea305..53cafed51e 100644
--- a/compiler/compiler.go
+++ b/compiler/compiler.go
@@ -1053,7 +1053,7 @@ func (b *builder) createInstruction(instr ssa.Instruction) {
 			// goroutine:
 			//   * The function context, for closures.
 			//   * The function pointer (for tasks).
-			funcPtr, context := b.decodeFuncValue(b.getValue(instr.Call.Value), instr.Call.Value.Type().(*types.Signature))
+			funcPtr, context := b.decodeFuncValue(b.getValue(instr.Call.Value), instr.Call.Value.Type().Underlying().(*types.Signature))
 			params = append(params, context) // context parameter
 			switch b.Scheduler() {
 			case "none", "coroutines":
@@ -1516,7 +1516,7 @@ func (b *builder) createExpr(expr ssa.Value) (llvm.Value, error) {
 		index := b.getValue(expr.Index)
 
 		// Check bounds.
-		arrayLen := expr.X.Type().(*types.Array).Len()
+		arrayLen := expr.X.Type().Underlying().(*types.Array).Len()
 		arrayLenLLVM := llvm.ConstInt(b.uintptrType, uint64(arrayLen), false)
 		b.createLookupBoundsCheck(arrayLenLLVM, index, expr.Index.Type())
 
@@ -1628,8 +1628,8 @@ func (b *builder) createExpr(expr ssa.Value) (llvm.Value, error) {
 		}
 
 		// Bounds checking.
-		lenType := expr.Len.Type().(*types.Basic)
-		capType := expr.Cap.Type().(*types.Basic)
+		lenType := expr.Len.Type().Underlying().(*types.Basic)
+		capType := expr.Cap.Type().Underlying().(*types.Basic)
 		b.createSliceBoundsCheck(maxSize, sliceLen, sliceCap, sliceCap, lenType, capType, capType)
 
 		// Allocate the backing array.
diff --git a/testdata/channel.go b/testdata/channel.go
index e1acea7ead..6a7945e5d1 100644
--- a/testdata/channel.go
+++ b/testdata/channel.go
@@ -8,6 +8,8 @@ import (
 
 var wg sync.WaitGroup
 
+type intchan chan int
+
 func main() {
 	ch := make(chan int, 2)
 	ch <- 1
@@ -40,6 +42,15 @@ func main() {
 	_ = make(chan int, uint32(2))
 	_ = make(chan int, uint64(2))
 
+	// Test that named channels don't crash the compiler.
+	named := make(intchan, 1)
+	named <- 3
+	<-named
+	select {
+	case <-named:
+	default:
+	}
+
 	// Test bigger values
 	ch2 := make(chan complex128)
 	wg.Add(1)
diff --git a/testdata/coroutines.go b/testdata/coroutines.go
index 49fdfc2870..bb8acdbc60 100644
--- a/testdata/coroutines.go
+++ b/testdata/coroutines.go
@@ -68,6 +68,8 @@ func main() {
 	m.Unlock()
 	println("done")
 
+	startSimpleFunc(emptyFunc)
+
 	time.Sleep(2 * time.Millisecond)
 }
 
@@ -100,6 +102,11 @@ func sleepFuncValue(fn func(int)) {
 	go fn(8)
 }
 
+func startSimpleFunc(fn simpleFunc) {
+	// Test that named function types don't crash the compiler.
+	go fn()
+}
+
 func nowait() {
 	println("non-blocking goroutine")
 }
@@ -115,3 +122,8 @@ func (i *myPrinter) Print() {
 	time.Sleep(time.Millisecond)
 	println("async interface method call")
 }
+
+type simpleFunc func()
+
+func emptyFunc() {
+}
diff --git a/testdata/slice.go b/testdata/slice.go
index d20de76d3b..b5e5436714 100644
--- a/testdata/slice.go
+++ b/testdata/slice.go
@@ -31,6 +31,7 @@ func main() {
 	assert(len(make([]int, makeUint32(2), makeUint32(3))) == 2)
 	assert(len(make([]int, makeUint64(2), makeUint64(3))) == 2)
 	assert(len(make([]int, makeUintptr(2), makeUintptr(3))) == 2)
+	assert(len(make([]int, makeMyUint8(2), makeMyUint8(3))) == 2)
 
 	// indexing into a slice with uncommon index types
 	assert(foo[int(2)] == 4)
@@ -131,6 +132,9 @@ func main() {
 	var named MySlice
 	assert(len(unnamed[:]) == 32)
 	assert(len(named[:]) == 32)
+	for _, c := range named {
+		assert(c == 0)
+	}
 }
 
 func printslice(name string, s []int) {
@@ -169,3 +173,4 @@ func makeUint16(x uint16) uint16    { return x }
 func makeUint32(x uint32) uint32    { return x }
 func makeUint64(x uint64) uint64    { return x }
 func makeUintptr(x uintptr) uintptr { return x }
+func makeMyUint8(x myUint8) myUint8 { return x }

From 19c7965fc505142d1abb924a40ffdc783ba82687 Mon Sep 17 00:00:00 2001
From: Ayke van Laethem 
Date: Mon, 13 Jan 2020 16:21:16 +0100
Subject: [PATCH 50/82] nrf: add support for pin change interrupts

---
 Makefile                                  |  2 +
 src/examples/pininterrupt/pca10040.go     | 10 ++++
 src/examples/pininterrupt/pininterrupt.go | 52 +++++++++++++++++
 src/machine/machine.go                    |  9 +--
 src/machine/machine_nrf.go                | 71 +++++++++++++++++++++++
 5 files changed, 140 insertions(+), 4 deletions(-)
 create mode 100644 src/examples/pininterrupt/pca10040.go
 create mode 100644 src/examples/pininterrupt/pininterrupt.go

diff --git a/Makefile b/Makefile
index 02d6a099c9..073b46ff28 100644
--- a/Makefile
+++ b/Makefile
@@ -203,6 +203,8 @@ smoketest:
 	@$(MD5SUM) test.hex
 	$(TINYGO) build -size short -o test.hex -target=microbit            examples/microbit-blink
 	@$(MD5SUM) test.hex
+	$(TINYGO) build -size short -o test.hex -target=pca10040            examples/pininterrupt
+	@$(MD5SUM) test.hex
 	$(TINYGO) build -size short -o test.hex -target=pca10040            examples/serial
 	@$(MD5SUM) test.hex
 	$(TINYGO) build -size short -o test.hex -target=pca10040            examples/systick
diff --git a/src/examples/pininterrupt/pca10040.go b/src/examples/pininterrupt/pca10040.go
new file mode 100644
index 0000000000..828252746e
--- /dev/null
+++ b/src/examples/pininterrupt/pca10040.go
@@ -0,0 +1,10 @@
+// +build pca10040
+
+package main
+
+import "machine"
+
+const (
+	buttonMode      = machine.PinInputPullup
+	buttonPinChange = machine.PinRising
+)
diff --git a/src/examples/pininterrupt/pininterrupt.go b/src/examples/pininterrupt/pininterrupt.go
new file mode 100644
index 0000000000..0cb29bc854
--- /dev/null
+++ b/src/examples/pininterrupt/pininterrupt.go
@@ -0,0 +1,52 @@
+package main
+
+// This example demonstrates how to use pin change interrupts.
+//
+// This is only an example and should not be copied directly in any serious
+// circuit, because it lacks an important feature: debouncing.
+// See: https://en.wikipedia.org/wiki/Switch#Contact_bounce
+
+import (
+	"machine"
+	"runtime/volatile"
+	"time"
+)
+
+const (
+	button = machine.BUTTON
+	led    = machine.LED
+)
+
+func main() {
+	var lightLed volatile.Register8
+	lightLed.Set(0)
+
+	// Configure the LED, defaulting to on (usually setting the pin to low will
+	// turn the LED on).
+	led.Configure(machine.PinConfig{Mode: machine.PinOutput})
+	led.Low()
+
+	// Make sure the pin is configured as a pullup to avoid floating inputs.
+	// Pullup works for most buttons, as most buttons short to ground when
+	// pressed.
+	button.Configure(machine.PinConfig{Mode: buttonMode})
+
+	// Set an interrupt on this pin.
+	err := button.SetInterrupt(buttonPinChange, func(machine.Pin) {
+		if lightLed.Get() != 0 {
+			lightLed.Set(0)
+			led.Low()
+		} else {
+			lightLed.Set(1)
+			led.High()
+		}
+	})
+	if err != nil {
+		println("could not configure pin interrupt:", err.Error())
+	}
+
+	// Make sure the program won't exit.
+	for {
+		time.Sleep(time.Hour)
+	}
+}
diff --git a/src/machine/machine.go b/src/machine/machine.go
index a7863d8ff6..e4e1dcb70b 100644
--- a/src/machine/machine.go
+++ b/src/machine/machine.go
@@ -3,10 +3,11 @@ package machine
 import "errors"
 
 var (
-	ErrInvalidInputPin  = errors.New("machine: invalid input pin")
-	ErrInvalidOutputPin = errors.New("machine: invalid output pin")
-	ErrInvalidClockPin  = errors.New("machine: invalid clock pin")
-	ErrInvalidDataPin   = errors.New("machine: invalid data pin")
+	ErrInvalidInputPin    = errors.New("machine: invalid input pin")
+	ErrInvalidOutputPin   = errors.New("machine: invalid output pin")
+	ErrInvalidClockPin    = errors.New("machine: invalid clock pin")
+	ErrInvalidDataPin     = errors.New("machine: invalid data pin")
+	ErrNoPinChangeChannel = errors.New("machine: no channel available for pin interrupt")
 )
 
 type PinConfig struct {
diff --git a/src/machine/machine_nrf.go b/src/machine/machine_nrf.go
index 663bf02405..382dd05c20 100644
--- a/src/machine/machine_nrf.go
+++ b/src/machine/machine_nrf.go
@@ -21,6 +21,18 @@ const (
 	PinOutput        PinMode = (nrf.GPIO_PIN_CNF_DIR_Output << nrf.GPIO_PIN_CNF_DIR_Pos) | (nrf.GPIO_PIN_CNF_INPUT_Disconnect << nrf.GPIO_PIN_CNF_INPUT_Pos)
 )
 
+type PinChange uint8
+
+// Pin change interrupt constants for SetInterrupt.
+const (
+	PinRising  PinChange = nrf.GPIOTE_CONFIG_POLARITY_LoToHi
+	PinFalling PinChange = nrf.GPIOTE_CONFIG_POLARITY_HiToLo
+	PinToggle  PinChange = nrf.GPIOTE_CONFIG_POLARITY_Toggle
+)
+
+// Callbacks to be called for pins configured with SetInterrupt.
+var pinCallbacks [len(nrf.GPIOTE.CONFIG)]func(Pin)
+
 // Configure this pin with the given configuration.
 func (p Pin) Configure(config PinConfig) {
 	cfg := config.Mode | nrf.GPIO_PIN_CNF_DRIVE_S0S1 | nrf.GPIO_PIN_CNF_SENSE_Disabled
@@ -59,6 +71,65 @@ func (p Pin) Get() bool {
 	return (port.IN.Get()>>pin)&1 != 0
 }
 
+// SetInterrupt sets an interrupt to be executed when a particular pin changes
+// state.
+//
+// This call will replace a previously set callback on this pin. You can pass a
+// nil func to unset the pin change interrupt. If you do so, the change
+// parameter is ignored and can be set to any value (such as 0).
+func (p Pin) SetInterrupt(change PinChange, callback func(Pin)) error {
+	// Some variables to easily check whether a channel was already configured
+	// as an event channel for the given pin.
+	// This is not just an optimization, this is requred: the datasheet says
+	// that configuring more than one channel for a given pin results in
+	// unpredictable behavior.
+	expectedConfigMask := uint32(nrf.GPIOTE_CONFIG_MODE_Msk | nrf.GPIOTE_CONFIG_PSEL_Msk)
+	expectedConfig := nrf.GPIOTE_CONFIG_MODE_Event<> nrf.GPIOTE_CONFIG_PSEL_Pos)
+				pinCallbacks[i](pin)
+			}
+		}
+	}).Enable()
+
+	// Everything was configured correctly.
+	return nil
+}
+
 // UART on the NRF.
 type UART struct {
 	Buffer *RingBuffer

From c72f9eb08ca4a51d33f068820109f9bb9da81338 Mon Sep 17 00:00:00 2001
From: Ayke van Laethem 
Date: Wed, 15 Jan 2020 12:58:40 +0100
Subject: [PATCH 51/82] sam: add support for pin change interrupts

---
 .../pininterrupt/circuitplay-express.go       |  10 ++
 src/machine/machine_atsamd21.go               | 128 ++++++++++++++++++
 2 files changed, 138 insertions(+)
 create mode 100644 src/examples/pininterrupt/circuitplay-express.go

diff --git a/src/examples/pininterrupt/circuitplay-express.go b/src/examples/pininterrupt/circuitplay-express.go
new file mode 100644
index 0000000000..e37105c2c7
--- /dev/null
+++ b/src/examples/pininterrupt/circuitplay-express.go
@@ -0,0 +1,10 @@
+// +build circuitplay_express
+
+package main
+
+import "machine"
+
+const (
+	buttonMode      = machine.PinInputPulldown
+	buttonPinChange = machine.PinFalling
+)
diff --git a/src/machine/machine_atsamd21.go b/src/machine/machine_atsamd21.go
index e96a7f4074..0f2305a205 100644
--- a/src/machine/machine_atsamd21.go
+++ b/src/machine/machine_atsamd21.go
@@ -33,6 +33,26 @@ const (
 	PinInputPulldown PinMode = 12
 )
 
+type PinChange uint8
+
+// Pin change interrupt constants for SetInterrupt.
+const (
+	PinRising  PinChange = sam.EIC_CONFIG_SENSE0_RISE
+	PinFalling PinChange = sam.EIC_CONFIG_SENSE0_FALL
+	PinToggle  PinChange = sam.EIC_CONFIG_SENSE0_BOTH
+)
+
+// Callbacks to be called for pins configured with SetInterrupt. Unfortunately,
+// we also need to keep track of which interrupt channel is used by which pin,
+// as the only alternative would be iterating through all pins.
+//
+// We're using the magic constant 16 here because the SAM D21 has 16 interrupt
+// channels configurable for pins.
+var (
+	interruptPins [16]Pin // warning: the value is invalid when pinCallbacks[i] is not set!
+	pinCallbacks  [16]func(Pin)
+)
+
 const (
 	pinPadMapSERCOM0Pad0 byte = (0x10 << 1) | 0x00
 	pinPadMapSERCOM1Pad0 byte = (0x20 << 1) | 0x00
@@ -144,6 +164,114 @@ func findPinPadMapping(sercom uint8, pin Pin) (pinMode PinMode, pad uint32, ok b
 	return
 }
 
+// SetInterrupt sets an interrupt to be executed when a particular pin changes
+// state.
+//
+// This call will replace a previously set callback on this pin. You can pass a
+// nil func to unset the pin change interrupt. If you do so, the change
+// parameter is ignored and can be set to any value (such as 0).
+func (p Pin) SetInterrupt(change PinChange, callback func(Pin)) error {
+	// Most pins follow a common pattern where the EXTINT value is the pin
+	// number modulo 16. However, there are a few exceptions, as you can see
+	// below.
+	extint := uint8(0)
+	switch p {
+	case PA08:
+		// Connected to NMI. This is not currently supported.
+		return ErrInvalidInputPin
+	case PA24:
+		extint = 12
+	case PA25:
+		extint = 13
+	case PA27:
+		extint = 15
+	case PA28:
+		extint = 8
+	case PA30:
+		extint = 10
+	case PA31:
+		extint = 11
+	default:
+		// All other pins follow a normal pattern.
+		extint = uint8(p) % 16
+	}
+
+	if callback == nil {
+		// Disable this pin interrupt (if it was enabled).
+		sam.EIC.INTENCLR.Set(1 << extint)
+		if pinCallbacks[extint] != nil {
+			pinCallbacks[extint] = nil
+		}
+		return nil
+	}
+
+	if pinCallbacks[extint] != nil {
+		// The pin was already configured.
+		// To properly re-configure a pin, unset it first and set a new
+		// configuration.
+		return ErrNoPinChangeChannel
+	}
+	pinCallbacks[extint] = callback
+	interruptPins[extint] = p
+
+	if sam.EIC.CTRL.Get() == 0 {
+		// EIC peripheral has not yet been initialized. Initialize it now.
+
+		// The EIC needs two clocks: CLK_EIC_APB and GCLK_EIC. CLK_EIC_APB is
+		// enabled by default, so doesn't have to be re-enabled. The other is
+		// required for detecting edges and must be enabled manually.
+		sam.GCLK.CLKCTRL.Set(sam.GCLK_CLKCTRL_ID_EIC<= 8 {
+		addr = &sam.EIC.CONFIG1
+	}
+	pos := (extint % 8) * 4 // bit position in register
+	addr.Set((addr.Get() &^ (0xf << pos)) | uint32(change)< 0 {
+		// odd pin, so save the even pins
+		val := p.getPMux() & sam.PORT_PMUX0_PMUXE_Msk
+		p.setPMux(val | (sam.PORT_PMUX0_PMUXO_A << sam.PORT_PMUX0_PMUXO_Pos))
+	} else {
+		// even pin, so save the odd pins
+		val := p.getPMux() & sam.PORT_PMUX0_PMUXO_Msk
+		p.setPMux(val | (sam.PORT_PMUX0_PMUXE_A << sam.PORT_PMUX0_PMUXE_Pos))
+	}
+
+	interrupt.New(sam.IRQ_EIC, func(interrupt.Interrupt) {
+		flags := sam.EIC.INTFLAG.Get()
+		sam.EIC.INTFLAG.Set(flags)      // clear interrupt
+		for i := uint(0); i < 16; i++ { // there are 16 channels
+			if flags&(1<
Date: Thu, 28 May 2020 01:15:13 +0200
Subject: [PATCH 52/82] transform: do not special-case zero or one
 implementations of a method call

This is a common case, but it also complicates the code. Removing this
special case does have a negative effect on code size in rare cases, but
I don't think it's worth keeping around (and possibly causing bugs) for
such uncommon cases.

This should not result in functional changes, although the output (as
stated above) sometimes changes a little bit.
---
 transform/interface-lowering.go     | 80 +++++++++++------------------
 transform/testdata/interface.out.ll | 18 ++++++-
 2 files changed, 47 insertions(+), 51 deletions(-)

diff --git a/transform/interface-lowering.go b/transform/interface-lowering.go
index 53e248d3a6..d64f2731d2 100644
--- a/transform/interface-lowering.go
+++ b/transform/interface-lowering.go
@@ -292,58 +292,40 @@ func (p *lowerInterfacesPass) run() error {
 
 		methodSet := use.Operand(1).Operand(0) // global variable
 		itf := p.interfaces[methodSet.Name()]
-		if len(itf.types) == 0 {
-			// This method call is impossible: no type implements this
-			// interface. In fact, the previous type assert that got this
-			// interface value should already have returned false.
-			// Replace the function pointer with undef (which will then be
-			// called), indicating to the optimizer this code is unreachable.
-			use.ReplaceAllUsesWith(llvm.Undef(p.uintptrType))
-			use.EraseFromParentAsInstruction()
-		} else if len(itf.types) == 1 {
-			// There is only one implementation of the given type.
-			// Call that function directly.
-			err := p.replaceInvokeWithCall(use, itf.types[0], signature)
-			if err != nil {
-				return err
-			}
-		} else {
-			// There are multiple types implementing this interface, thus there
-			// are multiple possible functions to call. Delegate calling the
-			// right function to a special wrapper function.
-			inttoptrs := getUses(use)
-			if len(inttoptrs) != 1 || inttoptrs[0].IsAIntToPtrInst().IsNil() {
-				return errorAt(use, "internal error: expected exactly one inttoptr use of runtime.interfaceMethod")
+
+		// Delegate calling the right function to a special wrapper function.
+		inttoptrs := getUses(use)
+		if len(inttoptrs) != 1 || inttoptrs[0].IsAIntToPtrInst().IsNil() {
+			return errorAt(use, "internal error: expected exactly one inttoptr use of runtime.interfaceMethod")
+		}
+		inttoptr := inttoptrs[0]
+		calls := getUses(inttoptr)
+		for _, call := range calls {
+			// Set up parameters for the call. First copy the regular params...
+			params := make([]llvm.Value, call.OperandsCount())
+			paramTypes := make([]llvm.Type, len(params))
+			for i := 0; i < len(params)-1; i++ {
+				params[i] = call.Operand(i)
+				paramTypes[i] = params[i].Type()
 			}
-			inttoptr := inttoptrs[0]
-			calls := getUses(inttoptr)
-			for _, call := range calls {
-				// Set up parameters for the call. First copy the regular params...
-				params := make([]llvm.Value, call.OperandsCount())
-				paramTypes := make([]llvm.Type, len(params))
-				for i := 0; i < len(params)-1; i++ {
-					params[i] = call.Operand(i)
-					paramTypes[i] = params[i].Type()
-				}
-				// then add the typecode to the end of the list.
-				params[len(params)-1] = typecode
-				paramTypes[len(params)-1] = p.uintptrType
-
-				// Create a function that redirects the call to the destination
-				// call, after selecting the right concrete type.
-				redirector := p.getInterfaceMethodFunc(itf, signature, call.Type(), paramTypes)
-
-				// Replace the old lookup/inttoptr/call with the new call.
-				p.builder.SetInsertPointBefore(call)
-				retval := p.builder.CreateCall(redirector, append(params, llvm.ConstNull(llvm.PointerType(p.ctx.Int8Type(), 0))), "")
-				if retval.Type().TypeKind() != llvm.VoidTypeKind {
-					call.ReplaceAllUsesWith(retval)
-				}
-				call.EraseFromParentAsInstruction()
+			// then add the typecode to the end of the list.
+			params[len(params)-1] = typecode
+			paramTypes[len(params)-1] = p.uintptrType
+
+			// Create a function that redirects the call to the destination
+			// call, after selecting the right concrete type.
+			redirector := p.getInterfaceMethodFunc(itf, signature, call.Type(), paramTypes)
+
+			// Replace the old lookup/inttoptr/call with the new call.
+			p.builder.SetInsertPointBefore(call)
+			retval := p.builder.CreateCall(redirector, append(params, llvm.ConstNull(llvm.PointerType(p.ctx.Int8Type(), 0))), "")
+			if retval.Type().TypeKind() != llvm.VoidTypeKind {
+				call.ReplaceAllUsesWith(retval)
 			}
-			inttoptr.EraseFromParentAsInstruction()
-			use.EraseFromParentAsInstruction()
+			call.EraseFromParentAsInstruction()
 		}
+		inttoptr.EraseFromParentAsInstruction()
+		use.EraseFromParentAsInstruction()
 	}
 
 	// Replace all typeasserts on interface types with matches on their concrete
diff --git a/transform/testdata/interface.out.ll b/transform/testdata/interface.out.ll
index 9ae8b48867..80f71ee4e9 100644
--- a/transform/testdata/interface.out.ll
+++ b/transform/testdata/interface.out.ll
@@ -47,8 +47,8 @@ typeswitch.notUnmatched:                          ; preds = %0
   br i1 %typeassert.ok, label %typeswitch.Doubler, label %typeswitch.notDoubler
 
 typeswitch.Doubler:                               ; preds = %typeswitch.notUnmatched
-  %doubler.result = call i32 @"(Number).Double$invoke"(i8* %value, i8* null)
-  call void @runtime.printint32(i32 %doubler.result)
+  %1 = call i32 @"(Doubler).Double"(i8* %value, i8* null, i32 %typecode, i8* null)
+  call void @runtime.printint32(i32 %1)
   ret void
 
 typeswitch.notDoubler:                            ; preds = %typeswitch.notUnmatched
@@ -76,6 +76,20 @@ define i32 @"(Number).Double$invoke"(i8* %receiverPtr, i8* %parentHandle) {
   ret i32 %ret
 }
 
+define internal i32 @"(Doubler).Double"(i8* %0, i8* %1, i32 %actualType, i8* %parentHandle) unnamed_addr {
+entry:
+  switch i32 %actualType, label %default [
+    i32 68, label %"reflect/types.type:named:Number"
+  ]
+
+default:                                          ; preds = %entry
+  unreachable
+
+"reflect/types.type:named:Number":                ; preds = %entry
+  %2 = call i32 @"(Number).Double$invoke"(i8* %0, i8* %1)
+  ret i32 %2
+}
+
 define internal i1 @"Doubler$typeassert"(i32 %actualType) unnamed_addr {
 entry:
   switch i32 %actualType, label %else [

From 734613c20e4695acb11aea0c569dc0fef9455c22 Mon Sep 17 00:00:00 2001
From: Ayke van Laethem 
Date: Thu, 28 May 2020 01:29:45 +0200
Subject: [PATCH 53/82] transform: introduce check for method calls on nil
 interfaces

I ran into an issue where I did a method call on a nil interface and it
resulted in a HardFault. Luckily I quickly realized what was going on so
I could fix it, but I think undefined behavior is definitely the wrong
behavior in this case. This commit therefore changes such calls to cause
a nil panic instead of introducing undefined behavior.

This does have a code size impact. It's relatively minor, much lower
than I expected. When comparing the before and after of the drivers
smoke tests (probably the most representative sample available), I found
that most did not change at all and those that did change, normally not
more than 100 bytes (16 or 32 byte changes are typical).

Right now the pattern is the following:

    switch typecode {
    case 1:
        call method 1
    case 2:
        call method 2
    default:
        nil panic
    }

I also tried the following (in the hope that it would be easier to
optimize), but it didn't really result in a code size reduction:

    switch typecode {
    case 1:
        call method 1
    case 2:
        call method 2
    case 0:
        nil panic
    default:
        unreachable
    }

Some code got smaller, while other code (the majority) got bigger. Maybe
this can be improved once range[1] is finally allowed[2] on function
parameters, but it'll probably take a while before that is implemented.

[1]: https://llvm.org/docs/LangRef.html#range-metadata
[2]: https://github.com/rust-lang/rust/issues/50156
---
 transform/interface-lowering.go     | 13 +++++++++++--
 transform/testdata/interface.ll     |  1 +
 transform/testdata/interface.out.ll |  3 +++
 3 files changed, 15 insertions(+), 2 deletions(-)

diff --git a/transform/interface-lowering.go b/transform/interface-lowering.go
index d64f2731d2..6aa3bb1347 100644
--- a/transform/interface-lowering.go
+++ b/transform/interface-lowering.go
@@ -616,10 +616,19 @@ func (p *lowerInterfacesPass) createInterfaceMethodFunc(itf *interfaceInfo, sign
 	// Create entry block.
 	entry := p.ctx.AddBasicBlock(fn, "entry")
 
-	// Create default block and make it unreachable (which it is, because all
-	// possible types are checked).
+	// Create default block and call runtime.nilPanic.
+	// The only other possible value remaining is nil for nil interfaces. We
+	// could panic with a different message here such as "nil interface" but
+	// that would increase code size and "nil panic" is close enough. Most
+	// importantly, it avoids undefined behavior when accidentally calling a
+	// method on a nil interface.
 	defaultBlock := p.ctx.AddBasicBlock(fn, "default")
 	p.builder.SetInsertPointAtEnd(defaultBlock)
+	nilPanic := p.mod.NamedFunction("runtime.nilPanic")
+	p.builder.CreateCall(nilPanic, []llvm.Value{
+		llvm.Undef(llvm.PointerType(p.ctx.Int8Type(), 0)),
+		llvm.Undef(llvm.PointerType(p.ctx.Int8Type(), 0)),
+	}, "")
 	p.builder.CreateUnreachable()
 
 	// Create type switch in entry block.
diff --git a/transform/testdata/interface.ll b/transform/testdata/interface.ll
index ee059c959a..1e2ce61b6e 100644
--- a/transform/testdata/interface.ll
+++ b/transform/testdata/interface.ll
@@ -24,6 +24,7 @@ declare void @runtime.printuint8(i8)
 declare void @runtime.printint32(i32)
 declare void @runtime.printptr(i32)
 declare void @runtime.printnl()
+declare void @runtime.nilPanic(i8*, i8*)
 
 define void @printInterfaces() {
   call void @printInterface(i32 ptrtoint (%runtime.typeInInterface* @"typeInInterface:reflect/types.type:basic:int" to i32), i8* inttoptr (i32 5 to i8*))
diff --git a/transform/testdata/interface.out.ll b/transform/testdata/interface.out.ll
index 80f71ee4e9..4bb9ec1bbc 100644
--- a/transform/testdata/interface.out.ll
+++ b/transform/testdata/interface.out.ll
@@ -25,6 +25,8 @@ declare void @runtime.printptr(i32)
 
 declare void @runtime.printnl()
 
+declare void @runtime.nilPanic(i8*, i8*)
+
 define void @printInterfaces() {
   call void @printInterface(i32 4, i8* inttoptr (i32 5 to i8*))
   call void @printInterface(i32 16, i8* inttoptr (i8 120 to i8*))
@@ -83,6 +85,7 @@ entry:
   ]
 
 default:                                          ; preds = %entry
+  call void @runtime.nilPanic(i8* undef, i8* undef)
   unreachable
 
 "reflect/types.type:named:Number":                ; preds = %entry

From fed433c04649ebeff2bbcf7a126fa36c525430ea Mon Sep 17 00:00:00 2001
From: Ayke van Laethem 
Date: Fri, 15 May 2020 23:24:41 +0200
Subject: [PATCH 54/82] compiler: add support for atomic operations

This also implements DisableInterrupts/EnableInterrupts for RISC-V, as
those operations were needed to implement a few libcalls.
---
 compiler/atomic.go              | 57 ++++++++++++++++++++
 compiler/compiler.go            |  8 +++
 src/device/riscv/riscv.go       | 16 ++++++
 src/runtime/arch_cortexm.go     | 83 ++++++++++++++++++++++++++++
 src/runtime/arch_tinygoriscv.go | 73 +++++++++++++++++++++++++
 src/runtime/atomic.go           | 24 ---------
 src/runtime/runtime_unix.go     | 11 ++++
 src/runtime/runtime_wasm.go     | 11 ++++
 testdata/atomic.go              | 95 +++++++++++++++++++++++++++++++++
 testdata/atomic.txt             | 35 ++++++++++++
 10 files changed, 389 insertions(+), 24 deletions(-)
 create mode 100644 compiler/atomic.go
 delete mode 100644 src/runtime/atomic.go
 create mode 100644 testdata/atomic.go
 create mode 100644 testdata/atomic.txt

diff --git a/compiler/atomic.go b/compiler/atomic.go
new file mode 100644
index 0000000000..99e8385ad1
--- /dev/null
+++ b/compiler/atomic.go
@@ -0,0 +1,57 @@
+package compiler
+
+import (
+	"golang.org/x/tools/go/ssa"
+	"tinygo.org/x/go-llvm"
+)
+
+// createAtomicOp lowers an atomic library call by lowering it as an LLVM atomic
+// operation. It returns the result of the operation and true if the call could
+// be lowered inline, and false otherwise.
+func (b *builder) createAtomicOp(call *ssa.CallCommon) (llvm.Value, bool) {
+	name := call.Value.(*ssa.Function).Name()
+	switch name {
+	case "AddInt32", "AddInt64", "AddUint32", "AddUint64", "AddUintptr":
+		ptr := b.getValue(call.Args[0])
+		val := b.getValue(call.Args[1])
+		oldVal := b.CreateAtomicRMW(llvm.AtomicRMWBinOpAdd, ptr, val, llvm.AtomicOrderingSequentiallyConsistent, true)
+		// Return the new value, not the original value returned by atomicrmw.
+		return b.CreateAdd(oldVal, val, ""), true
+	case "SwapInt32", "SwapInt64", "SwapUint32", "SwapUint64", "SwapUintptr", "SwapPointer":
+		ptr := b.getValue(call.Args[0])
+		val := b.getValue(call.Args[1])
+		isPointer := val.Type().TypeKind() == llvm.PointerTypeKind
+		if isPointer {
+			// atomicrmw only supports integers, so cast to an integer.
+			val = b.CreatePtrToInt(val, b.uintptrType, "")
+			ptr = b.CreateBitCast(ptr, llvm.PointerType(val.Type(), 0), "")
+		}
+		oldVal := b.CreateAtomicRMW(llvm.AtomicRMWBinOpXchg, ptr, val, llvm.AtomicOrderingSequentiallyConsistent, true)
+		if isPointer {
+			oldVal = b.CreateIntToPtr(oldVal, b.i8ptrType, "")
+		}
+		return oldVal, true
+	case "CompareAndSwapInt32", "CompareAndSwapInt64", "CompareAndSwapUint32", "CompareAndSwapUint64", "CompareAndSwapUintptr", "CompareAndSwapPointer":
+		ptr := b.getValue(call.Args[0])
+		old := b.getValue(call.Args[1])
+		newVal := b.getValue(call.Args[2])
+		tuple := b.CreateAtomicCmpXchg(ptr, old, newVal, llvm.AtomicOrderingSequentiallyConsistent, llvm.AtomicOrderingSequentiallyConsistent, true)
+		swapped := b.CreateExtractValue(tuple, 1, "")
+		return swapped, true
+	case "LoadInt32", "LoadInt64", "LoadUint32", "LoadUint64", "LoadUintptr", "LoadPointer":
+		ptr := b.getValue(call.Args[0])
+		val := b.CreateLoad(ptr, "")
+		val.SetOrdering(llvm.AtomicOrderingSequentiallyConsistent)
+		val.SetAlignment(b.targetData.PrefTypeAlignment(val.Type())) // required
+		return val, true
+	case "StoreInt32", "StoreInt64", "StoreUint32", "StoreUint64", "StoreUintptr", "StorePointer":
+		ptr := b.getValue(call.Args[0])
+		val := b.getValue(call.Args[1])
+		store := b.CreateStore(val, ptr)
+		store.SetOrdering(llvm.AtomicOrderingSequentiallyConsistent)
+		store.SetAlignment(b.targetData.PrefTypeAlignment(val.Type())) // required
+		return store, true
+	default:
+		return llvm.Value{}, false
+	}
+}
diff --git a/compiler/compiler.go b/compiler/compiler.go
index 53cafed51e..d977e1a068 100644
--- a/compiler/compiler.go
+++ b/compiler/compiler.go
@@ -1323,6 +1323,14 @@ func (b *builder) createFunctionCall(instr *ssa.CallCommon) (llvm.Value, error)
 			return b.createVolatileLoad(instr)
 		case strings.HasPrefix(name, "runtime/volatile.Store"):
 			return b.createVolatileStore(instr)
+		case strings.HasPrefix(name, "sync/atomic."):
+			val, ok := b.createAtomicOp(instr)
+			if ok {
+				// This call could be lowered as an atomic operation.
+				return val, nil
+			}
+			// This call couldn't be lowered as an atomic operation, it's
+			// probably something else. Continue as usual.
 		case name == "runtime/interrupt.New":
 			return b.createInterruptGlobal(instr)
 		}
diff --git a/src/device/riscv/riscv.go b/src/device/riscv/riscv.go
index 3a2e6cc43e..e4f9254b7d 100644
--- a/src/device/riscv/riscv.go
+++ b/src/device/riscv/riscv.go
@@ -19,3 +19,19 @@ func Asm(asm string)
 // You can use {} in the asm string (which expands to a register) to set the
 // return value.
 func AsmFull(asm string, regs map[string]interface{}) uintptr
+
+// DisableInterrupts disables all interrupts, and returns the old interrupt
+// state.
+func DisableInterrupts() uintptr {
+	// Note: this can be optimized with a CSRRW instruction, which atomically
+	// swaps the value and returns the old value.
+	mask := MIE.Get()
+	MIE.Set(0)
+	return mask
+}
+
+// EnableInterrupts enables all interrupts again. The value passed in must be
+// the mask returned by DisableInterrupts.
+func EnableInterrupts(mask uintptr) {
+	MIE.Set(mask)
+}
diff --git a/src/runtime/arch_cortexm.go b/src/runtime/arch_cortexm.go
index 17b824087b..3359eb6671 100644
--- a/src/runtime/arch_cortexm.go
+++ b/src/runtime/arch_cortexm.go
@@ -19,3 +19,86 @@ func align(ptr uintptr) uintptr {
 func getCurrentStackPointer() uintptr {
 	return arm.AsmFull("mov {}, sp", nil)
 }
+
+// Documentation:
+// * https://llvm.org/docs/Atomics.html
+// * https://gcc.gnu.org/onlinedocs/gcc/_005f_005fsync-Builtins.html
+//
+// In the case of Cortex-M, some atomic operations are emitted inline while
+// others are emitted as libcalls. How many are emitted as libcalls depends on
+// the MCU core variant (M3 and higher support some 32-bit atomic operations
+// while M0 and M0+ do not).
+
+//export __sync_fetch_and_add_4
+func __sync_fetch_and_add_4(ptr *uint32, value uint32) uint32 {
+	mask := arm.DisableInterrupts()
+	oldValue := *ptr
+	*ptr = oldValue + value
+	arm.EnableInterrupts(mask)
+	return oldValue
+}
+
+//export __sync_fetch_and_add_8
+func __sync_fetch_and_add_8(ptr *uint64, value uint64) uint64 {
+	mask := arm.DisableInterrupts()
+	oldValue := *ptr
+	*ptr = oldValue + value
+	arm.EnableInterrupts(mask)
+	return oldValue
+}
+
+//export __sync_lock_test_and_set_4
+func __sync_lock_test_and_set_4(ptr *uint32, value uint32) uint32 {
+	mask := arm.DisableInterrupts()
+	oldValue := *ptr
+	*ptr = value
+	arm.EnableInterrupts(mask)
+	return oldValue
+}
+
+//export __sync_lock_test_and_set_8
+func __sync_lock_test_and_set_8(ptr *uint64, value uint64) uint64 {
+	mask := arm.DisableInterrupts()
+	oldValue := *ptr
+	*ptr = value
+	arm.EnableInterrupts(mask)
+	return oldValue
+}
+
+//export __sync_val_compare_and_swap_4
+func __sync_val_compare_and_swap_4(ptr *uint32, expected, desired uint32) uint32 {
+	mask := arm.DisableInterrupts()
+	oldValue := *ptr
+	if oldValue == expected {
+		*ptr = desired
+	}
+	arm.EnableInterrupts(mask)
+	return oldValue
+}
+
+//export __sync_val_compare_and_swap_8
+func __sync_val_compare_and_swap_8(ptr *uint64, expected, desired uint64) uint64 {
+	mask := arm.DisableInterrupts()
+	oldValue := *ptr
+	if oldValue == expected {
+		*ptr = desired
+	}
+	arm.EnableInterrupts(mask)
+	return oldValue
+}
+
+// The safest thing to do here would just be to disable interrupts for
+// procPin/procUnpin. Note that a global variable is safe in this case, as any
+// access to procPinnedMask will happen with interrupts disabled.
+
+var procPinnedMask uintptr
+
+//go:linkname procPin sync/atomic.runtime_procPin
+func procPin() {
+	procPinnedMask = arm.DisableInterrupts()
+}
+
+//go:linkname procUnpin sync/atomic.runtime_procUnpin
+func procUnpin() {
+	arm.EnableInterrupts(procPinnedMask)
+}
diff --git a/src/runtime/arch_tinygoriscv.go b/src/runtime/arch_tinygoriscv.go
index 242f9b9691..98fbed70ac 100644
--- a/src/runtime/arch_tinygoriscv.go
+++ b/src/runtime/arch_tinygoriscv.go
@@ -17,3 +17,76 @@ func align(ptr uintptr) uintptr {
 func getCurrentStackPointer() uintptr {
 	return riscv.AsmFull("mv {}, sp", nil)
 }
+
+// Documentation:
+// * https://llvm.org/docs/Atomics.html
+// * https://gcc.gnu.org/onlinedocs/gcc/_005f_005fsync-Builtins.html
+//
+// In the case of RISC-V, some operations may be implemented with libcalls if
+// the operation is too big to be handled by assembly. Officially, these calls
+// should be implemented with a lock-free algorithm but as (as of this time) all
+// supported RISC-V chips have a single hart, we can simply disable interrupts
+// to get the same behavior.
+
+//export __atomic_load_8
+func __atomic_load_8(ptr *uint64, ordering int32) uint64 {
+	mask := riscv.DisableInterrupts()
+	value := *ptr
+	riscv.EnableInterrupts(mask)
+	return value
+}
+
+//export __atomic_store_8
+func __atomic_store_8(ptr *uint64, value uint64, ordering int32) {
+	mask := riscv.DisableInterrupts()
+	*ptr = value
+	riscv.EnableInterrupts(mask)
+}
+
+//export __atomic_exchange_8
+func __atomic_exchange_8(ptr *uint64, value uint64, ordering int32) uint64 {
+	mask := riscv.DisableInterrupts()
+	oldValue := *ptr
+	*ptr = value
+	riscv.EnableInterrupts(mask)
+	return oldValue
+}
+
+//export __atomic_compare_exchange_8
+func __atomic_compare_exchange_8(ptr, expected *uint64, desired uint64, success_ordering, failure_ordering int32) bool {
+	mask := riscv.DisableInterrupts()
+	oldValue := *ptr
+	success := oldValue == *expected
+	if success {
+		*ptr = desired
+	} else {
+		*expected = oldValue
+	}
+	riscv.EnableInterrupts(mask)
+	return success
+}
+
+//export __atomic_fetch_add_8
+func __atomic_fetch_add_8(ptr *uint64, value uint64, ordering int32) uint64 {
+	mask := riscv.DisableInterrupts()
+	oldValue := *ptr
+	*ptr = oldValue + value
+	riscv.EnableInterrupts(mask)
+	return oldValue
+}
+
+// The safest thing to do here would just be to disable interrupts for
+// procPin/procUnpin. Note that a global variable is safe in this case, as any
+// access to procPinnedMask will happen with interrupts disabled.
+
+var procPinnedMask uintptr
+
+//go:linkname procPin sync/atomic.runtime_procPin
+func procPin() {
+	procPinnedMask = riscv.DisableInterrupts()
+}
+
+//go:linkname procUnpin sync/atomic.runtime_procUnpin
+func procUnpin() {
+	riscv.EnableInterrupts(procPinnedMask)
+}
diff --git a/src/runtime/atomic.go b/src/runtime/atomic.go
deleted file mode 100644
index f9ae3031e9..0000000000
--- a/src/runtime/atomic.go
+++ /dev/null
@@ -1,24 +0,0 @@
-package runtime
-
-// This file contains implementations for the sync/atomic package.
-
-// All implementations assume there are no goroutines, threads or interrupts.
-
-//go:linkname loadUint64 sync/atomic.LoadUint64
-func loadUint64(addr *uint64) uint64 {
-	return *addr
-}
-
-//go:linkname storeUint32 sync/atomic.StoreUint32
-func storeUint32(addr *uint32, val uint32) {
-	*addr = val
-}
-
-//go:linkname compareAndSwapUint64 sync/atomic.CompareAndSwapUint64
-func compareAndSwapUint64(addr *uint64, old, new uint64) bool {
-	if *addr == old {
-		*addr = new
-		return true
-	}
-	return false
-}
diff --git a/src/runtime/runtime_unix.go b/src/runtime/runtime_unix.go
index e1adeb3428..acb8309467 100644
--- a/src/runtime/runtime_unix.go
+++ b/src/runtime/runtime_unix.go
@@ -94,3 +94,14 @@ func extalloc(size uintptr) unsafe.Pointer {
 
 //export free
 func extfree(ptr unsafe.Pointer)
+
+// TinyGo does not yet support any form of parallelism on an OS, so these can be
+// left empty.
+
+//go:linkname procPin sync/atomic.runtime_procPin
+func procPin() {
+}
+
+//go:linkname procUnpin sync/atomic.runtime_procUnpin
+func procUnpin() {
+}
diff --git a/src/runtime/runtime_wasm.go b/src/runtime/runtime_wasm.go
index 3cddd9d8e7..aaf95d7846 100644
--- a/src/runtime/runtime_wasm.go
+++ b/src/runtime/runtime_wasm.go
@@ -91,3 +91,14 @@ func ticks() timeUnit
 func abort() {
 	trap()
 }
+
+// TinyGo does not yet support any form of parallelism on WebAssembly, so these
+// can be left empty.
+
+//go:linkname procPin sync/atomic.runtime_procPin
+func procPin() {
+}
+
+//go:linkname procUnpin sync/atomic.runtime_procUnpin
+func procUnpin() {
+}
diff --git a/testdata/atomic.go b/testdata/atomic.go
new file mode 100644
index 0000000000..f99a39bb51
--- /dev/null
+++ b/testdata/atomic.go
@@ -0,0 +1,95 @@
+package main
+
+import (
+	"sync/atomic"
+	"unsafe"
+)
+
+func main() {
+	i32 := int32(-5)
+	println("AddInt32:", atomic.AddInt32(&i32, 8), i32)
+
+	i64 := int64(-5)
+	println("AddInt64:", atomic.AddInt64(&i64, 8), i64)
+
+	u32 := uint32(5)
+	println("AddUint32:", atomic.AddUint32(&u32, 8), u32)
+
+	u64 := uint64(5)
+	println("AddUint64:", atomic.AddUint64(&u64, 8), u64)
+
+	uptr := uintptr(5)
+	println("AddUintptr:", uint64(atomic.AddUintptr(&uptr, 8)), uint64(uptr))
+
+	println("SwapInt32:", atomic.SwapInt32(&i32, 33), i32)
+	println("SwapInt64:", atomic.SwapInt64(&i64, 33), i64)
+	println("SwapUint32:", atomic.SwapUint32(&u32, 33), u32)
+	println("SwapUint64:", atomic.SwapUint64(&u64, 33), u64)
+	println("SwapUintptr:", uint64(atomic.SwapUintptr(&uptr, 33)), uint64(uptr))
+	ptr := unsafe.Pointer(&i32)
+	println("SwapPointer:", atomic.SwapPointer(&ptr, unsafe.Pointer(&u32)) == unsafe.Pointer(&i32), ptr == unsafe.Pointer(&u32))
+
+	i32 = int32(-5)
+	println("CompareAndSwapInt32:", atomic.CompareAndSwapInt32(&i32, 5, 3), i32)
+	println("CompareAndSwapInt32:", atomic.CompareAndSwapInt32(&i32, -5, 3), i32)
+
+	i64 = int64(-5)
+	println("CompareAndSwapInt64:", atomic.CompareAndSwapInt64(&i64, 5, 3), i64)
+	println("CompareAndSwapInt64:", atomic.CompareAndSwapInt64(&i64, -5, 3), i64)
+
+	u32 = uint32(5)
+	println("CompareAndSwapUint32:", atomic.CompareAndSwapUint32(&u32, 4, 3), u32)
+	println("CompareAndSwapUint32:", atomic.CompareAndSwapUint32(&u32, 5, 3), u32)
+
+	u64 = uint64(5)
+	println("CompareAndSwapUint64:", atomic.CompareAndSwapUint64(&u64, 4, 3), u64)
+	println("CompareAndSwapUint64:", atomic.CompareAndSwapUint64(&u64, 5, 3), u64)
+
+	uptr = uintptr(5)
+	println("CompareAndSwapUintptr:", atomic.CompareAndSwapUintptr(&uptr, 4, 3), uint64(uptr))
+	println("CompareAndSwapUintptr:", atomic.CompareAndSwapUintptr(&uptr, 5, 3), uint64(uptr))
+
+	ptr = unsafe.Pointer(&i32)
+	println("CompareAndSwapPointer:", atomic.CompareAndSwapPointer(&ptr, unsafe.Pointer(&u32), unsafe.Pointer(&i64)), ptr == unsafe.Pointer(&i32))
+	println("CompareAndSwapPointer:", atomic.CompareAndSwapPointer(&ptr, unsafe.Pointer(&i32), unsafe.Pointer(&i64)), ptr == unsafe.Pointer(&i64))
+
+	println("LoadInt32:", atomic.LoadInt32(&i32))
+	println("LoadInt64:", atomic.LoadInt64(&i64))
+	println("LoadUint32:", atomic.LoadUint32(&u32))
+	println("LoadUint64:", atomic.LoadUint64(&u64))
+	println("LoadUintptr:", uint64(atomic.LoadUintptr(&uptr)))
+	println("LoadPointer:", atomic.LoadPointer(&ptr) == unsafe.Pointer(&i64))
+
+	atomic.StoreInt32(&i32, -20)
+	println("StoreInt32:", i32)
+
+	atomic.StoreInt64(&i64, -20)
+	println("StoreInt64:", i64)
+
+	atomic.StoreUint32(&u32, 20)
+	println("StoreUint32:", u32)
+
+	atomic.StoreUint64(&u64, 20)
+	println("StoreUint64:", u64)
+
+	atomic.StoreUintptr(&uptr, 20)
+	println("StoreUintptr:", uint64(uptr))
+
+	atomic.StorePointer(&ptr, unsafe.Pointer(&uptr))
+	println("StorePointer:", ptr == unsafe.Pointer(&uptr))
+
+	// test atomic.Value load/store operations
+	testValue(int(3), int(-2))
+	testValue("", "foobar", "baz")
+}
+
+func testValue(values ...interface{}) {
+	var av atomic.Value
+	for _, val := range values {
+		av.Store(val)
+		loadedVal := av.Load()
+		if loadedVal != val {
+			println("val store/load didn't work, expected", val, "but got", loadedVal)
+		}
+	}
+}
diff --git a/testdata/atomic.txt b/testdata/atomic.txt
new file mode 100644
index 0000000000..d1f2ab2937
--- /dev/null
+++ b/testdata/atomic.txt
@@ -0,0 +1,35 @@
+AddInt32: 3 3
+AddInt64: 3 3
+AddUint32: 13 13
+AddUint64: 13 13
+AddUintptr: 13 13
+SwapInt32: 3 33
+SwapInt64: 3 33
+SwapUint32: 13 33
+SwapUint64: 13 33
+SwapUintptr: 13 33
+SwapPointer: true true
+CompareAndSwapInt32: false -5
+CompareAndSwapInt32: true 3
+CompareAndSwapInt64: false -5
+CompareAndSwapInt64: true 3
+CompareAndSwapUint32: false 5
+CompareAndSwapUint32: true 3
+CompareAndSwapUint64: false 5
+CompareAndSwapUint64: true 3
+CompareAndSwapUintptr: false 5
+CompareAndSwapUintptr: true 3
+CompareAndSwapPointer: false true
+CompareAndSwapPointer: true true
+LoadInt32: 3
+LoadInt64: 3
+LoadUint32: 3
+LoadUint64: 3
+LoadUintptr: 3
+LoadPointer: true
+StoreInt32: -20
+StoreInt64: -20
+StoreUint32: 20
+StoreUint64: 20
+StoreUintptr: 20
+StorePointer: true

From 5c8d4e54d6d1d9e21a2c464874d78374f97f3bdf Mon Sep 17 00:00:00 2001
From: sago35 
Date: Tue, 12 May 2020 21:07:08 +0900
Subject: [PATCH 55/82] sam: add support for pin change interrupts (samd5x)

---
 src/machine/machine_atsamd51.go | 160 ++++++++++++++++++++++++++++++++
 1 file changed, 160 insertions(+)

diff --git a/src/machine/machine_atsamd51.go b/src/machine/machine_atsamd51.go
index 3b3c6a03b7..6489403efe 100644
--- a/src/machine/machine_atsamd51.go
+++ b/src/machine/machine_atsamd51.go
@@ -44,6 +44,26 @@ const (
 	PinInputPulldown PinMode = 18
 )
 
+type PinChange uint8
+
+// Pin change interrupt constants for SetInterrupt.
+const (
+	PinRising  PinChange = sam.EIC_CONFIG_SENSE0_RISE
+	PinFalling PinChange = sam.EIC_CONFIG_SENSE0_FALL
+	PinToggle  PinChange = sam.EIC_CONFIG_SENSE0_BOTH
+)
+
+// Callbacks to be called for pins configured with SetInterrupt. Unfortunately,
+// we also need to keep track of which interrupt channel is used by which pin,
+// as the only alternative would be iterating through all pins.
+//
+// We're using the magic constant 16 here because the SAM D21 has 16 interrupt
+// channels configurable for pins.
+var (
+	interruptPins [16]Pin // warning: the value is invalid when pinCallbacks[i] is not set!
+	pinCallbacks  [16]func(Pin)
+)
+
 // Hardware pins
 const (
 	PA00 Pin = 0
@@ -268,6 +288,146 @@ func findPinPadMapping(sercom uint8, pin Pin) (pinMode PinMode, pad uint32, ok b
 	return
 }
 
+// SetInterrupt sets an interrupt to be executed when a particular pin changes
+// state.
+//
+// This call will replace a previously set callback on this pin. You can pass a
+// nil func to unset the pin change interrupt. If you do so, the change
+// parameter is ignored and can be set to any value (such as 0).
+func (p Pin) SetInterrupt(change PinChange, callback func(Pin)) error {
+	// Most pins follow a common pattern where the EXTINT value is the pin
+	// number modulo 16. However, there are a few exceptions, as you can see
+	// below.
+	extint := uint8(0)
+
+	switch p {
+	case PA08:
+		// Connected to NMI. This is not currently supported.
+		return ErrInvalidInputPin
+	case PB26:
+		extint = 12
+	case PB27:
+		extint = 13
+	case PB28:
+		extint = 14
+	case PB29:
+		extint = 15
+	default:
+		// All other pins follow a normal pattern.
+		extint = uint8(p) % 16
+	}
+
+	if callback == nil {
+		// Disable this pin interrupt (if it was enabled).
+		sam.EIC.INTENCLR.Set(1 << extint)
+		if pinCallbacks[extint] != nil {
+			pinCallbacks[extint] = nil
+		}
+		return nil
+	}
+
+	if pinCallbacks[extint] != nil {
+		// The pin was already configured.
+		// To properly re-configure a pin, unset it first and set a new
+		// configuration.
+		return ErrNoPinChangeChannel
+	}
+	pinCallbacks[extint] = callback
+	interruptPins[extint] = p
+
+	if (sam.EIC.CTRLA.Get() & 0x02) == 0 {
+		// EIC peripheral has not yet been initialized. Initialize it now.
+
+		// The EIC needs two clocks: CLK_EIC_APB and GCLK_EIC. CLK_EIC_APB is
+		// enabled by default, so doesn't have to be re-enabled. The other is
+		// required for detecting edges and must be enabled manually.
+		sam.GCLK.PCHCTRL[4].Set((sam.GCLK_PCHCTRL_GEN_GCLK0 << sam.GCLK_PCHCTRL_GEN_Pos) | sam.GCLK_PCHCTRL_CHEN)
+
+		// should not be necessary (CLKCTRL is not synchronized)
+		for sam.GCLK.SYNCBUSY.HasBits(sam.GCLK_SYNCBUSY_GENCTRL_GCLK0 << sam.GCLK_SYNCBUSY_GENCTRL_Pos) {
+		}
+	}
+
+	// CONFIG register is enable-protected, so disable EIC.
+	sam.EIC.CTRLA.Set(0)
+
+	// Configure this pin. Set the 4 bits of the EIC.CONFIGx register to the
+	// sense value (filter bit set to 0, sense bits set to the change value).
+	addr := &sam.EIC.CONFIG[0]
+	if extint >= 8 {
+		addr = &sam.EIC.CONFIG[1]
+	}
+	pos := (extint % 8) * 4 // bit position in register
+	addr.Set((addr.Get() &^ (0xf << pos)) | uint32(change)< 0 {
+		// odd pin, so save the even pins
+		val := p.getPMux() & sam.PORT_GROUP_PMUX_PMUXE_Msk
+		p.setPMux(val | (0 << sam.PORT_GROUP_PMUX_PMUXO_Pos))
+	} else {
+		// even pin, so save the odd pins
+		val := p.getPMux() & sam.PORT_GROUP_PMUX_PMUXO_Msk
+		p.setPMux(val | (0 << sam.PORT_GROUP_PMUX_PMUXE_Pos))
+	}
+
+	handleEICInterrupt := func(interrupt.Interrupt) {
+		flags := sam.EIC.INTFLAG.Get()
+		sam.EIC.INTFLAG.Set(flags)      // clear interrupt
+		for i := uint(0); i < 16; i++ { // there are 16 channels
+			if flags&(1<
Date: Sun, 31 May 2020 01:03:46 +0900
Subject: [PATCH 56/82] sam: fix register access for interrupts pins in samd51
 (#1141)

* machine/samd51: fix register access for interrupts pins in samd51
---
 src/machine/machine_atsamd51.go | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/src/machine/machine_atsamd51.go b/src/machine/machine_atsamd51.go
index 6489403efe..786174442d 100644
--- a/src/machine/machine_atsamd51.go
+++ b/src/machine/machine_atsamd51.go
@@ -335,7 +335,7 @@ func (p Pin) SetInterrupt(change PinChange, callback func(Pin)) error {
 	pinCallbacks[extint] = callback
 	interruptPins[extint] = p
 
-	if (sam.EIC.CTRLA.Get() & 0x02) == 0 {
+	if !sam.EIC.CTRLA.HasBits(sam.EIC_CTRLA_ENABLE) {
 		// EIC peripheral has not yet been initialized. Initialize it now.
 
 		// The EIC needs two clocks: CLK_EIC_APB and GCLK_EIC. CLK_EIC_APB is
@@ -349,7 +349,7 @@ func (p Pin) SetInterrupt(change PinChange, callback func(Pin)) error {
 	}
 
 	// CONFIG register is enable-protected, so disable EIC.
-	sam.EIC.CTRLA.Set(0)
+	sam.EIC.CTRLA.ClearBits(sam.EIC_CTRLA_ENABLE)
 
 	// Configure this pin. Set the 4 bits of the EIC.CONFIGx register to the
 	// sense value (filter bit set to 0, sense bits set to the change value).

From 5e2a8024a35e20dd82078cfc05b844a0fef2cb67 Mon Sep 17 00:00:00 2001
From: deadprogram 
Date: Sat, 23 May 2020 08:34:47 +0200
Subject: [PATCH 57/82] main: use auto-retry (up to 10 seconds) to locate MSD
 for UF2 and HEX flashing

Signed-off-by: deadprogram 
---
 main.go | 30 ++++++++++++++++++++++++------
 1 file changed, 24 insertions(+), 6 deletions(-)

diff --git a/main.go b/main.go
index feece40000..1a8e4b961a 100644
--- a/main.go
+++ b/main.go
@@ -491,6 +491,8 @@ func touchSerialPortAt1200bps(port string) (err error) {
 	return fmt.Errorf("opening port: %s", err)
 }
 
+const maxMSDRetries = 10
+
 func flashUF2UsingMSD(volume, tmppath string) error {
 	// find standard UF2 info path
 	var infoPath string
@@ -507,9 +509,17 @@ func flashUF2UsingMSD(volume, tmppath string) error {
 		infoPath = path + "/INFO_UF2.TXT"
 	}
 
-	d, err := filepath.Glob(infoPath)
-	if err != nil {
-		return err
+	var d []string
+	var err error
+	for i := 0; i < maxMSDRetries; i++ {
+		d, err = filepath.Glob(infoPath)
+		if err != nil {
+			return err
+		}
+		if d != nil {
+			break
+		}
+		time.Sleep(1 * time.Second)
 	}
 	if d == nil {
 		return errors.New("unable to locate UF2 device: " + volume)
@@ -534,9 +544,17 @@ func flashHexUsingMSD(volume, tmppath string) error {
 		destPath = path + "/"
 	}
 
-	d, err := filepath.Glob(destPath)
-	if err != nil {
-		return err
+	var d []string
+	var err error
+	for i := 0; i < maxMSDRetries; i++ {
+		d, err = filepath.Glob(destPath)
+		if err != nil {
+			return err
+		}
+		if d != nil {
+			break
+		}
+		time.Sleep(1 * time.Second)
 	}
 	if d == nil {
 		return errors.New("unable to locate device: " + volume)

From bcbc241d810f899da46a3217b759cbc590ec1a2b Mon Sep 17 00:00:00 2001
From: deadprogram 
Date: Wed, 27 May 2020 21:44:39 +0200
Subject: [PATCH 58/82] main: improve/simplify auto-retry to locate MSD for UF2
 and HEX flashing

Signed-off-by: deadprogram 
---
 main.go | 39 ++++++++++++++++++---------------------
 1 file changed, 18 insertions(+), 21 deletions(-)

diff --git a/main.go b/main.go
index 1a8e4b961a..54a9c542b4 100644
--- a/main.go
+++ b/main.go
@@ -509,23 +509,12 @@ func flashUF2UsingMSD(volume, tmppath string) error {
 		infoPath = path + "/INFO_UF2.TXT"
 	}
 
-	var d []string
-	var err error
-	for i := 0; i < maxMSDRetries; i++ {
-		d, err = filepath.Glob(infoPath)
-		if err != nil {
-			return err
-		}
-		if d != nil {
-			break
-		}
-		time.Sleep(1 * time.Second)
-	}
-	if d == nil {
-		return errors.New("unable to locate UF2 device: " + volume)
+	d, err := locateDevice(volume, infoPath)
+	if err != nil {
+		return err
 	}
 
-	return moveFile(tmppath, filepath.Dir(d[0])+"/flash.uf2")
+	return moveFile(tmppath, filepath.Dir(d)+"/flash.uf2")
 }
 
 func flashHexUsingMSD(volume, tmppath string) error {
@@ -544,23 +533,31 @@ func flashHexUsingMSD(volume, tmppath string) error {
 		destPath = path + "/"
 	}
 
+	d, err := locateDevice(volume, destPath)
+	if err != nil {
+		return err
+	}
+
+	return moveFile(tmppath, d+"/flash.hex")
+}
+
+func locateDevice(volume, path string) (string, error) {
 	var d []string
 	var err error
 	for i := 0; i < maxMSDRetries; i++ {
-		d, err = filepath.Glob(destPath)
+		d, err = filepath.Glob(path)
 		if err != nil {
-			return err
+			return "", err
 		}
 		if d != nil {
 			break
 		}
-		time.Sleep(1 * time.Second)
+		time.Sleep(500 * time.Millisecond)
 	}
 	if d == nil {
-		return errors.New("unable to locate device: " + volume)
+		return "", errors.New("unable to locate device: " + volume)
 	}
-
-	return moveFile(tmppath, d[0]+"/flash.hex")
+	return d[0], nil
 }
 
 func windowsFindUSBDrive(volume string) (string, error) {

From 0e73790d67ef4f56187c62b8a87d6ee197a3d03a Mon Sep 17 00:00:00 2001
From: Ayke van Laethem 
Date: Sat, 30 May 2020 21:22:59 +0200
Subject: [PATCH 59/82] Dockerfile: avoid duplicate LLVM apt line

This line causes problems when installing software: apt-get complains
that there are duplicate lines.
---
 Dockerfile | 5 +----
 1 file changed, 1 insertion(+), 4 deletions(-)

diff --git a/Dockerfile b/Dockerfile
index 6d958125eb..60eff30f70 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -27,10 +27,7 @@ COPY --from=tinygo-base /go/bin/tinygo /go/bin/tinygo
 COPY --from=tinygo-base /tinygo/src /tinygo/src
 COPY --from=tinygo-base /tinygo/targets /tinygo/targets
 
-RUN wget -O- https://apt.llvm.org/llvm-snapshot.gpg.key| apt-key add - && \
-    echo "deb http://apt.llvm.org/buster/ llvm-toolchain-buster-10 main" >> /etc/apt/sources.list && \
-    apt-get update && \
-    apt-get install -y libllvm10 lld-10
+RUN apt-get install -y libllvm10 lld-10
 
 # tinygo-avr stage installs the needed dependencies to compile TinyGo programs for AVR microcontrollers.
 FROM tinygo-base AS tinygo-avr

From 3c31a3110f7b91e738154e27198576951aa9290e Mon Sep 17 00:00:00 2001
From: Ayke van Laethem 
Date: Sun, 31 May 2020 13:11:00 +0200
Subject: [PATCH 60/82] builder: use newer version of gohex

This version adds error handling for the DumpIntelHex method, and thus
fixes a TODO comment.
---
 builder/objcopy.go | 3 +--
 go.mod             | 2 +-
 go.sum             | 2 ++
 3 files changed, 4 insertions(+), 3 deletions(-)

diff --git a/builder/objcopy.go b/builder/objcopy.go
index 3ab0eaeff9..e5b245c675 100644
--- a/builder/objcopy.go
+++ b/builder/objcopy.go
@@ -123,8 +123,7 @@ func objcopy(infile, outfile string) error {
 		if err != nil {
 			return objcopyError{"failed to create .hex file", err}
 		}
-		mem.DumpIntelHex(f, 16) // TODO: handle error
-		return nil
+		return mem.DumpIntelHex(f, 16)
 	default:
 		panic("unreachable")
 	}
diff --git a/go.mod b/go.mod
index a35767d3b0..186b44999c 100644
--- a/go.mod
+++ b/go.mod
@@ -7,7 +7,7 @@ require (
 	github.com/chromedp/cdproto v0.0.0-20200116234248-4da64dd111ac
 	github.com/chromedp/chromedp v0.5.3
 	github.com/google/shlex v0.0.0-20181106134648-c34317bd91bf
-	github.com/marcinbor85/gohex v0.0.0-20180128172054-7a43cd876e46
+	github.com/marcinbor85/gohex v0.0.0-20200531091804-343a4b548892
 	go.bug.st/serial v1.0.0
 	golang.org/x/tools v0.0.0-20200216192241-b320d3a0f5a2
 	google.golang.org/appengine v1.4.0 // indirect
diff --git a/go.sum b/go.sum
index 60fdd4db1a..e6d4295c14 100644
--- a/go.sum
+++ b/go.sum
@@ -22,6 +22,8 @@ github.com/mailru/easyjson v0.7.0 h1:aizVhC/NAAcKWb+5QsU1iNOZb4Yws5UO2I+aIprQITM
 github.com/mailru/easyjson v0.7.0/go.mod h1:KAzv3t3aY1NaHWoQz1+4F1ccyAH66Jk7yos7ldAVICs=
 github.com/marcinbor85/gohex v0.0.0-20180128172054-7a43cd876e46 h1:wXG2bA8fO7Vv7lLk2PihFMTqmbT173Tje39oKzQ50Mo=
 github.com/marcinbor85/gohex v0.0.0-20180128172054-7a43cd876e46/go.mod h1:Pb6XcsXyropB9LNHhnqaknG/vEwYztLkQzVCHv8sQ3M=
+github.com/marcinbor85/gohex v0.0.0-20200531091804-343a4b548892 h1:6J+qramlHVLmiBOgRiBOnQkno8uprqG6YFFQTt6uYIw=
+github.com/marcinbor85/gohex v0.0.0-20200531091804-343a4b548892/go.mod h1:Pb6XcsXyropB9LNHhnqaknG/vEwYztLkQzVCHv8sQ3M=
 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=

From 72064e12db1fda5f8ce348d112207f92100dd2f0 Mon Sep 17 00:00:00 2001
From: sago35 
Date: Thu, 7 May 2020 12:44:49 +0900
Subject: [PATCH 61/82] WIP flash: fix touchSerialPortAt1200bps on windows

---
 main.go | 7 +++++++
 1 file changed, 7 insertions(+)

diff --git a/main.go b/main.go
index 54a9c542b4..f77c5612b0 100644
--- a/main.go
+++ b/main.go
@@ -479,6 +479,13 @@ func touchSerialPortAt1200bps(port string) (err error) {
 		// Open port
 		p, e := serial.Open(port, &serial.Mode{BaudRate: 1200})
 		if e != nil {
+			if runtime.GOOS == `windows` {
+				se, ok := e.(*serial.PortError)
+				if ok && se.Code() == serial.InvalidSerialPort {
+					// InvalidSerialPort error occurs when transitioning to boot
+					return nil
+				}
+			}
 			time.Sleep(1 * time.Second)
 			err = e
 			continue

From 97122972fb55932aeaad68b2d216c9c66297d9e9 Mon Sep 17 00:00:00 2001
From: sago35 
Date: Wed, 3 Jun 2020 21:30:39 +0900
Subject: [PATCH 62/82] Add SAMD51 pins

---
 src/machine/machine_atsamd51.go | 64 +++++++++++++++++++++++++++++++++
 1 file changed, 64 insertions(+)

diff --git a/src/machine/machine_atsamd51.go b/src/machine/machine_atsamd51.go
index 786174442d..186a510c2e 100644
--- a/src/machine/machine_atsamd51.go
+++ b/src/machine/machine_atsamd51.go
@@ -130,6 +130,70 @@ const (
 	PB29 Pin = 61
 	PB30 Pin = 62
 	PB31 Pin = 63
+	PC00 Pin = 64
+	PC01 Pin = 65
+	PC02 Pin = 66
+	PC03 Pin = 67
+	PC04 Pin = 68
+	PC05 Pin = 69
+	PC06 Pin = 70
+	PC07 Pin = 71
+	PC08 Pin = 72
+	PC09 Pin = 73
+	PC10 Pin = 74
+	PC11 Pin = 75
+	PC12 Pin = 76
+	PC13 Pin = 77
+	PC14 Pin = 78
+	PC15 Pin = 79
+	PC16 Pin = 80
+	PC17 Pin = 81
+	PC18 Pin = 82
+	PC19 Pin = 83
+	PC20 Pin = 84
+	PC21 Pin = 85
+	PC22 Pin = 86
+	PC23 Pin = 87
+	PC24 Pin = 88
+	PC25 Pin = 89
+	PC26 Pin = 90
+	PC27 Pin = 91
+	PC28 Pin = 92
+	PC29 Pin = 93
+	PC30 Pin = 94
+	PC31 Pin = 95
+	PD00 Pin = 96
+	PD01 Pin = 97
+	PD02 Pin = 98
+	PD03 Pin = 99
+	PD04 Pin = 100
+	PD05 Pin = 101
+	PD06 Pin = 102
+	PD07 Pin = 103
+	PD08 Pin = 104
+	PD09 Pin = 105
+	PD10 Pin = 106
+	PD11 Pin = 107
+	PD12 Pin = 108
+	PD13 Pin = 109
+	PD14 Pin = 110
+	PD15 Pin = 111
+	PD16 Pin = 112
+	PD17 Pin = 113
+	PD18 Pin = 114
+	PD19 Pin = 115
+	PD20 Pin = 116
+	PD21 Pin = 117
+	PD22 Pin = 118
+	PD23 Pin = 119
+	PD24 Pin = 120
+	PD25 Pin = 121
+	PD26 Pin = 122
+	PD27 Pin = 123
+	PD28 Pin = 124
+	PD29 Pin = 125
+	PD30 Pin = 126
+	PD31 Pin = 127
 )
 
 const (

From 40afeea5695e0fd8233375d761c0eb9691fc1537 Mon Sep 17 00:00:00 2001
From: sago35 
Date: Sun, 24 May 2020 10:34:41 +0900
Subject: [PATCH 63/82] Add SAMD51 ADC settings

---
 src/machine/machine_atsamd51.go | 20 +++++++++++++++++++-
 1 file changed, 19 insertions(+), 1 deletion(-)

diff --git a/src/machine/machine_atsamd51.go b/src/machine/machine_atsamd51.go
index 186a510c2e..766fa97c87 100644
--- a/src/machine/machine_atsamd51.go
+++ b/src/machine/machine_atsamd51.go
@@ -810,7 +810,7 @@ func (a ADC) Get() uint16 {
 }
 
 func (a ADC) getADCBus() *sam.ADC_Type {
-	if a.Pin >= PB04 && a.Pin <= PB07 {
+	if (a.Pin >= PB04 && a.Pin <= PB07) || (a.Pin >= PC00) {
 		return sam.ADC1
 	}
 	return sam.ADC0
@@ -853,6 +853,24 @@ func (a ADC) getADCChannel() uint8 {
 		return 8
 	case PB07:
 		return 9
+
+	case PC00:
+		return 10
+	case PC01:
+		return 11
+	case PC02:
+		return 4
+	case PC03:
+		return 5
+	case PC30:
+		return 12
+	case PC31:
+		return 13
+
+	case PD00:
+		return 14
+	case PD01:
+		return 15
 	default:
 		panic("Invalid ADC pin")
 	}

From f103e910d7426baa3c5a384b16385b5007fc775f Mon Sep 17 00:00:00 2001
From: sago35 
Date: Wed, 3 Jun 2020 21:36:52 +0900
Subject: [PATCH 64/82] Add SAMD51 pin change interrupt settings

---
 src/machine/machine_atsamd51.go | 16 ++++++++++++++++
 1 file changed, 16 insertions(+)

diff --git a/src/machine/machine_atsamd51.go b/src/machine/machine_atsamd51.go
index 766fa97c87..d8d62c7ff7 100644
--- a/src/machine/machine_atsamd51.go
+++ b/src/machine/machine_atsamd51.go
@@ -376,6 +376,22 @@ func (p Pin) SetInterrupt(change PinChange, callback func(Pin)) error {
 		extint = 14
 	case PB29:
 		extint = 15
+	case PC07:
+		extint = 9
+	case PD08:
+		extint = 3
+	case PD09:
+		extint = 4
+	case PD10:
+		extint = 5
+	case PD11:
+		extint = 6
+	case PD12:
+		extint = 7
+	case PD20:
+		extint = 10
+	case PD21:
+		extint = 11
 	default:
 		// All other pins follow a normal pattern.
 		extint = uint8(p) % 16

From 64d51b215f63a3b178c6337ab252086d16003ff7 Mon Sep 17 00:00:00 2001
From: sago35 
Date: Wed, 27 May 2020 23:53:04 +0900
Subject: [PATCH 65/82] Extend SAMD51 pinPadMapping

---
 src/machine/machine_atsamd51.go | 26 +++++++++++++-------------
 1 file changed, 13 insertions(+), 13 deletions(-)

diff --git a/src/machine/machine_atsamd51.go b/src/machine/machine_atsamd51.go
index d8d62c7ff7..042b90d9d6 100644
--- a/src/machine/machine_atsamd51.go
+++ b/src/machine/machine_atsamd51.go
@@ -264,7 +264,7 @@ const (
 // easy to check whether a nibble is set at all.
 //
 // Datasheet: http://ww1.microchip.com/downloads/en/DeviceDoc/60001507E.pdf
-var pinPadMapping = [32]uint16{
+var pinPadMapping = [64]uint16{
 	// page 32
 	PA00 / 2: 0 | pinPadMapSERCOM1AltPad0,
 
@@ -272,28 +272,28 @@ var pinPadMapping = [32]uint16{
 	PB08 / 2: 0 | pinPadMapSERCOM4AltPad0,
 	PA04 / 2: 0 | pinPadMapSERCOM0AltPad0,
 	PA06 / 2: 0 | pinPadMapSERCOM0AltPad2,
-	//PC04 / 2: pinPadMapSERCOM6Pad0 | 0,
-	//PC06 / 2: pinPadMapSERCOM6Pad2 | 0,
+	PC04 / 2: pinPadMapSERCOM6Pad0 | 0,
+	PC06 / 2: pinPadMapSERCOM6Pad2 | 0,
 	PA08 / 2: pinPadMapSERCOM0Pad0 | pinPadMapSERCOM2AltPad1,
 	PA10 / 2: pinPadMapSERCOM0Pad2 | pinPadMapSERCOM2AltPad2,
 	PB10 / 2: 0 | pinPadMapSERCOM4AltPad2,
 	PB12 / 2: pinPadMapSERCOM4Pad0 | 0,
 	PB14 / 2: pinPadMapSERCOM4Pad2 | 0,
-	//PD08 / 2: pinPadMapSERCOM7Pad0 | pinPadMapSERCOM6AltPad1,
-	//PD10 / 2: pinPadMapSERCOM7Pad2 | pinPadMapSERCOM6AltPad2,
-	//PC10 / 2: pinPadMapSERCOM6Pad2 | pinPadMapSERCOM7AltPad2,
+	PD08 / 2: pinPadMapSERCOM7Pad0 | pinPadMapSERCOM6AltPad1,
+	PD10 / 2: pinPadMapSERCOM7Pad2 | pinPadMapSERCOM6AltPad2,
+	PC10 / 2: pinPadMapSERCOM6Pad2 | pinPadMapSERCOM7AltPad2,
 
 	// page 34
-	//PC12 / 2: pinPadMapSERCOM7Pad0 | pinPadMapSERCOM6AltPad1,
-	//PC14 / 2: pinPadMapSERCOM7Pad2 | pinPadMapSERCOM6AltPad2,
+	PC12 / 2: pinPadMapSERCOM7Pad0 | pinPadMapSERCOM6AltPad1,
+	PC14 / 2: pinPadMapSERCOM7Pad2 | pinPadMapSERCOM6AltPad2,
 	PA12 / 2: pinPadMapSERCOM2Pad0 | pinPadMapSERCOM4AltPad1,
 	PA14 / 2: pinPadMapSERCOM2Pad2 | pinPadMapSERCOM4AltPad2,
 	PA16 / 2: pinPadMapSERCOM1Pad0 | pinPadMapSERCOM3AltPad1,
 	PA18 / 2: pinPadMapSERCOM1Pad2 | pinPadMapSERCOM3AltPad2,
-	//PC16 / 2: pinPadMapSERCOM6Pad0 | pinPadMapSERCOM0AltPad1,
-	//PC18 / 2: pinPadMapSERCOM6Pad2 | pinPadMapSERCOM0AltPad2,
-	//PC22 / 2: pinPadMapSERCOM1Pad0 | pinPadMapSERCOM3AltPad1,
-	//PD20 / 2: pinPadMapSERCOM1Pad2 | pinPadMapSERCOM3AltPad2,
+	PC16 / 2: pinPadMapSERCOM6Pad0 | pinPadMapSERCOM0AltPad1,
+	PC18 / 2: pinPadMapSERCOM6Pad2 | pinPadMapSERCOM0AltPad2,
+	PC22 / 2: pinPadMapSERCOM1Pad0 | pinPadMapSERCOM3AltPad1,
+	PD20 / 2: pinPadMapSERCOM1Pad2 | pinPadMapSERCOM3AltPad2,
 	PB16 / 2: pinPadMapSERCOM5Pad0 | 0,
 	PB18 / 2: pinPadMapSERCOM5Pad2 | pinPadMapSERCOM7AltPad2,
 
@@ -306,7 +306,7 @@ var pinPadMapping = [32]uint16{
 	PB24 / 2: pinPadMapSERCOM0Pad0 | pinPadMapSERCOM2AltPad1,
 	PB26 / 2: pinPadMapSERCOM2Pad0 | pinPadMapSERCOM4AltPad1,
 	PB28 / 2: pinPadMapSERCOM2Pad2 | pinPadMapSERCOM4AltPad2,
-	//PC24 / 2: pinPadMapSERCOM0Pad2 | pinPadMapSERCOM2AltPad2,
+	PC24 / 2: pinPadMapSERCOM0Pad2 | pinPadMapSERCOM2AltPad2,
 	//PC26 / 2: pinPadMapSERCOM1Pad1 | 0, // note: PC26 doesn't support SERCOM, but PC27 does
 	//PC28 / 2: pinPadMapSERCOM1Pad1 | 0, // note: PC29 doesn't exist in the datasheet?
 	PA30 / 2: 0 | pinPadMapSERCOM1AltPad2,

From 0c880ec44c3199cebbdf3611eba331d5b683afdd Mon Sep 17 00:00:00 2001
From: sago35 
Date: Fri, 5 Jun 2020 15:14:31 +0900
Subject: [PATCH 66/82] Standardize SAMD51 UART settings (#1155)

* machine/samd51: standardize samd51 uart settings
---
 src/machine/board_feather-m4.go               | 22 +-------
 src/machine/board_feather-m4_baremetal.go     | 43 +++++++++++++++
 src/machine/board_itsybitsy-m4.go             | 25 +--------
 src/machine/board_itsybitsy-m4_baremetal.go   | 43 +++++++++++++++
 src/machine/board_metro-m4-airlift.go         | 37 ++-----------
 .../board_metro-m4-airlift_baremetal.go       | 52 +++++++++++++++++++
 src/machine/board_pybadge.go                  | 33 +-----------
 src/machine/board_pybadge_baremetal.go        | 51 ++++++++++++++++++
 src/machine/board_pyportal.go                 | 29 ++---------
 src/machine/board_pyportal_baremetal.go       | 37 +++++++++++++
 src/machine/machine_atsamd51.go               | 20 -------
 11 files changed, 238 insertions(+), 154 deletions(-)
 create mode 100644 src/machine/board_feather-m4_baremetal.go
 create mode 100644 src/machine/board_itsybitsy-m4_baremetal.go
 create mode 100644 src/machine/board_metro-m4-airlift_baremetal.go
 create mode 100644 src/machine/board_pybadge_baremetal.go
 create mode 100644 src/machine/board_pyportal_baremetal.go

diff --git a/src/machine/board_feather-m4.go b/src/machine/board_feather-m4.go
index fee8c2e4ad..511834e23d 100644
--- a/src/machine/board_feather-m4.go
+++ b/src/machine/board_feather-m4.go
@@ -1,9 +1,7 @@
-// +build sam,atsamd51,feather_m4
+// +build feather_m4
 
 package machine
 
-import "device/sam"
-
 // used to reset into bootloader
 const RESET_MAGIC_VALUE = 0xf01669ef
 
@@ -47,13 +45,11 @@ const (
 	USBCDC_DP_PIN = PA25
 )
 
-// UART1 pins
 const (
 	UART_TX_PIN = D1
 	UART_RX_PIN = D0
 )
 
-// UART2 pins
 const (
 	UART2_TX_PIN = A4
 	UART2_RX_PIN = A5
@@ -65,14 +61,6 @@ const (
 	SCL_PIN = D21 // SCL: SERCOM2/PAD[1]
 )
 
-// I2C on the Feather M4.
-var (
-	I2C0 = I2C{
-		Bus:    sam.SERCOM2_I2CM,
-		SERCOM: 2,
-	}
-)
-
 // SPI pins
 const (
 	SPI0_SCK_PIN  = D25 // SCK: SERCOM1/PAD[1]
@@ -80,14 +68,6 @@ const (
 	SPI0_MISO_PIN = D23 // MISO: SERCOM1/PAD[2]
 )
 
-// SPI on the Feather M4.
-var (
-	SPI0 = SPI{
-		Bus:    sam.SERCOM1_SPIM,
-		SERCOM: 1,
-	}
-)
-
 // USB CDC identifiers
 const (
 	usb_STRING_PRODUCT      = "Adafruit Feather M4"
diff --git a/src/machine/board_feather-m4_baremetal.go b/src/machine/board_feather-m4_baremetal.go
new file mode 100644
index 0000000000..8492173994
--- /dev/null
+++ b/src/machine/board_feather-m4_baremetal.go
@@ -0,0 +1,43 @@
+// +build sam,atsamd51,feather_m4
+
+package machine
+
+import (
+	"device/sam"
+	"runtime/interrupt"
+)
+
+var (
+	UART1 = UART{
+		Buffer: NewRingBuffer(),
+		Bus:    sam.SERCOM5_USART_INT,
+		SERCOM: 5,
+	}
+
+	UART2 = UART{
+		Buffer: NewRingBuffer(),
+		Bus:    sam.SERCOM0_USART_INT,
+		SERCOM: 0,
+	}
+)
+
+func init() {
+	UART1.Interrupt = interrupt.New(sam.IRQ_SERCOM5_2, UART1.handleInterrupt)
+	UART2.Interrupt = interrupt.New(sam.IRQ_SERCOM0_2, UART2.handleInterrupt)
+}
+
+// I2C on the Feather M4.
+var (
+	I2C0 = I2C{
+		Bus:    sam.SERCOM2_I2CM,
+		SERCOM: 2,
+	}
+)
+
+// SPI on the Feather M4.
+var (
+	SPI0 = SPI{
+		Bus:    sam.SERCOM1_SPIM,
+		SERCOM: 1,
+	}
+)
diff --git a/src/machine/board_itsybitsy-m4.go b/src/machine/board_itsybitsy-m4.go
index c6d3390b0d..43c9a4ba50 100644
--- a/src/machine/board_itsybitsy-m4.go
+++ b/src/machine/board_itsybitsy-m4.go
@@ -1,9 +1,7 @@
-// +build sam,atsamd51,itsybitsy_m4
+// +build itsybitsy_m4
 
 package machine
 
-import "device/sam"
-
 // used to reset into bootloader
 const RESET_MAGIC_VALUE = 0xf01669ef
 
@@ -51,30 +49,17 @@ const (
 	UART_RX_PIN = D0
 )
 
-// UART1 var is on SERCOM3, defined in atsamd51.go
-
-// UART2 pins
 const (
 	UART2_TX_PIN = A4
 	UART2_RX_PIN = D2
 )
 
-// UART2 var is on SERCOM0, defined in atsamd51.go
-
 // I2C pins
 const (
 	SDA_PIN = PA12 // SDA: SERCOM2/PAD[0]
 	SCL_PIN = PA13 // SCL: SERCOM2/PAD[1]
 )
 
-// I2C on the ItsyBitsy M4.
-var (
-	I2C0 = I2C{
-		Bus:    sam.SERCOM2_I2CM,
-		SERCOM: 2,
-	}
-)
-
 // SPI pins
 const (
 	SPI0_SCK_PIN  = PA01 // SCK: SERCOM1/PAD[1]
@@ -82,14 +67,6 @@ const (
 	SPI0_MISO_PIN = PB23 // MISO: SERCOM1/PAD[3]
 )
 
-// SPI on the ItsyBitsy M4.
-var (
-	SPI0 = SPI{
-		Bus:    sam.SERCOM1_SPIM,
-		SERCOM: 1,
-	}
-)
-
 // USB CDC identifiers
 const (
 	usb_STRING_PRODUCT      = "Adafruit ItsyBitsy M4"
diff --git a/src/machine/board_itsybitsy-m4_baremetal.go b/src/machine/board_itsybitsy-m4_baremetal.go
new file mode 100644
index 0000000000..88097bca85
--- /dev/null
+++ b/src/machine/board_itsybitsy-m4_baremetal.go
@@ -0,0 +1,43 @@
+// +build sam,atsamd51,itsybitsy_m4
+
+package machine
+
+import (
+	"device/sam"
+	"runtime/interrupt"
+)
+
+var (
+	UART1 = UART{
+		Buffer: NewRingBuffer(),
+		Bus:    sam.SERCOM3_USART_INT,
+		SERCOM: 3,
+	}
+
+	UART2 = UART{
+		Buffer: NewRingBuffer(),
+		Bus:    sam.SERCOM0_USART_INT,
+		SERCOM: 0,
+	}
+)
+
+func init() {
+	UART1.Interrupt = interrupt.New(sam.IRQ_SERCOM3_2, UART1.handleInterrupt)
+	UART2.Interrupt = interrupt.New(sam.IRQ_SERCOM0_2, UART2.handleInterrupt)
+}
+
+// I2C on the ItsyBitsy M4.
+var (
+	I2C0 = I2C{
+		Bus:    sam.SERCOM2_I2CM,
+		SERCOM: 2,
+	}
+)
+
+// SPI on the ItsyBitsy M4.
+var (
+	SPI0 = SPI{
+		Bus:    sam.SERCOM1_SPIM,
+		SERCOM: 1,
+	}
+)
diff --git a/src/machine/board_metro-m4-airlift.go b/src/machine/board_metro-m4-airlift.go
index a62c4c2443..c1f49a1906 100644
--- a/src/machine/board_metro-m4-airlift.go
+++ b/src/machine/board_metro-m4-airlift.go
@@ -1,9 +1,7 @@
-// +build sam,atsamd51,metro_m4_airlift
+// +build metro_m4_airlift
 
 package machine
 
-import "device/sam"
-
 // used to reset into bootloader
 const RESET_MAGIC_VALUE = 0xf01669ef
 
@@ -53,7 +51,10 @@ const (
 	UART_RX_PIN = D0
 )
 
-// Note: UART1 is on SERCOM3, defined in machine_atsamd51.go
+const (
+	UART2_TX_PIN = PA04
+	UART2_RX_PIN = PA07
+)
 
 const (
 	NINA_CS     = PA15
@@ -66,23 +67,12 @@ const (
 	NINA_RTS = PB23
 )
 
-// UART2 is on SERCOM0,  defined in machine_atsamd51.go, and connects to the
-// onboard ESP32-WROOM chip.
-
 // I2C pins
 const (
 	SDA_PIN = PB02 // SDA: SERCOM5/PAD[0]
 	SCL_PIN = PB03 // SCL: SERCOM5/PAD[1]
 )
 
-// I2C on the Metro M4.
-var (
-	I2C0 = I2C{
-		Bus:    sam.SERCOM5_I2CM,
-		SERCOM: 5,
-	}
-)
-
 // SPI pins
 const (
 	SPI0_SCK_PIN  = PA13 // SCK:  SERCOM2/PAD[1]
@@ -94,29 +84,12 @@ const (
 	NINA_SCK  = SPI0_SCK_PIN
 )
 
-// SPI on the Metro M4.
-var (
-	SPI0 = SPI{
-		Bus:    sam.SERCOM2_SPIM,
-		SERCOM: 2,
-	}
-	NINA_SPI = SPI0
-)
-
 const (
 	SPI1_SCK_PIN  = D12 // MISO: SERCOM1/PAD[1]
 	SPI1_MOSI_PIN = D11 // MOSI: SERCOM1/PAD[3]
 	SPI1_MISO_PIN = D13 // SCK:  SERCOM1/PAD[0]
 )
 
-// SPI1 on the Metro M4 on pins 11,12,13
-var (
-	SPI1 = SPI{
-		Bus:    sam.SERCOM1_SPIM,
-		SERCOM: 1,
-	}
-)
-
 // USB CDC identifiers
 const (
 	usb_STRING_PRODUCT      = "Adafruit Metro M4 Airlift Lite"
diff --git a/src/machine/board_metro-m4-airlift_baremetal.go b/src/machine/board_metro-m4-airlift_baremetal.go
new file mode 100644
index 0000000000..b04f13cc3b
--- /dev/null
+++ b/src/machine/board_metro-m4-airlift_baremetal.go
@@ -0,0 +1,52 @@
+// +build sam,atsamd51,metro_m4_airlift
+
+package machine
+
+import (
+	"device/sam"
+	"runtime/interrupt"
+)
+
+var (
+	UART1 = UART{
+		Buffer: NewRingBuffer(),
+		Bus:    sam.SERCOM3_USART_INT,
+		SERCOM: 3,
+	}
+
+	UART2 = UART{
+		Buffer: NewRingBuffer(),
+		Bus:    sam.SERCOM0_USART_INT,
+		SERCOM: 0,
+	}
+)
+
+func init() {
+	UART1.Interrupt = interrupt.New(sam.IRQ_SERCOM3_2, UART1.handleInterrupt)
+	UART2.Interrupt = interrupt.New(sam.IRQ_SERCOM0_2, UART2.handleInterrupt)
+}
+
+// I2C on the Metro M4.
+var (
+	I2C0 = I2C{
+		Bus:    sam.SERCOM5_I2CM,
+		SERCOM: 5,
+	}
+)
+
+// SPI on the Metro M4.
+var (
+	SPI0 = SPI{
+		Bus:    sam.SERCOM2_SPIM,
+		SERCOM: 2,
+	}
+	NINA_SPI = SPI0
+)
+
+// SPI1 on the Metro M4 on pins 11,12,13
+var (
+	SPI1 = SPI{
+		Bus:    sam.SERCOM1_SPIM,
+		SERCOM: 1,
+	}
+)
diff --git a/src/machine/board_pybadge.go b/src/machine/board_pybadge.go
index 84ccb7ba8f..2b830197a7 100644
--- a/src/machine/board_pybadge.go
+++ b/src/machine/board_pybadge.go
@@ -1,9 +1,7 @@
-// +build sam,atsamd51,pybadge
+// +build pybadge
 
 package machine
 
-import "device/sam"
-
 // used to reset into bootloader
 const RESET_MAGIC_VALUE = 0xf01669ef
 
@@ -80,30 +78,17 @@ const (
 	UART_RX_PIN = D0
 )
 
-// UART1 var is on SERCOM3, defined in atsamd51.go
-
-// UART2 pins
 const (
 	UART2_TX_PIN = A4
 	UART2_RX_PIN = A5
 )
 
-// UART2 var is on SERCOM0, defined in atsamd51.go
-
 // I2C pins
 const (
 	SDA_PIN = PA12 // SDA: SERCOM2/PAD[0]
 	SCL_PIN = PA13 // SCL: SERCOM2/PAD[1]
 )
 
-// I2C on the ItsyBitsy M4.
-var (
-	I2C0 = I2C{
-		Bus:    sam.SERCOM2_I2CM,
-		SERCOM: 2,
-	}
-)
-
 // SPI pins
 const (
 	SPI0_SCK_PIN  = PA17 // SCK: SERCOM1/PAD[1]
@@ -111,14 +96,6 @@ const (
 	SPI0_MISO_PIN = PB22 // MISO: SERCOM1/PAD[2]
 )
 
-// SPI on the PyBadge.
-var (
-	SPI0 = SPI{
-		Bus:    sam.SERCOM1_SPIM,
-		SERCOM: 1,
-	}
-)
-
 // TFT SPI pins
 const (
 	SPI1_SCK_PIN  = PB13 // SCK: SERCOM4/PAD[1]
@@ -126,14 +103,6 @@ const (
 	SPI1_MISO_PIN = NoPin
 )
 
-// TFT SPI on the PyBadge.
-var (
-	SPI1 = SPI{
-		Bus:    sam.SERCOM4_SPIM,
-		SERCOM: 4,
-	}
-)
-
 // USB CDC identifiers
 const (
 	usb_STRING_PRODUCT      = "Adafruit pyBadge M4"
diff --git a/src/machine/board_pybadge_baremetal.go b/src/machine/board_pybadge_baremetal.go
new file mode 100644
index 0000000000..e3fd82e6f9
--- /dev/null
+++ b/src/machine/board_pybadge_baremetal.go
@@ -0,0 +1,51 @@
+// +build sam,atsamd51,pybadge
+
+package machine
+
+import (
+	"device/sam"
+	"runtime/interrupt"
+)
+
+var (
+	UART1 = UART{
+		Buffer: NewRingBuffer(),
+		Bus:    sam.SERCOM5_USART_INT,
+		SERCOM: 5,
+	}
+
+	UART2 = UART{
+		Buffer: NewRingBuffer(),
+		Bus:    sam.SERCOM0_USART_INT,
+		SERCOM: 0,
+	}
+)
+
+func init() {
+	UART1.Interrupt = interrupt.New(sam.IRQ_SERCOM5_2, UART1.handleInterrupt)
+	UART2.Interrupt = interrupt.New(sam.IRQ_SERCOM0_2, UART2.handleInterrupt)
+}
+
+// I2C on the ItsyBitsy M4.
+var (
+	I2C0 = I2C{
+		Bus:    sam.SERCOM2_I2CM,
+		SERCOM: 2,
+	}
+)
+
+// SPI on the PyBadge.
+var (
+	SPI0 = SPI{
+		Bus:    sam.SERCOM1_SPIM,
+		SERCOM: 1,
+	}
+)
+
+// TFT SPI on the PyBadge.
+var (
+	SPI1 = SPI{
+		Bus:    sam.SERCOM4_SPIM,
+		SERCOM: 4,
+	}
+)
diff --git a/src/machine/board_pyportal.go b/src/machine/board_pyportal.go
index 7000631ef1..2eaf0b880b 100644
--- a/src/machine/board_pyportal.go
+++ b/src/machine/board_pyportal.go
@@ -1,11 +1,7 @@
-// +build sam,atsamd51,pyportal
+// +build pyportal
 
 package machine
 
-import (
-	"device/sam"
-)
-
 // used to reset into bootloader
 const RESET_MAGIC_VALUE = 0xf01669ef
 
@@ -104,10 +100,10 @@ const (
 	USBCDC_DP_PIN = PA25
 )
 
-// TODO: add configuration for UART on SERCOM4 that is connected to TX/RX of ESP32
+// UART1 aka NINA_TX/NINA_RX
 const (
-	UART_TX_PIN = NoPin
-	UART_RX_PIN = NoPin
+	UART_TX_PIN = D1
+	UART_RX_PIN = D0
 )
 
 // I2C pins
@@ -116,14 +112,6 @@ const (
 	SCL_PIN = PB03 // SCL: SERCOM2/PAD[1]
 )
 
-// I2C on the PyPortal.
-var (
-	I2C0 = I2C{
-		Bus:    sam.SERCOM5_I2CM,
-		SERCOM: 5,
-	}
-)
-
 // SPI pins
 const (
 	SPI0_SCK_PIN  = PA13 // SCK: SERCOM1/PAD[1]
@@ -135,15 +123,6 @@ const (
 	NINA_SCK  = SPI0_SCK_PIN
 )
 
-// SPI on the PyPortal.
-var (
-	SPI0 = SPI{
-		Bus:    sam.SERCOM2_SPIM,
-		SERCOM: 2,
-	}
-	NINA_SPI = SPI0
-)
-
 // USB CDC identifiers
 const (
 	usb_STRING_PRODUCT      = "Adafruit PyPortal M4"
diff --git a/src/machine/board_pyportal_baremetal.go b/src/machine/board_pyportal_baremetal.go
new file mode 100644
index 0000000000..24ce6d38e1
--- /dev/null
+++ b/src/machine/board_pyportal_baremetal.go
@@ -0,0 +1,37 @@
+// +build sam,atsamd51,pyportal
+
+package machine
+
+import (
+	"device/sam"
+	"runtime/interrupt"
+)
+
+var (
+	UART1 = UART{
+		Buffer: NewRingBuffer(),
+		Bus:    sam.SERCOM4_USART_INT,
+		SERCOM: 4,
+	}
+)
+
+func init() {
+	UART1.Interrupt = interrupt.New(sam.IRQ_SERCOM4_2, UART1.handleInterrupt)
+}
+
+// I2C on the PyPortal.
+var (
+	I2C0 = I2C{
+		Bus:    sam.SERCOM5_I2CM,
+		SERCOM: 5,
+	}
+)
+
+// SPI on the PyPortal.
+var (
+	SPI0 = SPI{
+		Bus:    sam.SERCOM2_SPIM,
+		SERCOM: 2,
+	}
+	NINA_SPI = SPI0
+)
diff --git a/src/machine/machine_atsamd51.go b/src/machine/machine_atsamd51.go
index 042b90d9d6..7008590bab 100644
--- a/src/machine/machine_atsamd51.go
+++ b/src/machine/machine_atsamd51.go
@@ -903,28 +903,8 @@ type UART struct {
 var (
 	// UART0 is actually a USB CDC interface.
 	UART0 = USBCDC{Buffer: NewRingBuffer()}
-
-	// The first hardware serial port on the SAMD51. Uses the SERCOM3 interface.
-	UART1 = UART{
-		Buffer: NewRingBuffer(),
-		Bus:    sam.SERCOM3_USART_INT,
-		SERCOM: 3,
-	}
-
-	// The second hardware serial port on the SAMD51. Uses the SERCOM0 interface.
-	UART2 = UART{
-		Buffer: NewRingBuffer(),
-		Bus:    sam.SERCOM0_USART_INT,
-		SERCOM: 0,
-	}
 )
 
-func init() {
-	// Register RXC interrupts.
-	UART1.Interrupt = interrupt.New(sam.IRQ_SERCOM3_2, UART1.handleInterrupt)
-	UART2.Interrupt = interrupt.New(sam.IRQ_SERCOM0_2, UART2.handleInterrupt)
-}
-
 const (
 	sampleRate16X = 16
 	lsbFirst      = 1

From c5a896771d865fc77fac3e50c98e3d0ec98bad54 Mon Sep 17 00:00:00 2001
From: sago35 
Date: Sat, 6 Jun 2020 19:00:26 +0900
Subject: [PATCH 67/82] Seeed WioTerminal support (#1124)

* machine/wioterminal: add support for wioterminal board
---
 Makefile                                   |   2 +
 README.md                                  |   3 +-
 src/examples/pininterrupt/wioterminal.go   |  10 +
 src/machine/board_wioterminal.go           | 392 +++++++++++++++++++++
 src/machine/board_wioterminal_baremetal.go |  67 ++++
 src/machine/machine_atsamd51p19.go         |  58 +++
 src/runtime/runtime_atsamd51p19.go         |  53 +++
 targets/atsamd51p19a.json                  |  11 +
 targets/wioterminal.json                   |   8 +
 9 files changed, 603 insertions(+), 1 deletion(-)
 create mode 100644 src/examples/pininterrupt/wioterminal.go
 create mode 100644 src/machine/board_wioterminal.go
 create mode 100644 src/machine/board_wioterminal_baremetal.go
 create mode 100644 src/machine/machine_atsamd51p19.go
 create mode 100644 src/runtime/runtime_atsamd51p19.go
 create mode 100644 targets/atsamd51p19a.json
 create mode 100644 targets/wioterminal.json

diff --git a/Makefile b/Makefile
index 073b46ff28..d16d77f82c 100644
--- a/Makefile
+++ b/Makefile
@@ -293,6 +293,8 @@ smoketest:
 	@$(MD5SUM) test.hex
 	$(TINYGO) build -size short -o test.hex -target=reelboard-s140v7    examples/blinky1
 	@$(MD5SUM) test.hex
+	$(TINYGO) build -size short -o test.hex -target=wioterminal         examples/blinky1
+	@$(MD5SUM) test.hex
 ifneq ($(AVR), 0)
 	$(TINYGO) build -size short -o test.hex -target=atmega1284p         examples/serial
 	@$(MD5SUM) test.hex
diff --git a/README.md b/README.md
index 6424a42dc3..06002839ee 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 32 microcontroller boards are currently supported:
+The following 33 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)
@@ -72,6 +72,7 @@ The following 32 microcontroller boards are currently supported:
 * [Particle Xenon](https://docs.particle.io/datasheets/discontinued/xenon-datasheet/)
 * [Phytec reel board](https://www.phytec.eu/product-eu/internet-of-things/reelboard/)
 * [PineTime DevKit](https://www.pine64.org/pinetime/)
+* [Seeed Wio Terminal](https://www.seeedstudio.com/Wio-Terminal-p-4509.html)
 * [SiFIve HiFive1](https://www.sifive.com/boards/hifive1)
 * [ST Micro "Nucleo F103RB"](https://www.st.com/en/evaluation-tools/nucleo-f103rb.html)
 * [ST Micro STM32F103XX "Bluepill"](http://wiki.stm32duino.com/index.php?title=Blue_Pill)
diff --git a/src/examples/pininterrupt/wioterminal.go b/src/examples/pininterrupt/wioterminal.go
new file mode 100644
index 0000000000..a9fbfc8492
--- /dev/null
+++ b/src/examples/pininterrupt/wioterminal.go
@@ -0,0 +1,10 @@
+// +build wioterminal
+
+package main
+
+import "machine"
+
+const (
+	buttonMode      = machine.PinInput
+	buttonPinChange = machine.PinFalling
+)
diff --git a/src/machine/board_wioterminal.go b/src/machine/board_wioterminal.go
new file mode 100644
index 0000000000..14f0a494fa
--- /dev/null
+++ b/src/machine/board_wioterminal.go
@@ -0,0 +1,392 @@
+// +build wioterminal
+
+package machine
+
+// used to reset into bootloader
+const RESET_MAGIC_VALUE = 0xf01669ef
+
+const (
+	ADC0 = A0
+	ADC1 = A1
+	ADC2 = A2
+	ADC3 = A3
+	ADC4 = A4
+	ADC5 = A5
+	ADC6 = A6
+	ADC7 = A7
+	ADC8 = A8
+
+	LED    = PIN_LED
+	BUTTON = BUTTON_1
+)
+
+const (
+	// https://github.com/Seeed-Studio/ArduinoCore-samd/blob/master/variants/wio_terminal/variant.h
+
+	// LEDs
+	PIN_LED_13   = PA15
+	PIN_LED_RXL  = PA15
+	PIN_LED_TXL  = PA15
+	PIN_LED      = PIN_LED_13
+	PIN_LED2     = PIN_LED_RXL
+	PIN_LED3     = PIN_LED_TXL
+	LED_BUILTIN  = PIN_LED_13
+	PIN_NEOPIXEL = PA15
+
+	//Digital PINs
+	D0 = PB08
+	D1 = PB09
+	D2 = PA07
+	D3 = PB04
+	D4 = PB05
+	D5 = PB06
+	D6 = PA04
+	D7 = PB07
+	D8 = PA06
+
+	//Analog PINs
+	A0 = PB08 // ADC/AIN[0]
+	A1 = PB09 // ADC/AIN[2]
+	A2 = PA07 // ADC/AIN[3]
+	A3 = PB04 // ADC/AIN[4]
+	A4 = PB05 // ADC/AIN[5]
+	A5 = PB06 // ADC/AIN[10]
+	A6 = PA04 // ADC/AIN[10]
+	A7 = PB07 // ADC/AIN[10]
+	A8 = PA06 // ADC/AIN[10]
+
+	// 3.3V    ||    5V
+	// BCM2    ||    5V
+	// BCM3    ||    GND
+	// BCM4    ||    BCM14
+	// GND     ||    BCM15
+	// BCM17   ||    BCM18
+	// BCM27   ||    GND
+	// BCM22   ||    BCM23
+	// GND     ||    BCM24
+	// BCM10   ||    GND
+	// BCM9    ||    BCM25
+	// BCM11   ||    BCM8
+	// GND     ||    BCM7
+	// BCM0    ||    BCM1
+	// BCM5    ||    GND
+	// BCM6    ||    BCM12
+	// BCM13   ||    GND
+	// BCM19   ||    BCM16
+	// BCM26   ||    BCM20
+	// GND     ||    BCM21
+
+	//PIN DEFINE FOR RPI
+	BCM0  = PA13 // I2C Wire1
+	BCM1  = PA12 // I2C Wire1
+	BCM2  = PA17 // I2C Wire2
+	BCM3  = PA16 // I2C Wire2
+	BCM4  = PB14 // GCLK
+	BCM5  = PB12 // GCLK
+	BCM6  = PB13 // GCLK
+	BCM7  = PA05 // DAC1
+	BCM8  = PB01 // SPI SS
+	BCM9  = PB00 // SPI MISO
+	BCM10 = PB02 // SPI MOSI
+	BCM11 = PB03 // SPI SCK
+	BCM12 = PB06
+	BCM13 = PA07
+	BCM14 = PB27 // UART Serial1
+	BCM15 = PB26 // UART Serial1
+	BCM16 = PB07
+	BCM17 = PA02 // DAC0
+	BCM18 = PB28 // FPC Digital & AD pins
+	BCM19 = PA20 // WIO_IR
+	BCM20 = PA21 // I2S SDO
+	BCM21 = PA22 // I2S SDI
+	BCM22 = PB09
+	BCM23 = PA07
+	BCM24 = PB04
+	BCM25 = PB05
+	BCM26 = PA06
+	BCM27 = PB08
+
+	// FPC NEW DEFINE
+	FPC1  = PB28 // FPC Digital & AD pins
+	FPC2  = PB17
+	FPC3  = PB29
+	FPC4  = PA14
+	FPC5  = PC01
+	FPC6  = PC02
+	FPC7  = PC03
+	FPC8  = PC04
+	FPC9  = PC31
+	FPC10 = PD00
+
+	// RPI Analog RPIs
+	RPI_A0 = PB08
+	RPI_A1 = PB09
+	RPI_A2 = PA07
+	RPI_A3 = PB04
+	RPI_A4 = PB05
+	RPI_A5 = PB06
+	RPI_A6 = PA04
+	RPI_A7 = PB07
+	RPI_A8 = PA06
+
+	PIN_DAC0 = PA02
+	PIN_DAC1 = PA05
+
+	DAC0 = PIN_DAC0
+	DAC1 = PIN_DAC1
+
+	// FPO Analog RPIs
+	//FPC_A7  = FPC_D7
+	//FPC_A8  = FPC_D8
+	//FPC_A9  = FPC_D9
+	//FPC_A11 = FPC_D11
+	//FPC_A12 = FPC_D12
+	//FPC_A13 = FPC_D13
+
+	// USB
+	PIN_USB_DM          = PA24
+	PIN_USB_DP          = PA25
+	PIN_USB_HOST_ENABLE = PA27
+
+	// BUTTON
+	BUTTON_1  = PC26
+	BUTTON_2  = PC27
+	BUTTON_3  = PC28
+	WIO_KEY_A = PC26
+	WIO_KEY_B = PC27
+	WIO_KEY_C = PC28
+
+	// SWITCH
+	SWITCH_X = PD20
+	SWITCH_Y = PD12
+	SWITCH_Z = PD09
+	SWITCH_B = PD08
+	SWITCH_U = PD10
+
+	WIO_5S_UP    = PD20
+	WIO_5S_LEFT  = PD12
+	WIO_5S_RIGHT = PD09
+	WIO_5S_DOWN  = PD08
+	WIO_5S_PRESS = PD10
+
+	// IRQ0 : RTL8720D
+	IRQ0 = PC20
+
+	// BUZZER_CTR
+	BUZZER_CTR = PD11
+	WIO_BUZZER = PD11
+
+	// MIC_INPUT
+	MIC_INPUT = PC30
+	WIO_MIC   = PC30
+
+	// GCLK
+	GCLK0 = PB14
+	GCLK1 = PB12
+	GCLK2 = PB13
+
+	// Serial interfaces
+	// Serial1
+	PIN_SERIAL1_RX = PB27
+	PIN_SERIAL1_TX = PB26
+
+	// Serial2 : RTL8720D
+	PIN_SERIAL2_RX = PC23
+	PIN_SERIAL2_TX = PC22
+
+	// Wire Interfaces
+	// I2C Wire2
+	// I2C1
+	PIN_WIRE_SDA = PA17
+	PIN_WIRE_SCL = PA16
+	SDA          = PIN_WIRE_SDA
+	SCL          = PIN_WIRE_SCL
+
+	// I2C Wire1
+	// I2C0 : LIS3DHTR and ATECC608
+	PIN_WIRE1_SDA = PA13
+	PIN_WIRE1_SCL = PA12
+
+	SDA1 = PIN_WIRE1_SDA
+	SCL1 = PIN_WIRE1_SCL
+
+	PIN_GYROSCOPE_WIRE_SDA = PIN_WIRE1_SDA
+	PIN_GYROSCOPE_WIRE_SCL = PIN_WIRE1_SCL
+	GYROSCOPE_INT1         = PC21
+
+	WIO_LIS3DH_SDA = PIN_WIRE1_SDA
+	WIO_LIS3DH_SCL = PIN_WIRE1_SCL
+	WIO_LIS3DH_INT = PC21
+
+	// SPI
+	PIN_SPI_MISO = PB00
+	PIN_SPI_MOSI = PB02
+	PIN_SPI_SCK  = PB03
+	PIN_SPI_SS   = PB01
+
+	SS   = PIN_SPI_SS
+	MOSI = PIN_SPI_MOSI
+	MISO = PIN_SPI_MISO
+	SCK  = PIN_SPI_SCK
+
+	// SPI1 RTL8720D_SPI
+	PIN_SPI1_MISO = PC24
+	PIN_SPI1_MOSI = PB24
+	PIN_SPI1_SCK  = PB25
+	PIN_SPI1_SS   = PC25
+
+	SS1   = PIN_SPI1_SS
+	MOSI1 = PIN_SPI1_MOSI
+	MISO1 = PIN_SPI1_MISO
+	SCK1  = PIN_SPI1_SCK
+
+	// SPI2 SD_SPI
+	PIN_SPI2_MISO = PC18
+	PIN_SPI2_MOSI = PC16
+	PIN_SPI2_SCK  = PC17
+	PIN_SPI2_SS   = PC19
+
+	SS2   = PIN_SPI2_SS
+	MOSI2 = PIN_SPI2_MOSI
+	MISO2 = PIN_SPI2_MISO
+	SCK2  = PIN_SPI2_SCK
+
+	// SPI3 LCD_SPI
+	PIN_SPI3_MISO = PB18
+	PIN_SPI3_MOSI = PB19
+	PIN_SPI3_SCK  = PB20
+	PIN_SPI3_SS   = PB21
+
+	SS3   = PIN_SPI3_SS
+	MOSI3 = PIN_SPI3_MOSI
+	MISO3 = PIN_SPI3_MISO
+	SCK3  = PIN_SPI3_SCK
+
+	// Needed for SD library
+	SDCARD_MISO_PIN = PIN_SPI2_MISO
+	SDCARD_MOSI_PIN = PIN_SPI2_MOSI
+	SDCARD_SCK_PIN  = PIN_SPI2_SCK
+	SDCARD_SS_PIN   = PIN_SPI2_SS
+	SDCARD_DET_PIN  = PD21
+
+	LCD_MISO_PIN  = PIN_SPI3_MISO
+	LCD_MOSI_PIN  = PIN_SPI3_MOSI
+	LCD_SCK_PIN   = PIN_SPI3_SCK
+	LCD_SS_PIN    = PIN_SPI3_SS
+	LCD_DC        = PC06
+	LCD_RESET     = PC07
+	LCD_BACKLIGHT = PC05
+
+	// 4 WIRE LCD TOUCH
+	LCD_XL = PC10
+	LCD_YU = PC11
+	LCD_XR = PC12
+	LCD_YD = PC13
+
+	// Needed for RTL8720D
+	RTL8720D_MISO_PIN = PIN_SPI1_MISO
+	RTL8720D_MOSI_PIN = PIN_SPI1_MOSI
+	RTL8720D_SCK_PIN  = PIN_SPI1_SCK
+	RTL8720D_SS_PIN   = PIN_SPI1_SS
+
+	//QSPI Pins
+	PIN_QSPI_IO0 = PA08
+	PIN_QSPI_IO1 = PA09
+	PIN_QSPI_IO2 = PA10
+	PIN_QSPI_IO3 = PA11
+	PIN_QSPI_SCK = PB10
+	PIN_QSPI_CS  = PB11
+
+	// I2S Interfaces
+	PIN_I2S_FS  = PA20
+	PIN_I2S_SCK = PB16
+	PIN_I2S_SDO = PA22
+	PIN_I2S_SDI = PA21
+
+	I2S_LRCLK = PA20
+	I2S_BLCK  = PB16
+	I2S_SDOUT = PA22
+	I2S_SDIN  = PA21
+
+	// RTL8720D Interfaces
+	RTL8720D_CHIP_PU = PA18
+	RTL8720D_GPIO0   = PA19 // SYNC
+
+	// SWD
+	SWDCLK = PA30
+	SWDIO  = PA31
+	SWO    = PB30
+
+	// light sensor
+	WIO_LIGHT = PD01
+
+	// ir sensor
+	WIO_IR = PB31
+
+	// OUTPUT_CTR
+	OUTPUT_CTR_5V  = PC14
+	OUTPUT_CTR_3V3 = PC15
+)
+
+// UART0 aka USBCDC pins
+const (
+	USBCDC_DM_PIN = PIN_USB_DM
+	USBCDC_DP_PIN = PIN_USB_DP
+)
+
+// UART1 pins
+const (
+	UART_TX_PIN = PIN_SERIAL1_TX
+	UART_RX_PIN = PIN_SERIAL1_RX
+)
+
+// UART2 pins RTL8720D
+const (
+	UART2_TX_PIN = PIN_SERIAL2_TX
+	UART2_RX_PIN = PIN_SERIAL2_RX
+)
+
+// I2C pins
+const (
+	SDA0_PIN = PIN_WIRE_SDA // SDA: SERCOM3/PAD[0]
+	SCL0_PIN = PIN_WIRE_SCL // SCL: SERCOM3/PAD[1]
+
+	SDA1_PIN = PIN_WIRE1_SDA // SDA: SERCOM4/PAD[0]
+	SCL1_PIN = PIN_WIRE1_SCL // SCL: SERCOM4/PAD[1]
+
+	SDA_PIN = SDA0_PIN
+	SCL_PIN = SCL0_PIN
+)
+
+// SPI pins
+const (
+	SPI0_SCK_PIN  = SCK  // SCK:  SERCOM5/PAD[1]
+	SPI0_MOSI_PIN = MOSI // MOSI: SERCOM5/PAD[0]
+	SPI0_MISO_PIN = MISO // MISO: SERCOM5/PAD[2]
+
+	// RTL8720D
+	SPI1_SCK_PIN  = SCK1  // SCK:  SERCOM0/PAD[1]
+	SPI1_MOSI_PIN = MOSI1 // MOSI: SERCOM0/PAD[0]
+	SPI1_MISO_PIN = MISO1 // MISO: SERCOM0/PAD[2]
+
+	// SD
+	SPI2_SCK_PIN  = SCK2  // SCK:  SERCOM6/PAD[1]
+	SPI2_MOSI_PIN = MOSI2 // MOSI: SERCOM6/PAD[0]
+	SPI2_MISO_PIN = MISO2 // MISO: SERCOM6/PAD[2]
+
+	// LCD
+	SPI3_SCK_PIN  = SCK3  // SCK:  SERCOM7/PAD[1]
+	SPI3_MOSI_PIN = MOSI3 // MOSI: SERCOM7/PAD[3]
+	SPI3_MISO_PIN = MISO3 // MISO: SERCOM7/PAD[2]
+)
+
+// USB CDC identifiers
+const (
+	usb_STRING_PRODUCT      = "Seeed Wio Terminal"
+	usb_STRING_MANUFACTURER = "Seeed"
+)
+
+var (
+	usb_VID uint16 = 0x2886
+	usb_PID uint16 = 0x802D
+)
diff --git a/src/machine/board_wioterminal_baremetal.go b/src/machine/board_wioterminal_baremetal.go
new file mode 100644
index 0000000000..8fbc025dce
--- /dev/null
+++ b/src/machine/board_wioterminal_baremetal.go
@@ -0,0 +1,67 @@
+// +build sam,atsamd51,wioterminal
+
+package machine
+
+import (
+	"device/sam"
+	"runtime/interrupt"
+)
+
+var (
+	UART1 = UART{
+		Buffer: NewRingBuffer(),
+		Bus:    sam.SERCOM2_USART_INT,
+		SERCOM: 2,
+	}
+
+	// RTL8720D
+	UART2 = UART{
+		Buffer: NewRingBuffer(),
+		Bus:    sam.SERCOM1_USART_INT,
+		SERCOM: 1,
+	}
+)
+
+func init() {
+	UART1.Interrupt = interrupt.New(sam.IRQ_SERCOM2_2, UART1.handleInterrupt)
+	UART2.Interrupt = interrupt.New(sam.IRQ_SERCOM1_2, UART2.handleInterrupt)
+}
+
+// I2C on the Wio Terminal
+var (
+	I2C0 = I2C{
+		Bus:    sam.SERCOM4_I2CM,
+		SERCOM: 4,
+	}
+
+	I2C1 = I2C{
+		Bus:    sam.SERCOM4_I2CM,
+		SERCOM: 4,
+	}
+)
+
+// SPI on the Wio Terminal
+var (
+	SPI0 = SPI{
+		Bus:    sam.SERCOM5_SPIM,
+		SERCOM: 5,
+	}
+
+	// RTL8720D
+	SPI1 = SPI{
+		Bus:    sam.SERCOM0_SPIM,
+		SERCOM: 0,
+	}
+
+	// SD
+	SPI2 = SPI{
+		Bus:    sam.SERCOM6_SPIM,
+		SERCOM: 6,
+	}
+
+	// LCD
+	SPI3 = SPI{
+		Bus:    sam.SERCOM7_SPIM,
+		SERCOM: 7,
+	}
+)
diff --git a/src/machine/machine_atsamd51p19.go b/src/machine/machine_atsamd51p19.go
new file mode 100644
index 0000000000..2bb5940814
--- /dev/null
+++ b/src/machine/machine_atsamd51p19.go
@@ -0,0 +1,58 @@
+// +build sam,atsamd51,atsamd51p19
+
+// Peripheral abstraction layer for the atsamd51.
+//
+// Datasheet:
+// http://ww1.microchip.com/downloads/en/DeviceDoc/60001507C.pdf
+//
+package machine
+
+import "device/sam"
+
+const HSRAM_SIZE = 0x00030000
+
+// InitPWM initializes the PWM interface.
+func InitPWM() {
+	// turn on timer clocks used for PWM
+	sam.MCLK.APBBMASK.SetBits(sam.MCLK_APBBMASK_TCC0_ | sam.MCLK_APBBMASK_TCC1_)
+	sam.MCLK.APBCMASK.SetBits(sam.MCLK_APBCMASK_TCC2_)
+	sam.MCLK.APBDMASK.SetBits(sam.MCLK_APBDMASK_TCC4_)
+
+	//use clock generator 0
+	sam.GCLK.PCHCTRL[25].Set((sam.GCLK_PCHCTRL_GEN_GCLK0 << sam.GCLK_PCHCTRL_GEN_Pos) |
+		sam.GCLK_PCHCTRL_CHEN)
+	sam.GCLK.PCHCTRL[29].Set((sam.GCLK_PCHCTRL_GEN_GCLK0 << sam.GCLK_PCHCTRL_GEN_Pos) |
+		sam.GCLK_PCHCTRL_CHEN)
+	sam.GCLK.PCHCTRL[38].Set((sam.GCLK_PCHCTRL_GEN_GCLK0 << sam.GCLK_PCHCTRL_GEN_Pos) |
+		sam.GCLK_PCHCTRL_CHEN)
+}
+
+// getTimer returns the timer to be used for PWM on this pin
+func (pwm PWM) getTimer() *sam.TCC_Type {
+	switch pwm.Pin {
+	case PA16:
+		return sam.TCC1
+	case PA17:
+		return sam.TCC1
+	case PA14:
+		return sam.TCC2
+	case PA15:
+		return sam.TCC2
+	case PA18:
+		return sam.TCC1
+	case PA19:
+		return sam.TCC1
+	case PA20:
+		return sam.TCC0
+	case PA21:
+		return sam.TCC0
+	case PA23:
+		return sam.TCC0
+	case PA22:
+		return sam.TCC0
+	case PB31:
+		return sam.TCC4
+	default:
+		return nil // not supported on this pin
+	}
+}
diff --git a/src/runtime/runtime_atsamd51p19.go b/src/runtime/runtime_atsamd51p19.go
new file mode 100644
index 0000000000..8821147218
--- /dev/null
+++ b/src/runtime/runtime_atsamd51p19.go
@@ -0,0 +1,53 @@
+// +build sam,atsamd51,atsamd51p19
+
+package runtime
+
+import (
+	"device/sam"
+)
+
+func initSERCOMClocks() {
+	// Turn on clock to SERCOM0 for UART0
+	sam.MCLK.APBAMASK.SetBits(sam.MCLK_APBAMASK_SERCOM0_)
+	sam.GCLK.PCHCTRL[7].Set((sam.GCLK_PCHCTRL_GEN_GCLK1 << sam.GCLK_PCHCTRL_GEN_Pos) |
+		sam.GCLK_PCHCTRL_CHEN)
+
+	// sets the "slow" clock shared by all SERCOM
+	sam.GCLK.PCHCTRL[3].Set((sam.GCLK_PCHCTRL_GEN_GCLK1 << sam.GCLK_PCHCTRL_GEN_Pos) |
+		sam.GCLK_PCHCTRL_CHEN)
+
+	// Turn on clock to SERCOM1
+	sam.MCLK.APBAMASK.SetBits(sam.MCLK_APBAMASK_SERCOM1_)
+	sam.GCLK.PCHCTRL[8].Set((sam.GCLK_PCHCTRL_GEN_GCLK1 << sam.GCLK_PCHCTRL_GEN_Pos) |
+		sam.GCLK_PCHCTRL_CHEN)
+
+	// Turn on clock to SERCOM2
+	sam.MCLK.APBBMASK.SetBits(sam.MCLK_APBBMASK_SERCOM2_)
+	sam.GCLK.PCHCTRL[23].Set((sam.GCLK_PCHCTRL_GEN_GCLK1 << sam.GCLK_PCHCTRL_GEN_Pos) |
+		sam.GCLK_PCHCTRL_CHEN)
+
+	// Turn on clock to SERCOM3
+	sam.MCLK.APBBMASK.SetBits(sam.MCLK_APBBMASK_SERCOM3_)
+	sam.GCLK.PCHCTRL[24].Set((sam.GCLK_PCHCTRL_GEN_GCLK1 << sam.GCLK_PCHCTRL_GEN_Pos) |
+		sam.GCLK_PCHCTRL_CHEN)
+
+	// Turn on clock to SERCOM4
+	sam.MCLK.APBDMASK.SetBits(sam.MCLK_APBDMASK_SERCOM4_)
+	sam.GCLK.PCHCTRL[34].Set((sam.GCLK_PCHCTRL_GEN_GCLK1 << sam.GCLK_PCHCTRL_GEN_Pos) |
+		sam.GCLK_PCHCTRL_CHEN)
+
+	// Turn on clock to SERCOM5
+	sam.MCLK.APBDMASK.SetBits(sam.MCLK_APBDMASK_SERCOM5_)
+	sam.GCLK.PCHCTRL[35].Set((sam.GCLK_PCHCTRL_GEN_GCLK1 << sam.GCLK_PCHCTRL_GEN_Pos) |
+		sam.GCLK_PCHCTRL_CHEN)
+
+	// Turn on clock to SERCOM6
+	sam.MCLK.APBDMASK.SetBits(sam.MCLK_APBDMASK_SERCOM6_)
+	sam.GCLK.PCHCTRL[36].Set((sam.GCLK_PCHCTRL_GEN_GCLK1 << sam.GCLK_PCHCTRL_GEN_Pos) |
+		sam.GCLK_PCHCTRL_CHEN)
+
+	// Turn on clock to SERCOM7
+	sam.MCLK.APBDMASK.SetBits(sam.MCLK_APBDMASK_SERCOM7_)
+	sam.GCLK.PCHCTRL[37].Set((sam.GCLK_PCHCTRL_GEN_GCLK1 << sam.GCLK_PCHCTRL_GEN_Pos) |
+		sam.GCLK_PCHCTRL_CHEN)
+}
diff --git a/targets/atsamd51p19a.json b/targets/atsamd51p19a.json
new file mode 100644
index 0000000000..23c1777ca6
--- /dev/null
+++ b/targets/atsamd51p19a.json
@@ -0,0 +1,11 @@
+{
+	"inherits": ["cortex-m4"],
+	"build-tags": ["atsamd51p19", "atsamd51", "sam"],
+	"cflags": [
+		"-Qunused-arguments"
+	],
+	"linkerscript": "targets/atsamd51.ld",
+	"extra-files": [
+		"src/device/sam/atsamd51p19a.s"
+	]
+}
diff --git a/targets/wioterminal.json b/targets/wioterminal.json
new file mode 100644
index 0000000000..04a80dda88
--- /dev/null
+++ b/targets/wioterminal.json
@@ -0,0 +1,8 @@
+{
+    "inherits": ["atsamd51p19a"],
+    "build-tags": ["sam", "atsamd51p19a", "wioterminal"],
+    "flash-1200-bps-reset": "true",
+    "flash-method": "msd",
+    "msd-volume-name": "Arduino",
+    "msd-firmware-name": "firmware.uf2"
+}

From d61d5d7ab1beeb7d2ecace8c510ec6bdaece88d8 Mon Sep 17 00:00:00 2001
From: Yannis Huber 
Date: Sun, 7 Jun 2020 11:23:14 +0200
Subject: [PATCH 68/82] Zero PLIC threshold value at startup

The PLIC threshold value must be zeroed at startup to permit
all interrupt priorities. Fixes #1128.
---
 src/runtime/runtime_fe310.go | 3 +++
 1 file changed, 3 insertions(+)

diff --git a/src/runtime/runtime_fe310.go b/src/runtime/runtime_fe310.go
index de0d7ec465..437f03bf58 100644
--- a/src/runtime/runtime_fe310.go
+++ b/src/runtime/runtime_fe310.go
@@ -24,6 +24,9 @@ func main() {
 	sifive.PLIC.ENABLE[0].Set(0)
 	sifive.PLIC.ENABLE[1].Set(0)
 
+	// Zero the threshold value to allow all priorities of interrupts.
+	sifive.PLIC.THRESHOLD.Set(0)
+
 	// Set the interrupt address.
 	// Note that this address must be aligned specially, otherwise the MODE bits
 	// of MTVEC won't be zero.

From 2396c22658275c3df83ac24453e81fb5d252f5e6 Mon Sep 17 00:00:00 2001
From: Yannis Huber <32066446+yannishuber@users.noreply.github.com>
Date: Mon, 8 Jun 2020 16:47:39 +0200
Subject: [PATCH 69/82] risc-v: add support for 64-bit RISC-V CPUs

---
 src/runtime/volatile/register.go       | 64 +++++++++++++++++++++++++-
 src/runtime/volatile/volatile.go       |  6 +++
 targets/fe310.json                     |  2 +-
 targets/riscv-qemu.json                |  2 +-
 targets/riscv.json                     |  5 --
 targets/riscv32.json                   | 12 +++++
 targets/riscv64.json                   | 13 ++++++
 tools/gen-device-svd/gen-device-svd.go |  4 ++
 8 files changed, 100 insertions(+), 8 deletions(-)
 create mode 100644 targets/riscv32.json
 create mode 100644 targets/riscv64.json

diff --git a/src/runtime/volatile/register.go b/src/runtime/volatile/register.go
index 5be97e7fde..adfe372eee 100644
--- a/src/runtime/volatile/register.go
+++ b/src/runtime/volatile/register.go
@@ -1,6 +1,6 @@
 package volatile
 
-// This file defines Register{8,16,32} types, which are convenience types for
+// This file defines Register{8,16,32,64} types, which are convenience types for
 // volatile register accesses.
 
 // Special types that causes loads/stores to be volatile (necessary for
@@ -190,3 +190,65 @@ func (r *Register32) HasBits(value uint32) bool {
 func (r *Register32) ReplaceBits(value uint32, mask uint32, pos uint8) {
 	StoreUint32(&r.Reg, LoadUint32(&r.Reg)&^(mask< 0
+//
+//go:inline
+func (r *Register64) HasBits(value uint64) bool {
+	return (r.Get() & value) > 0
+}
+
+// ReplaceBits is a helper to simplify setting multiple bits high and/or low at
+// once. It is the volatile equivalent of:
+//
+//     r.Reg = (r.Reg & ^(mask << pos)) | value << pos
+//
+// go:inline
+func (r *Register64) ReplaceBits(value uint64, mask uint64, pos uint8) {
+	StoreUint64(&r.Reg, LoadUint64(&r.Reg)&^(mask<
Date: Mon, 8 Jun 2020 16:50:39 +0200
Subject: [PATCH 70/82] compiler: add support for custom code model

---
 compileopts/config.go |  9 +++++++++
 compileopts/target.go |  4 ++++
 compiler/compiler.go  | 20 +++++++++++++++++++-
 3 files changed, 32 insertions(+), 1 deletion(-)

diff --git a/compileopts/config.go b/compileopts/config.go
index f9fc01fe4b..24a12a9f33 100644
--- a/compileopts/config.go
+++ b/compileopts/config.go
@@ -272,6 +272,15 @@ func (c *Config) OpenOCDConfiguration() (args []string, err error) {
 	return args, nil
 }
 
+// CodeModel returns the code model used on this platform.
+func (c *Config) CodeModel() string {
+	if c.Target.CodeModel != "" {
+		return c.Target.CodeModel
+	}
+
+	return "default"
+}
+
 type TestConfig struct {
 	CompileTestBinary bool
 	// TODO: Filter the test functions to run, include verbose flag, etc
diff --git a/compileopts/target.go b/compileopts/target.go
index 2071c73a53..5d5e2f1b3d 100644
--- a/compileopts/target.go
+++ b/compileopts/target.go
@@ -49,6 +49,7 @@ type TargetSpec struct {
 	OpenOCDTarget    string   `json:"openocd-target"`
 	OpenOCDTransport string   `json:"openocd-transport"`
 	JLinkDevice      string   `json:"jlink-device"`
+	CodeModel        string   `json:"code-model"`
 }
 
 // copyProperties copies all properties that are set in spec2 into itself.
@@ -130,6 +131,9 @@ func (spec *TargetSpec) copyProperties(spec2 *TargetSpec) {
 	if spec2.JLinkDevice != "" {
 		spec.JLinkDevice = spec2.JLinkDevice
 	}
+	if spec2.CodeModel != "" {
+		spec.CodeModel = spec2.CodeModel
+	}
 }
 
 // load reads a target specification from the JSON in the given io.Reader. It
diff --git a/compiler/compiler.go b/compiler/compiler.go
index d977e1a068..677128d0e7 100644
--- a/compiler/compiler.go
+++ b/compiler/compiler.go
@@ -91,7 +91,25 @@ func NewTargetMachine(config *compileopts.Config) (llvm.TargetMachine, error) {
 		return llvm.TargetMachine{}, err
 	}
 	features := strings.Join(config.Features(), ",")
-	machine := target.CreateTargetMachine(config.Triple(), config.CPU(), features, llvm.CodeGenLevelDefault, llvm.RelocStatic, llvm.CodeModelDefault)
+
+	var codeModel llvm.CodeModel
+
+	switch config.CodeModel() {
+	case "default":
+		codeModel = llvm.CodeModelDefault
+	case "tiny":
+		codeModel = llvm.CodeModelTiny
+	case "small":
+		codeModel = llvm.CodeModelSmall
+	case "kernel":
+		codeModel = llvm.CodeModelKernel
+	case "medium":
+		codeModel = llvm.CodeModelMedium
+	case "large":
+		codeModel = llvm.CodeModelLarge
+	}
+
+	machine := target.CreateTargetMachine(config.Triple(), config.CPU(), features, llvm.CodeGenLevelDefault, llvm.RelocStatic, codeModel)
 	return machine, nil
 }
 

From e2c55e3d2657be8af55b1c80a96b8a1331c0eae3 Mon Sep 17 00:00:00 2001
From: Yannis Huber <32066446+yannishuber@users.noreply.github.com>
Date: Mon, 8 Jun 2020 16:59:13 +0200
Subject: [PATCH 71/82] gen-device-svd: fix lowercase cluster name

---
 tools/gen-device-svd/gen-device-svd.go | 5 +++++
 1 file changed, 5 insertions(+)

diff --git a/tools/gen-device-svd/gen-device-svd.go b/tools/gen-device-svd/gen-device-svd.go
index c32cf97346..e63b026a02 100755
--- a/tools/gen-device-svd/gen-device-svd.go
+++ b/tools/gen-device-svd/gen-device-svd.go
@@ -342,6 +342,11 @@ func readSVD(path, sourceURL string) (*Device, error) {
 				firstAddress := clusterRegisters[0].address
 				dimIncrement = int(lastAddress - firstAddress)
 			}
+
+			if !unicode.IsUpper(rune(clusterName[0])) && !unicode.IsDigit(rune(clusterName[0])) {
+				clusterName = strings.ToUpper(clusterName)
+			}
+
 			p.registers = append(p.registers, &PeripheralField{
 				name:        clusterName,
 				address:     baseAddress + clusterOffset,

From 9ed5eae6a939541b73dfcf2786cee3188ec6b125 Mon Sep 17 00:00:00 2001
From: Ayke van Laethem 
Date: Mon, 8 Jun 2020 16:23:01 +0200
Subject: [PATCH 72/82] cgo: use scanner.Error in libclang

Previously it would return a `*scanner.Error`, which is not supported in
the error printer of the main package. This can easily be fixed by
making it a regular object (instead of a pointer).
---
 cgo/libclang.go | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)

diff --git a/cgo/libclang.go b/cgo/libclang.go
index 3d7695b5e6..649494f120 100644
--- a/cgo/libclang.go
+++ b/cgo/libclang.go
@@ -246,9 +246,9 @@ func tinygo_clang_globals_visitor(c, parent C.GoCXCursor, client_data C.CXClient
 		}
 		value := source[len(name):]
 		// Try to convert this #define into a Go constant expression.
-		expr, err := parseConst(pos+token.Pos(len(name)), p.fset, value)
-		if err != nil {
-			p.errors = append(p.errors, err)
+		expr, scannerError := parseConst(pos+token.Pos(len(name)), p.fset, value)
+		if scannerError != nil {
+			p.errors = append(p.errors, *scannerError)
 		}
 		if expr != nil {
 			// Parsing was successful.

From 169d5f17b879a05145ce47ea4fb4af898b23b9d8 Mon Sep 17 00:00:00 2001
From: Ayke van Laethem 
Date: Mon, 8 Jun 2020 16:25:31 +0200
Subject: [PATCH 73/82] nrf: fix bug in SPI.Tx

There was what appears to be a race condition in the Tx function. While
it would work fine in many cases, when there were interrupts (such as
when using BLE), the function would just hang waiting for `EVENTS_READY`
to arrive.

I think what was happening was that the `spi.Bus.RXD.Get()` would start
the next transfer, which would complete (and generate an event) before
`EVENTS_READY` was reset to 0. The fix is easy: clear `EVENTS_READY`
before doing something that can trigger an event.

I believe I've seen this bug before on the PineTime but I couldn't find
the issue back then.
---
 src/machine/machine_nrf.go | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/src/machine/machine_nrf.go b/src/machine/machine_nrf.go
index 382dd05c20..3d65ad578d 100644
--- a/src/machine/machine_nrf.go
+++ b/src/machine/machine_nrf.go
@@ -443,13 +443,13 @@ func (spi SPI) Tx(w, r []byte) error {
 			spi.Bus.TXD.Set(uint32(b))
 			for spi.Bus.EVENTS_READY.Get() == 0 {
 			}
-			_ = spi.Bus.RXD.Get()
 			spi.Bus.EVENTS_READY.Set(0)
+			_ = spi.Bus.RXD.Get()
 		}
 		for spi.Bus.EVENTS_READY.Get() == 0 {
 		}
-		_ = spi.Bus.RXD.Get()
 		spi.Bus.EVENTS_READY.Set(0)
+		_ = spi.Bus.RXD.Get()
 
 	default:
 		// write/read

From 4e4b5955db2d9604ae82949e6cd06d5b92a15408 Mon Sep 17 00:00:00 2001
From: Ayke van Laethem 
Date: Mon, 8 Jun 2020 16:29:54 +0200
Subject: [PATCH 74/82] nrf: support debugging the PCA10056

This change adds support for `tinygo gdb` on the PCA10056.

The board is normally flashed with the MSD programmer. Debugging needs a
real debugger interface however, which is relatively simple by just
adding the right OpenOCD configuration.
---
 targets/pca10056.json | 4 +++-
 1 file changed, 3 insertions(+), 1 deletion(-)

diff --git a/targets/pca10056.json b/targets/pca10056.json
index 1e35e51e97..fc0acaa0a2 100644
--- a/targets/pca10056.json
+++ b/targets/pca10056.json
@@ -4,5 +4,7 @@
 	"flash-method": "command",
 	"flash-command": "nrfjprog -f nrf52 --sectorerase --program {hex} --reset",
 	"msd-volume-name": "JLINK",
-	"msd-firmware-name": "firmware.hex"
+	"msd-firmware-name": "firmware.hex",
+	"openocd-interface": "jlink",
+	"openocd-transport": "swd"
 }

From 43219236414d1b16db817e89f0f482af65e4d6b2 Mon Sep 17 00:00:00 2001
From: Jaden Weiss 
Date: Sat, 30 May 2020 09:04:28 -0400
Subject: [PATCH 75/82] compiler/runtime: move the channel blocked list onto
 the stack

Previously, chansend and chanrecv allocated a heap object before blocking on a channel.
This object was used to implement a linked list of goroutines blocked on the channel.
The chansend and chanrecv now instead accept a buffer to store this object in as an argument.
The compiler now creates a stack allocation for this object and passes it in.
---
 compiler/channel.go | 16 +++++++++++++---
 src/runtime/chan.go | 10 ++++++----
 2 files changed, 19 insertions(+), 7 deletions(-)

diff --git a/compiler/channel.go b/compiler/channel.go
index a2532e4898..6e2d4319dd 100644
--- a/compiler/channel.go
+++ b/compiler/channel.go
@@ -35,12 +35,17 @@ func (b *builder) createChanSend(instr *ssa.Send) {
 	valueAlloca, valueAllocaCast, valueAllocaSize := b.createTemporaryAlloca(valueType, "chan.value")
 	b.CreateStore(chanValue, valueAlloca)
 
+	// Allocate blockedlist buffer.
+	channelBlockedList := b.mod.GetTypeByName("runtime.channelBlockedList")
+	channelBlockedListAlloca, channelBlockedListAllocaCast, channelBlockedListAllocaSize := b.createTemporaryAlloca(channelBlockedList, "chan.blockedList")
+
 	// Do the send.
-	b.createRuntimeCall("chanSend", []llvm.Value{ch, valueAllocaCast}, "")
+	b.createRuntimeCall("chanSend", []llvm.Value{ch, valueAllocaCast, channelBlockedListAlloca}, "")
 
-	// End the lifetime of the alloca.
+	// End the lifetime of the allocas.
 	// This also works around a bug in CoroSplit, at least in LLVM 8:
 	// https://bugs.llvm.org/show_bug.cgi?id=41742
+	b.emitLifetimeEnd(channelBlockedListAllocaCast, channelBlockedListAllocaSize)
 	b.emitLifetimeEnd(valueAllocaCast, valueAllocaSize)
 }
 
@@ -53,9 +58,14 @@ func (b *builder) createChanRecv(unop *ssa.UnOp) llvm.Value {
 	// Allocate memory to receive into.
 	valueAlloca, valueAllocaCast, valueAllocaSize := b.createTemporaryAlloca(valueType, "chan.value")
 
+	// Allocate blockedlist buffer.
+	channelBlockedList := b.mod.GetTypeByName("runtime.channelBlockedList")
+	channelBlockedListAlloca, channelBlockedListAllocaCast, channelBlockedListAllocaSize := b.createTemporaryAlloca(channelBlockedList, "chan.blockedList")
+
 	// Do the receive.
-	commaOk := b.createRuntimeCall("chanRecv", []llvm.Value{ch, valueAllocaCast}, "")
+	commaOk := b.createRuntimeCall("chanRecv", []llvm.Value{ch, valueAllocaCast, channelBlockedListAlloca}, "")
 	received := b.CreateLoad(valueAlloca, "chan.received")
+	b.emitLifetimeEnd(channelBlockedListAllocaCast, channelBlockedListAllocaSize)
 	b.emitLifetimeEnd(valueAllocaCast, valueAllocaSize)
 
 	if unop.CommaOk {
diff --git a/src/runtime/chan.go b/src/runtime/chan.go
index 6563d4557f..37ad154a65 100644
--- a/src/runtime/chan.go
+++ b/src/runtime/chan.go
@@ -446,7 +446,7 @@ type chanSelectState struct {
 // chanSend sends a single value over the channel.
 // This operation will block unless a value is immediately available.
 // May panic if the channel is closed.
-func chanSend(ch *channel, value unsafe.Pointer) {
+func chanSend(ch *channel, value unsafe.Pointer, blockedlist *channelBlockedList) {
 	if ch.trySend(value) {
 		// value immediately sent
 		chanDebug(ch)
@@ -462,10 +462,11 @@ func chanSend(ch *channel, value unsafe.Pointer) {
 	sender := task.Current()
 	ch.state = chanStateSend
 	sender.Ptr = value
-	ch.blocked = &channelBlockedList{
+	*blockedlist = channelBlockedList{
 		next: ch.blocked,
 		t:    sender,
 	}
+	ch.blocked = blockedlist
 	chanDebug(ch)
 	task.Pause()
 	sender.Ptr = nil
@@ -475,7 +476,7 @@ func chanSend(ch *channel, value unsafe.Pointer) {
 // It blocks if there is no available value to recieve.
 // The recieved value is copied into the value pointer.
 // Returns the comma-ok value.
-func chanRecv(ch *channel, value unsafe.Pointer) bool {
+func chanRecv(ch *channel, value unsafe.Pointer, blockedlist *channelBlockedList) bool {
 	if rx, ok := ch.tryRecv(value); rx {
 		// value immediately available
 		chanDebug(ch)
@@ -491,10 +492,11 @@ func chanRecv(ch *channel, value unsafe.Pointer) bool {
 	receiver := task.Current()
 	ch.state = chanStateRecv
 	receiver.Ptr, receiver.Data = value, 1
-	ch.blocked = &channelBlockedList{
+	*blockedlist = channelBlockedList{
 		next: ch.blocked,
 		t:    receiver,
 	}
+	ch.blocked = blockedlist
 	chanDebug(ch)
 	task.Pause()
 	ok := receiver.Data == 1

From 2281b6a3f50bb329967df810f111873cadd3b934 Mon Sep 17 00:00:00 2001
From: deadprogram 
Date: Tue, 9 Jun 2020 11:03:06 +0200
Subject: [PATCH 76/82] machine/hifive1b: remove extra println left in by
 mistake

Signed-off-by: deadprogram 
---
 src/machine/machine_fe310.go | 1 -
 1 file changed, 1 deletion(-)

diff --git a/src/machine/machine_fe310.go b/src/machine/machine_fe310.go
index f2de109247..6bd2384213 100644
--- a/src/machine/machine_fe310.go
+++ b/src/machine/machine_fe310.go
@@ -48,7 +48,6 @@ func (p Pin) Set(high bool) {
 // Get returns the current value of a GPIO pin.
 func (p Pin) Get() bool {
 	val := sifive.GPIO0.VALUE.Get() & (1 << uint8(p))
-	println(sifive.GPIO0.VALUE.Get())
 	return (val > 0)
 }
 

From 4d1d0c2d3bbd7dbacc74c445c6adc00219bae49a Mon Sep 17 00:00:00 2001
From: Ayke van Laethem 
Date: Wed, 10 Jun 2020 20:49:15 +0200
Subject: [PATCH 77/82] main: go mod tidy

Clean up the go.mod and go.sum which have gotten a bit messy, and add an
extra line to go.sum that keeps reappearing locally for some reason (so
it seems important).
---
 go.mod |  1 -
 go.sum | 30 +-----------------------------
 2 files changed, 1 insertion(+), 30 deletions(-)

diff --git a/go.mod b/go.mod
index 186b44999c..688793696d 100644
--- a/go.mod
+++ b/go.mod
@@ -10,6 +10,5 @@ require (
 	github.com/marcinbor85/gohex v0.0.0-20200531091804-343a4b548892
 	go.bug.st/serial v1.0.0
 	golang.org/x/tools v0.0.0-20200216192241-b320d3a0f5a2
-	google.golang.org/appengine v1.4.0 // indirect
 	tinygo.org/x/go-llvm v0.0.0-20200503225853-345b2947b59d
 )
diff --git a/go.sum b/go.sum
index e6d4295c14..c24491ef21 100644
--- a/go.sum
+++ b/go.sum
@@ -13,15 +13,12 @@ github.com/gobwas/pool v0.2.0 h1:QEmUOlnSjWtnpRGHF3SauEiOsy82Cup83Vf2LcMlnc8=
 github.com/gobwas/pool v0.2.0/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw=
 github.com/gobwas/ws v1.0.2 h1:CoAavW/wd/kulfZmSIBt6p24n4j7tHgNVCjsfHVNUbo=
 github.com/gobwas/ws v1.0.2/go.mod h1:szmBTxLgaFppYjEmNtny/v3w89xOydFnnZMcgRRu/EM=
-github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
 github.com/google/shlex v0.0.0-20181106134648-c34317bd91bf h1:7+FW5aGwISbqUtkfmIpZJGRgNFg2ioYPvFaUxdqpDsg=
 github.com/google/shlex v0.0.0-20181106134648-c34317bd91bf/go.mod h1:RpwtwJQFrIEPstU94h88MWPXP2ektJZ8cZ0YntAmXiE=
 github.com/knq/sysutil v0.0.0-20191005231841-15668db23d08 h1:V0an7KRw92wmJysvFvtqtKMAPmvS5O0jtB0nYo6t+gs=
 github.com/knq/sysutil v0.0.0-20191005231841-15668db23d08/go.mod h1:dFWs1zEqDjFtnBXsd1vPOZaLsESovai349994nHx3e0=
 github.com/mailru/easyjson v0.7.0 h1:aizVhC/NAAcKWb+5QsU1iNOZb4Yws5UO2I+aIprQITM=
 github.com/mailru/easyjson v0.7.0/go.mod h1:KAzv3t3aY1NaHWoQz1+4F1ccyAH66Jk7yos7ldAVICs=
-github.com/marcinbor85/gohex v0.0.0-20180128172054-7a43cd876e46 h1:wXG2bA8fO7Vv7lLk2PihFMTqmbT173Tje39oKzQ50Mo=
-github.com/marcinbor85/gohex v0.0.0-20180128172054-7a43cd876e46/go.mod h1:Pb6XcsXyropB9LNHhnqaknG/vEwYztLkQzVCHv8sQ3M=
 github.com/marcinbor85/gohex v0.0.0-20200531091804-343a4b548892 h1:6J+qramlHVLmiBOgRiBOnQkno8uprqG6YFFQTt6uYIw=
 github.com/marcinbor85/gohex v0.0.0-20200531091804-343a4b548892/go.mod h1:Pb6XcsXyropB9LNHhnqaknG/vEwYztLkQzVCHv8sQ3M=
 github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
@@ -33,46 +30,21 @@ 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/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
-golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
 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/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.0.0-20190423024810-112230192c58/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 h1:vEOn+mP2zCOVzKckCZy6YsCtDblrpj/w7B9nxGNELpg=
 golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
-golang.org/x/tools v0.0.0-20190227180812-8dcc6e70cdef h1:ymc9FeDom3RIEA3coKokSllBB1hRcMT0tZ1W3Jf9Ids=
-golang.org/x/tools v0.0.0-20190227180812-8dcc6e70cdef/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
 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/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
-google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
 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-20190224120431-7707ae5d1261 h1:rJS2Hga39YAnm7DE4qrPm6Dr/67EOojL0XPzvbEeBiw=
-tinygo.org/x/go-llvm v0.0.0-20190224120431-7707ae5d1261/go.mod h1:fv1F0BSNpxMfCL0zF3M4OPFbgYHnhtB6ST0HvUtu/LE=
-tinygo.org/x/go-llvm v0.0.0-20190818154551-95bc4ffe1add h1:dFjMH1sLhYADg8UQm7DB56B7e+TfvAmWmEZLhyv3r/w=
-tinygo.org/x/go-llvm v0.0.0-20190818154551-95bc4ffe1add/go.mod h1:fv1F0BSNpxMfCL0zF3M4OPFbgYHnhtB6ST0HvUtu/LE=
-tinygo.org/x/go-llvm v0.0.0-20191103182207-90b6e4bdc0b9 h1:d6rAX39a3C0pKrY5HcojEGyN8w9ocU0v7X28lC/TRKU=
-tinygo.org/x/go-llvm v0.0.0-20191103182207-90b6e4bdc0b9/go.mod h1:fv1F0BSNpxMfCL0zF3M4OPFbgYHnhtB6ST0HvUtu/LE=
-tinygo.org/x/go-llvm v0.0.0-20191103200204-37e93e3f04e2 h1:Q5Hv3e5cLMGkiYwYgZL1Zrv6nb/EY+DJpRWrdO6ws6o=
-tinygo.org/x/go-llvm v0.0.0-20191103200204-37e93e3f04e2/go.mod h1:fv1F0BSNpxMfCL0zF3M4OPFbgYHnhtB6ST0HvUtu/LE=
-tinygo.org/x/go-llvm v0.0.0-20191113125529-bad6d01809e8 h1:9Bfvso+tTVQg16UzOA614NaYA4x8vsRBNtd3eBrXwp0=
-tinygo.org/x/go-llvm v0.0.0-20191113125529-bad6d01809e8/go.mod h1:fv1F0BSNpxMfCL0zF3M4OPFbgYHnhtB6ST0HvUtu/LE=
-tinygo.org/x/go-llvm v0.0.0-20191124211856-b2db3df3f257 h1:o8VDylrMN7gWemBMu8rEyuogKPhcLTdx5KrUAp9macc=
-tinygo.org/x/go-llvm v0.0.0-20191124211856-b2db3df3f257/go.mod h1:fv1F0BSNpxMfCL0zF3M4OPFbgYHnhtB6ST0HvUtu/LE=
-tinygo.org/x/go-llvm v0.0.0-20191215173731-ad71f3d24aae h1:s8J5EyxCkHxXB08UI3gk9W9IS/ekizRvSX+PfZxnAB0=
-tinygo.org/x/go-llvm v0.0.0-20191215173731-ad71f3d24aae/go.mod h1:fv1F0BSNpxMfCL0zF3M4OPFbgYHnhtB6ST0HvUtu/LE=
-tinygo.org/x/go-llvm v0.0.0-20200104190746-1ff21df33566 h1:a4y30bTf7U0zDA75v2PTL+XQ2OzJetj19gK8XwQpUNY=
-tinygo.org/x/go-llvm v0.0.0-20200104190746-1ff21df33566/go.mod h1:fv1F0BSNpxMfCL0zF3M4OPFbgYHnhtB6ST0HvUtu/LE=
-tinygo.org/x/go-llvm v0.0.0-20200226165415-53522ab6713d h1:mtgZh/e8a3wxneQFuLXoQYO//1mvlki02yZ1JCwMKp4=
-tinygo.org/x/go-llvm v0.0.0-20200226165415-53522ab6713d/go.mod h1:fv1F0BSNpxMfCL0zF3M4OPFbgYHnhtB6ST0HvUtu/LE=
-tinygo.org/x/go-llvm v0.0.0-20200401165421-8d120882fc7a h1:Ugje2Lxuv8CFncHzs5W+hWfJvPsM+W4K0zRvzFbLvoE=
-tinygo.org/x/go-llvm v0.0.0-20200401165421-8d120882fc7a/go.mod h1:fv1F0BSNpxMfCL0zF3M4OPFbgYHnhtB6ST0HvUtu/LE=
 tinygo.org/x/go-llvm v0.0.0-20200503225853-345b2947b59d h1:hcX7vpB067GWM/EH4sGGOti0PMgIx+0bbZwUXctOIvE=
 tinygo.org/x/go-llvm v0.0.0-20200503225853-345b2947b59d/go.mod h1:fv1F0BSNpxMfCL0zF3M4OPFbgYHnhtB6ST0HvUtu/LE=

From e2bf7bbb4991298d3596384bcbdf2b7c566c92f3 Mon Sep 17 00:00:00 2001
From: Ayke van Laethem 
Date: Sun, 14 Jun 2020 19:07:30 +0200
Subject: [PATCH 78/82] device: add new cross-arch Asm and AsmFull functions

This is necessary to avoid a circular dependency between the device/avr
and runtime/interrupts package in the next commit.

It may be worth replacing existing calls like device/arm.Asm to
device.Asm, to have a single place where these are defined.
---
 compiler/compiler.go |  4 ++--
 src/device/asm.go    | 21 +++++++++++++++++++++
 2 files changed, 23 insertions(+), 2 deletions(-)
 create mode 100644 src/device/asm.go

diff --git a/compiler/compiler.go b/compiler/compiler.go
index 677128d0e7..fe45eb3fc6 100644
--- a/compiler/compiler.go
+++ b/compiler/compiler.go
@@ -1327,9 +1327,9 @@ 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 == "device/arm.Asm" || name == "device/avr.Asm" || name == "device/riscv.Asm":
+		case name == "device.Asm" || name == "device/arm.Asm" || name == "device/avr.Asm" || name == "device/riscv.Asm":
 			return b.createInlineAsm(instr.Args)
-		case name == "device/arm.AsmFull" || name == "device/avr.AsmFull" || name == "device/riscv.AsmFull":
+		case name == "device.AsmFull" || name == "device/arm.AsmFull" || name == "device/avr.AsmFull" || name == "device/riscv.AsmFull":
 			return b.createInlineAsmFull(instr)
 		case strings.HasPrefix(name, "device/arm.SVCall"):
 			return b.emitSVCall(instr.Args)
diff --git a/src/device/asm.go b/src/device/asm.go
new file mode 100644
index 0000000000..f441b6ae29
--- /dev/null
+++ b/src/device/asm.go
@@ -0,0 +1,21 @@
+package device
+
+// Run the given assembly code. The code will be marked as having side effects,
+// as it doesn't produce output and thus would normally be eliminated by the
+// optimizer.
+func Asm(asm string)
+
+// Run the given inline assembly. The code will be marked as having side
+// effects, as it would otherwise be optimized away. The inline assembly string
+// recognizes template values in the form {name}, like so:
+//
+//     arm.AsmFull(
+//         "str {value}, {result}",
+//         map[string]interface{}{
+//             "value":  1
+//             "result": &dest,
+//         })
+//
+// You can use {} in the asm string (which expands to a register) to set the
+// return value.
+func AsmFull(asm string, regs map[string]interface{}) uintptr

From e86ca2080d0cf021facaadb70feaa268d1bd737e Mon Sep 17 00:00:00 2001
From: Ayke van Laethem 
Date: Sun, 14 Jun 2020 19:38:50 +0200
Subject: [PATCH 79/82] runtime/interrupt: add cross-chip disable/restore
 interrupt support

This should cover all supported baremetal targets.
---
 src/runtime/interrupt/interrupt_avr.go        | 36 +++++++++++++++++++
 src/runtime/interrupt/interrupt_cortexm.go    | 24 +++++++++++++
 .../interrupt/interrupt_gameboyadvance.go     | 29 +++++++++++++++
 .../interrupt/interrupt_tinygoriscv.go        | 29 +++++++++++++++
 4 files changed, 118 insertions(+)
 create mode 100644 src/runtime/interrupt/interrupt_avr.go
 create mode 100644 src/runtime/interrupt/interrupt_tinygoriscv.go

diff --git a/src/runtime/interrupt/interrupt_avr.go b/src/runtime/interrupt/interrupt_avr.go
new file mode 100644
index 0000000000..6eb6b28fa4
--- /dev/null
+++ b/src/runtime/interrupt/interrupt_avr.go
@@ -0,0 +1,36 @@
+// +build avr
+
+package interrupt
+
+import "device"
+
+// State represents the previous global interrupt state.
+type State uint8
+
+// Disable disables all interrupts and returns the previous interrupt state. It
+// can be used in a critical section like this:
+//
+//     state := interrupt.Disable()
+//     // critical section
+//     interrupt.Restore(state)
+//
+// Critical sections can be nested. Make sure to call Restore in the same order
+// as you called Disable (this happens naturally with the pattern above).
+func Disable() (state State) {
+	// SREG is at I/O address 0x3f.
+	return State(device.AsmFull(`
+		in {}, 0x3f
+		cli
+	`, nil))
+}
+
+// Restore restores interrupts to what they were before. Give the previous state
+// returned by Disable as a parameter. If interrupts were disabled before
+// calling Disable, this will not re-enable interrupts, allowing for nested
+// cricital sections.
+func Restore(state State) {
+	// SREG is at I/O address 0x3f.
+	device.AsmFull("out 0x3f, {state}", map[string]interface{}{
+		"state": state,
+	})
+}
diff --git a/src/runtime/interrupt/interrupt_cortexm.go b/src/runtime/interrupt/interrupt_cortexm.go
index 5354be9e00..c23f736d80 100644
--- a/src/runtime/interrupt/interrupt_cortexm.go
+++ b/src/runtime/interrupt/interrupt_cortexm.go
@@ -21,3 +21,27 @@ func (irq Interrupt) Enable() {
 func (irq Interrupt) SetPriority(priority uint8) {
 	arm.SetPriority(uint32(irq.num), uint32(priority))
 }
+
+// State represents the previous global interrupt state.
+type State uintptr
+
+// Disable disables all interrupts and returns the previous interrupt state. It
+// can be used in a critical section like this:
+//
+//     state := interrupt.Disable()
+//     // critical section
+//     interrupt.Restore(state)
+//
+// Critical sections can be nested. Make sure to call Restore in the same order
+// as you called Disable (this happens naturally with the pattern above).
+func Disable() (state State) {
+	return State(arm.DisableInterrupts())
+}
+
+// Restore restores interrupts to what they were before. Give the previous state
+// returned by Disable as a parameter. If interrupts were disabled before
+// calling Disable, this will not re-enable interrupts, allowing for nested
+// cricital sections.
+func Restore(state State) {
+	arm.EnableInterrupts(uintptr(state))
+}
diff --git a/src/runtime/interrupt/interrupt_gameboyadvance.go b/src/runtime/interrupt/interrupt_gameboyadvance.go
index c25afc14b9..e4b202937a 100644
--- a/src/runtime/interrupt/interrupt_gameboyadvance.go
+++ b/src/runtime/interrupt/interrupt_gameboyadvance.go
@@ -34,3 +34,32 @@ func handleInterrupt() {
 // appropriate interrupt handler for the given interrupt ID.
 //go:linkname callInterruptHandler runtime.callInterruptHandler
 func callInterruptHandler(id int)
+
+// State represents the previous global interrupt state.
+type State uint8
+
+// Disable disables all interrupts and returns the previous interrupt state. It
+// can be used in a critical section like this:
+//
+//     state := interrupt.Disable()
+//     // critical section
+//     interrupt.Restore(state)
+//
+// Critical sections can be nested. Make sure to call Restore in the same order
+// as you called Disable (this happens naturally with the pattern above).
+func Disable() (state State) {
+	// Save the previous interrupt state.
+	state = State(regInterruptMasterEnable.Get())
+	// Disable all interrupts.
+	regInterruptMasterEnable.Set(0)
+	return
+}
+
+// Restore restores interrupts to what they were before. Give the previous state
+// returned by Disable as a parameter. If interrupts were disabled before
+// calling Disable, this will not re-enable interrupts, allowing for nested
+// cricital sections.
+func Restore(state State) {
+	// Restore interrupts to the previous state.
+	regInterruptMasterEnable.Set(uint16(state))
+}
diff --git a/src/runtime/interrupt/interrupt_tinygoriscv.go b/src/runtime/interrupt/interrupt_tinygoriscv.go
new file mode 100644
index 0000000000..0161018da5
--- /dev/null
+++ b/src/runtime/interrupt/interrupt_tinygoriscv.go
@@ -0,0 +1,29 @@
+// +build tinygo.riscv
+
+package interrupt
+
+import "device/riscv"
+
+// State represents the previous global interrupt state.
+type State uintptr
+
+// Disable disables all interrupts and returns the previous interrupt state. It
+// can be used in a critical section like this:
+//
+//     state := interrupt.Disable()
+//     // critical section
+//     interrupt.Restore(state)
+//
+// Critical sections can be nested. Make sure to call Restore in the same order
+// as you called Disable (this happens naturally with the pattern above).
+func Disable() (state State) {
+	return State(riscv.DisableInterrupts())
+}
+
+// Restore restores interrupts to what they were before. Give the previous state
+// returned by Disable as a parameter. If interrupts were disabled before
+// calling Disable, this will not re-enable interrupts, allowing for nested
+// cricital sections.
+func Restore(state State) {
+	riscv.EnableInterrupts(uintptr(state))
+}

From cb504e31220ee49e53efeb4fd2fc81ca899331b1 Mon Sep 17 00:00:00 2001
From: APDevice <59993100+APDevice@users.noreply.github.com>
Date: Thu, 18 Jun 2020 01:58:21 -0700
Subject: [PATCH 80/82] added pygamer to smoketest

---
 Makefile | 4 +++-
 1 file changed, 3 insertions(+), 1 deletion(-)

diff --git a/Makefile b/Makefile
index d16d77f82c..d02079a6fa 100644
--- a/Makefile
+++ b/Makefile
@@ -192,7 +192,7 @@ smoketest:
 	$(TINYGO) build -size short -o test.hex -target=pca10040            examples/blinky2
 	@$(MD5SUM) test.hex
 	$(TINYGO) build -size short -o test.hex -target=pca10040            examples/button
-	@$(MD5SUM) test.hex
+	@$(MD5SUM) test.
 	$(TINYGO) build -size short -o test.hex -target=pca10040            examples/button2
 	@$(MD5SUM) test.hex
 	$(TINYGO) build -size short -o test.hex -target=pca10040            examples/echo
@@ -295,6 +295,8 @@ smoketest:
 	@$(MD5SUM) test.hex
 	$(TINYGO) build -size short -o test.hex -target=wioterminal         examples/blinky1
 	@$(MD5SUM) test.hex
+	$(TINYGO) build -size short -o test.hex -target=pygamer             examples/blinky1
+	@$(MD5SUM) test.hex
 ifneq ($(AVR), 0)
 	$(TINYGO) build -size short -o test.hex -target=atmega1284p         examples/serial
 	@$(MD5SUM) test.hex

From 697cb1835a8dab2d58cd2384afda4dd65bcc47e0 Mon Sep 17 00:00:00 2001
From: APDevice <59993100+APDevice@users.noreply.github.com>
Date: Thu, 18 Jun 2020 02:00:54 -0700
Subject: [PATCH 81/82] added pygamer to board

---
 src/machine/board_pygamer.go | 147 +++++++++++++++++++++++++++++++++++
 1 file changed, 147 insertions(+)
 create mode 100644 src/machine/board_pygamer.go

diff --git a/src/machine/board_pygamer.go b/src/machine/board_pygamer.go
new file mode 100644
index 0000000000..6719f0d069
--- /dev/null
+++ b/src/machine/board_pygamer.go
@@ -0,0 +1,147 @@
+// +build sam,atsamd51,pygamer
+
+package machine
+
+import "device/sam"
+
+// used to reset into bootloader
+const RESET_MAGIC_VALUE = 0xf01669ef
+
+// GPIO Pins
+const (
+	D0  = PB17 // UART0 RX/PWM available
+	D1  = PB16 // UART0 TX/PWM available
+	D2  = PB03
+	D3  = PB02
+	D4  = PA14 // PWM available
+	D5  = PA16 // PWM available
+	D6  = PA18 // PWM available
+	D7  = PB14 // CS for microSD card slot
+	D8  = PA15 // built-in neopixel
+	D9  = PA19 // PWM available
+	D10 = PA20 // can be used for PWM or UART1 TX
+	D11 = PA21 // can be used for PWM or UART1 RX
+	D12 = PA22 // PWM available
+	D13 = PA23 // PWM available
+)
+
+// Analog pins
+const (
+	A0 = PA02 // ADC/AIN[0]
+	A1 = PA05 // ADC/AIN[2]
+	A2 = PB08 // ADC/AIN[3]
+	A3 = PB09 // ADC/AIN[4]
+	A4 = PA04 // ADC/AIN[5]
+	A5 = PA06 // ADC/AIN[6]
+	A6 = PB01 // ADC/AIN[12]/VMEAS
+	A7 = PB04 // ADC/AIN[6]/LIGHT
+	A8 = D2   // ADC/AIN[14]
+	A9 = D3   // ADC/AIN[15]
+)
+
+const (
+	LED       = D13
+	NEOPIXELS = D8
+
+	SD_CS = D7
+
+	LIGHTSENSOR = A7
+
+	BUTTON_LATCH = PB00
+	BUTTON_OUT   = PB30
+	BUTTON_CLK   = PB31
+
+	JOYY = PB06
+	JOYX = PB07
+
+	TFT_DC   = PB05
+	TFT_CS   = PB12
+	TFT_RST  = PA00
+	TFT_LITE = PA01
+
+	SPEAKER_ENABLE = PA27
+)
+
+const (
+	BUTTON_SELECT_MASK = 16
+	BUTTON_START_MASK  = 32
+	BUTTON_A_MASK      = 64
+	BUTTON_B_MASK      = 128
+)
+
+// UART0 aka USBCDC pins
+const (
+	USBCDC_DM_PIN = PA24
+	USBCDC_DP_PIN = PA25
+)
+
+// UART1 pins
+const (
+	UART_TX_PIN = D1
+	UART_RX_PIN = D0
+)
+
+// UART1 var is on SERCOM3, defined in atsamd51.go
+
+// UART2 pins
+const (
+	UART2_TX_PIN = A4
+	UART2_RX_PIN = A5
+)
+
+// UART2 var is on SERCOM0, defined in atsamd51.go
+
+// I2C pins
+const (
+	SDA_PIN = PA12 // SDA: SERCOM2/PAD[0]
+	SCL_PIN = PA13 // SCL: SERCOM2/PAD[1]
+)
+
+// I2C on the PyGamer.
+var (
+	I2C0 = I2C{
+		Bus:    sam.SERCOM2_I2CM,
+		SERCOM: 2,
+	}
+)
+
+// SPI pins
+const (
+	SPI0_SCK_PIN  = PA17 // SCK: SERCOM1/PAD[1]
+	SPI0_MOSI_PIN = PB23 // MOSI: SERCOM1/PAD[3]
+	SPI0_MISO_PIN = PB22 // MISO: SERCOM1/PAD[2]
+)
+
+// SPI on the PyGamer.
+var (
+	SPI0 = SPI{
+		Bus:    sam.SERCOM1_SPIM,
+		SERCOM: 1,
+	}
+)
+
+// TFT SPI pins
+const (
+	SPI1_SCK_PIN  = PB13 // SCK: SERCOM4/PAD[1]
+	SPI1_MOSI_PIN = PB15 // MOSI: SERCOM4/PAD[3]
+	SPI1_MISO_PIN = NoPin
+)
+
+// TFT SPI on the PyGamer.
+var (
+	SPI1 = SPI{
+		Bus:    sam.SERCOM4_SPIM,
+		SERCOM: 4,
+	}
+)
+
+// USB CDC identifiers
+const (
+	usb_STRING_PRODUCT      = "Adafruit pyGamer M4"
+	usb_STRING_MANUFACTURER = "Adafruit"
+)
+
+var (
+	usb_VID uint16 = 0x239A
+	usb_PID uint16 = 0x8033
+)

From 0478dd5fc34759582af9b3eabc7b9f6ac93d2c79 Mon Sep 17 00:00:00 2001
From: APDevice <59993100+APDevice@users.noreply.github.com>
Date: Thu, 18 Jun 2020 02:02:48 -0700
Subject: [PATCH 82/82] Adding Board: PyGamer

---
 targets/pygamer.json | 8 ++++++++
 1 file changed, 8 insertions(+)
 create mode 100644 targets/pygamer.json

diff --git a/targets/pygamer.json b/targets/pygamer.json
new file mode 100644
index 0000000000..a14460d349
--- /dev/null
+++ b/targets/pygamer.json
@@ -0,0 +1,8 @@
+{
+    "inherits": ["atsamd51j19a"],
+    "build-tags": ["sam", "atsamd51j19a", "pygamer"],
+    "flash-1200-bps-reset": "true",
+    "flash-method": "msd",
+    "msd-volume-name": "PYGAMERBOOT",
+    "msd-firmware-name": "arcade.uf2"
+}