Merge remote-tracking branch 'origin/dev' into feature/usb-common

This commit is contained in:
ardnew
2021-09-08 23:46:21 -05:00
90 changed files with 2826 additions and 565 deletions
+7 -21
View File
@@ -286,8 +286,8 @@ commands:
- run: - run:
name: "Install dependencies" name: "Install dependencies"
command: | command: |
curl https://dl.google.com/go/go1.16.darwin-amd64.tar.gz -o go1.16.darwin-amd64.tar.gz curl https://dl.google.com/go/go1.17.darwin-amd64.tar.gz -o go1.17.darwin-amd64.tar.gz
sudo tar -C /usr/local -xzf go1.16.darwin-amd64.tar.gz sudo tar -C /usr/local -xzf go1.17.darwin-amd64.tar.gz
ln -s /usr/local/go/bin/go /usr/local/bin/go ln -s /usr/local/go/bin/go /usr/local/bin/go
HOMEBREW_NO_AUTO_UPDATE=1 brew install qemu HOMEBREW_NO_AUTO_UPDATE=1 brew install qemu
- install-xtensa-toolchain: - install-xtensa-toolchain:
@@ -365,24 +365,12 @@ commands:
- /go/pkg/mod - /go/pkg/mod
jobs: jobs:
test-llvm10-go113: test-llvm10-go115:
docker:
- image: circleci/golang:1.13-buster
steps:
- test-linux:
llvm: "10"
test-llvm10-go114:
docker:
- image: circleci/golang:1.14-buster
steps:
- test-linux:
llvm: "10"
test-llvm11-go115:
docker: docker:
- image: circleci/golang:1.15-buster - image: circleci/golang:1.15-buster
steps: steps:
- test-linux: - test-linux:
llvm: "11" llvm: "10"
test-llvm11-go116: test-llvm11-go116:
docker: docker:
- image: circleci/golang:1.16-buster - image: circleci/golang:1.16-buster
@@ -391,12 +379,12 @@ jobs:
llvm: "11" llvm: "11"
assert-test-linux: assert-test-linux:
docker: docker:
- image: circleci/golang:1.14-stretch - image: circleci/golang:1.17-stretch
steps: steps:
- assert-test-linux - assert-test-linux
build-linux: build-linux:
docker: docker:
- image: circleci/golang:1.14-stretch - image: circleci/golang:1.17-stretch
steps: steps:
- build-linux - build-linux
build-macos: build-macos:
@@ -410,9 +398,7 @@ jobs:
workflows: workflows:
test-all: test-all:
jobs: jobs:
- test-llvm10-go113 - test-llvm10-go115
- test-llvm10-go114
- test-llvm11-go115
- test-llvm11-go116 - test-llvm11-go116
- build-linux - build-linux
- build-macos - build-macos
+3
View File
@@ -0,0 +1,3 @@
build/
llvm-*/
@@ -1,7 +1,7 @@
name: CI for tinygo-dev docker container name: CI for tinygo-dev docker container
on: on:
push: push:
branches: [ dev ] branches: [ dev, fix-docker-llvm-build ]
jobs: jobs:
push_to_registry: push_to_registry:
+1 -1
View File
@@ -23,7 +23,7 @@ different guide:
LLVM, Clang and LLD are quite light on dependencies, requiring only standard LLVM, Clang and LLD are quite light on dependencies, requiring only standard
build tools to be built. Go is of course necessary to build TinyGo itself. build tools to be built. Go is of course necessary to build TinyGo itself.
* Go (1.13+) * Go (1.15+)
* Standard build tools (gcc/clang) * Standard build tools (gcc/clang)
* git * git
* CMake * CMake
+3 -3
View File
@@ -1,8 +1,8 @@
# TinyGo base stage installs the most recent Go 1.16.x, LLVM 11 and the TinyGo compiler itself. # TinyGo base stage installs the most recent Go 1.17.x, LLVM 11 and the TinyGo compiler itself.
FROM golang:1.16 AS tinygo-base FROM golang:1.17 AS tinygo-base
RUN wget -O- https://apt.llvm.org/llvm-snapshot.gpg.key| apt-key add - && \ RUN wget -O- https://apt.llvm.org/llvm-snapshot.gpg.key| apt-key add - && \
echo "deb http://apt.llvm.org/buster/ llvm-toolchain-buster-11 main" >> /etc/apt/sources.list && \ echo "deb http://apt.llvm.org/bullseye/ llvm-toolchain-bullseye-11 main" >> /etc/apt/sources.list && \
apt-get update && \ apt-get update && \
apt-get install -y llvm-11-dev libclang-11-dev lld-11 git apt-get install -y llvm-11-dev libclang-11-dev lld-11 git
+11 -1
View File
@@ -100,7 +100,7 @@ endif
clean: clean:
@rm -rf build @rm -rf build
FMT_PATHS = ./*.go builder cgo compiler interp loader src/device/arm src/examples src/machine src/os src/reflect src/runtime src/sync src/syscall src/internal/reflectlite transform FMT_PATHS = ./*.go builder cgo compiler interp loader src/device/arm src/examples src/machine src/os src/reflect src/runtime src/sync src/syscall src/testing src/internal/reflectlite transform
fmt: fmt:
@gofmt -l -w $(FMT_PATHS) @gofmt -l -w $(FMT_PATHS)
fmt-check: fmt-check:
@@ -190,6 +190,10 @@ TEST_PACKAGES = \
container/list \ container/list \
container/ring \ container/ring \
crypto/des \ crypto/des \
crypto/md5 \
crypto/sha1 \
crypto/sha256 \
crypto/sha512 \
encoding \ encoding \
encoding/ascii85 \ encoding/ascii85 \
encoding/base32 \ encoding/base32 \
@@ -199,6 +203,7 @@ TEST_PACKAGES = \
hash/crc64 \ hash/crc64 \
math \ math \
math/cmplx \ math/cmplx \
testing \
text/scanner \ text/scanner \
unicode/utf8 \ unicode/utf8 \
@@ -358,6 +363,8 @@ smoketest:
@$(MD5SUM) test.hex @$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=arduino-nano33 examples/blinky1 $(TINYGO) build -size short -o test.hex -target=arduino-nano33 examples/blinky1
@$(MD5SUM) test.hex @$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=arduino-mkrwifi1010 examples/blinky1
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=pico examples/blinky1 $(TINYGO) build -size short -o test.hex -target=pico examples/blinky1
@$(MD5SUM) test.hex @$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=nano-33-ble examples/blinky1 $(TINYGO) build -size short -o test.hex -target=nano-33-ble examples/blinky1
@@ -444,6 +451,9 @@ endif
@$(MD5SUM) test.nro @$(MD5SUM) test.nro
$(TINYGO) build -size short -o test.hex -target=pca10040 -opt=0 ./testdata/stdlib.go $(TINYGO) build -size short -o test.hex -target=pca10040 -opt=0 ./testdata/stdlib.go
@$(MD5SUM) test.hex @$(MD5SUM) test.hex
ifneq ($(OS),Windows_NT)
$(TINYGO) build -o test.elf -gc=leaking -scheduler=none examples/serial
endif
wasmtest: wasmtest:
+3 -1
View File
@@ -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. You can compile TinyGo programs for microcontrollers, WebAssembly and Linux.
The following 68 microcontroller boards are currently supported: The following 70 microcontroller boards are currently supported:
* [Adafruit Circuit Playground Bluefruit](https://www.adafruit.com/product/4333) * [Adafruit Circuit Playground Bluefruit](https://www.adafruit.com/product/4333)
* [Adafruit Circuit Playground Express](https://www.adafruit.com/product/3333) * [Adafruit Circuit Playground Express](https://www.adafruit.com/product/3333)
@@ -69,6 +69,7 @@ The following 68 microcontroller boards are currently supported:
* [Arduino Mega 1280](https://www.arduino.cc/en/Main/arduinoBoardMega/) * [Arduino Mega 1280](https://www.arduino.cc/en/Main/arduinoBoardMega/)
* [Arduino Mega 2560](https://store.arduino.cc/arduino-mega-2560-rev3) * [Arduino Mega 2560](https://store.arduino.cc/arduino-mega-2560-rev3)
* [Arduino MKR1000](https://store.arduino.cc/arduino-mkr1000-wifi) * [Arduino MKR1000](https://store.arduino.cc/arduino-mkr1000-wifi)
* [Arduino MKR WiFi 1010](https://store.arduino.cc/usa/mkr-wifi-1010)
* [Arduino Nano](https://store.arduino.cc/arduino-nano) * [Arduino Nano](https://store.arduino.cc/arduino-nano)
* [Arduino Nano 33 BLE](https://store.arduino.cc/nano-33-ble) * [Arduino Nano 33 BLE](https://store.arduino.cc/nano-33-ble)
* [Arduino Nano 33 BLE Sense](https://store.arduino.cc/nano-33-ble-sense) * [Arduino Nano 33 BLE Sense](https://store.arduino.cc/nano-33-ble-sense)
@@ -101,6 +102,7 @@ The following 68 microcontroller boards are currently supported:
* [PJRC Teensy 4.0](https://www.pjrc.com/store/teensy40.html) * [PJRC Teensy 4.0](https://www.pjrc.com/store/teensy40.html)
* [ProductivityOpen P1AM-100](https://facts-engineering.github.io/modules/P1AM-100/P1AM-100.html) * [ProductivityOpen P1AM-100](https://facts-engineering.github.io/modules/P1AM-100/P1AM-100.html)
* [Raspberry Pi Pico](https://www.raspberrypi.org/products/raspberry-pi-pico/) * [Raspberry Pi Pico](https://www.raspberrypi.org/products/raspberry-pi-pico/)
* [Raytac MDBT50Q-RX Dongle (with TinyUF2 bootloader)](https://www.adafruit.com/product/5199)
* [Seeed Wio Terminal](https://www.seeedstudio.com/Wio-Terminal-p-4509.html) * [Seeed Wio Terminal](https://www.seeedstudio.com/Wio-Terminal-p-4509.html)
* [Seeed Seeeduino XIAO](https://www.seeedstudio.com/Seeeduino-XIAO-Arduino-Microcontroller-SAMD21-Cortex-M0+-p-4426.html) * [Seeed Seeeduino XIAO](https://www.seeedstudio.com/Seeeduino-XIAO-Arduino-Microcontroller-SAMD21-Cortex-M0+-p-4426.html)
* [Seeed Sipeed MAix BiT](https://www.seeedstudio.com/Sipeed-MAix-BiT-for-RISC-V-AI-IoT-p-2872.html) * [Seeed Sipeed MAix BiT](https://www.seeedstudio.com/Sipeed-MAix-BiT-for-RISC-V-AI-IoT-p-2872.html)
+1 -1
View File
@@ -12,7 +12,7 @@ jobs:
steps: steps:
- task: GoTool@0 - task: GoTool@0
inputs: inputs:
version: '1.16' version: '1.17'
- checkout: self - checkout: self
fetchDepth: 1 fetchDepth: 1
- task: Cache@2 - task: Cache@2
+4
View File
@@ -155,6 +155,10 @@ func compileAndCacheCFile(abspath, tmpdir string, cflags []string, printCommands
// Write dependencies file. // Write dependencies file.
f, err := ioutil.TempFile(filepath.Dir(depfileCachePath), depfileName) f, err := ioutil.TempFile(filepath.Dir(depfileCachePath), depfileName)
if err != nil {
return "", err
}
buf, err = json.MarshalIndent(dependencySlice, "", "\t") buf, err = json.MarshalIndent(dependencySlice, "", "\t")
if err != nil { if err != nil {
panic(err) // shouldn't happen panic(err) // shouldn't happen
+2 -2
View File
@@ -33,8 +33,8 @@ func NewConfig(options *compileopts.Options) (*compileopts.Config, error) {
if err != nil { if err != nil {
return nil, fmt.Errorf("could not read version from GOROOT (%v): %v", goroot, err) return nil, fmt.Errorf("could not read version from GOROOT (%v): %v", goroot, err)
} }
if major != 1 || minor < 13 || minor > 16 { if major != 1 || minor < 15 || minor > 17 {
return nil, fmt.Errorf("requires go version 1.13 through 1.16, got go%d.%d", major, minor) return nil, fmt.Errorf("requires go version 1.15 through 1.17, got go%d.%d", major, minor)
} }
clangHeaderPath := getClangHeaderPath(goenv.Get("TINYGOROOT")) clangHeaderPath := getClangHeaderPath(goenv.Get("TINYGOROOT"))
+3
View File
@@ -297,6 +297,9 @@ func (c *Config) Programmer() (method, openocdInterface string) {
case "openocd", "msd", "command": case "openocd", "msd", "command":
// The -programmer flag only specifies the flash method. // The -programmer flag only specifies the flash method.
return c.Options.Programmer, c.Target.OpenOCDInterface return c.Options.Programmer, c.Target.OpenOCDInterface
case "bmp":
// The -programmer flag only specifies the flash method.
return c.Options.Programmer, ""
default: default:
// The -programmer flag specifies something else, assume it specifies // The -programmer flag specifies something else, assume it specifies
// the OpenOCD interface name. // the OpenOCD interface name.
+3
View File
@@ -253,6 +253,9 @@ func defaultTarget(goos, goarch, triple string) (*TargetSpec, error) {
GDB: []string{"gdb"}, GDB: []string{"gdb"},
PortReset: "false", PortReset: "false",
} }
if goarch == "386" {
spec.CPU = "pentium4"
}
if goos == "darwin" { if goos == "darwin" {
spec.CFlags = append(spec.CFlags, "-isysroot", "/Library/Developer/CommandLineTools/SDKs/MacOSX.sdk") spec.CFlags = append(spec.CFlags, "-isysroot", "/Library/Developer/CommandLineTools/SDKs/MacOSX.sdk")
spec.LDFlags = append(spec.LDFlags, "-Wl,-dead_strip") spec.LDFlags = append(spec.LDFlags, "-Wl,-dead_strip")
+99
View File
@@ -0,0 +1,99 @@
package compiler
// This file defines alias functions for functions that are normally defined in
// Go assembly.
//
// The Go toolchain defines many performance critical functions in assembly
// instead of plain Go. This is a problem for TinyGo as it currently (as of
// august 2021) is not able to compile these assembly files and even if it
// could, it would not be able to make use of them for many targets that are
// supported by TinyGo (baremetal RISC-V, AVR, etc). Therefore, many of these
// functions are aliased to their generic Go implementation.
// This results in slower than possible implementations, but at least they are
// usable.
import "tinygo.org/x/go-llvm"
var stdlibAliases = map[string]string{
// crypto packages
"crypto/md5.block": "crypto/md5.blockGeneric",
"crypto/sha1.block": "crypto/sha1.blockGeneric",
"crypto/sha1.blockAMD64": "crypto/sha1.blockGeneric",
"crypto/sha256.block": "crypto/sha256.blockGeneric",
"crypto/sha512.blockAMD64": "crypto/sha512.blockGeneric",
// math package
"math.Asin": "math.asin",
"math.Asinh": "math.asinh",
"math.Acos": "math.acos",
"math.Acosh": "math.acosh",
"math.Atan": "math.atan",
"math.Atanh": "math.atanh",
"math.Atan2": "math.atan2",
"math.Cbrt": "math.cbrt",
"math.Ceil": "math.ceil",
"math.archCeil": "math.ceil",
"math.Cos": "math.cos",
"math.Cosh": "math.cosh",
"math.Erf": "math.erf",
"math.Erfc": "math.erfc",
"math.Exp": "math.exp",
"math.archExp": "math.exp",
"math.Expm1": "math.expm1",
"math.Exp2": "math.exp2",
"math.archExp2": "math.exp2",
"math.Floor": "math.floor",
"math.archFloor": "math.floor",
"math.Frexp": "math.frexp",
"math.Hypot": "math.hypot",
"math.archHypot": "math.hypot",
"math.Ldexp": "math.ldexp",
"math.Log": "math.log",
"math.archLog": "math.log",
"math.Log1p": "math.log1p",
"math.Log10": "math.log10",
"math.Log2": "math.log2",
"math.Max": "math.max",
"math.archMax": "math.max",
"math.Min": "math.min",
"math.archMin": "math.min",
"math.Mod": "math.mod",
"math.Modf": "math.modf",
"math.archModf": "math.modf",
"math.Pow": "math.pow",
"math.Remainder": "math.remainder",
"math.Sin": "math.sin",
"math.Sinh": "math.sinh",
"math.Sqrt": "math.sqrt",
"math.archSqrt": "math.sqrt",
"math.Tan": "math.tan",
"math.Tanh": "math.tanh",
"math.Trunc": "math.trunc",
"math.archTrunc": "math.trunc",
}
// createAlias implements the function (in the builder) as a call to the alias
// function.
func (b *builder) createAlias(alias llvm.Value) {
if b.Debug {
if b.fn.Syntax() != nil {
// Create debug info file if present.
b.difunc = b.attachDebugInfo(b.fn)
}
pos := b.program.Fset.Position(b.fn.Pos())
b.SetCurrentDebugLocation(uint(pos.Line), uint(pos.Column), b.difunc, llvm.Metadata{})
}
entryBlock := llvm.AddBasicBlock(b.llvmFn, "entry")
b.SetInsertPointAtEnd(entryBlock)
if b.llvmFn.Type() != alias.Type() {
b.addError(b.fn.Pos(), "alias function should have the same type as aliasee "+alias.Name())
b.CreateUnreachable()
return
}
result := b.CreateCall(alias, b.llvmFn.Params(), "")
if result.Type().TypeKind() == llvm.VoidTypeKind {
b.CreateRetVoid()
} else {
b.CreateRet(result)
}
}
+49
View File
@@ -101,6 +101,55 @@ func (b *builder) createSliceBoundsCheck(capacity, low, high, max llvm.Value, lo
b.createRuntimeAssert(outOfBounds, "slice", "slicePanic") b.createRuntimeAssert(outOfBounds, "slice", "slicePanic")
} }
// createSliceToArrayPointerCheck adds a check for slice-to-array pointer
// conversions. This conversion was added in Go 1.17. For details, see:
// https://tip.golang.org/ref/spec#Conversions_from_slice_to_array_pointer
func (b *builder) createSliceToArrayPointerCheck(sliceLen llvm.Value, arrayLen int64) {
// From the spec:
// > If the length of the slice is less than the length of the array, a
// > run-time panic occurs.
arrayLenValue := llvm.ConstInt(b.uintptrType, uint64(arrayLen), false)
isLess := b.CreateICmp(llvm.IntULT, sliceLen, arrayLenValue, "")
b.createRuntimeAssert(isLess, "slicetoarray", "sliceToArrayPointerPanic")
}
// createUnsafeSliceCheck inserts a runtime check used for unsafe.Slice. This
// function must panic if the ptr/len parameters are invalid.
func (b *builder) createUnsafeSliceCheck(ptr, len llvm.Value, lenType *types.Basic) {
// From the documentation of unsafe.Slice:
// > At run time, if len is negative, or if ptr is nil and len is not
// > zero, a run-time panic occurs.
// However, in practice, it is also necessary to check that the length is
// not too big that a GEP wouldn't be possible without wrapping the pointer.
// These two checks (non-negative and not too big) can be merged into one
// using an unsiged greater than.
// Make sure the len value is at least as big as a uintptr.
if len.Type().IntTypeWidth() < b.uintptrType.IntTypeWidth() {
if lenType.Info()&types.IsUnsigned != 0 {
len = b.CreateZExt(len, b.uintptrType, "")
} else {
len = b.CreateSExt(len, b.uintptrType, "")
}
}
// Determine the maximum slice size, and therefore the maximum value of the
// len parameter.
maxSize := b.maxSliceSize(ptr.Type().ElementType())
maxSizeValue := llvm.ConstInt(len.Type(), maxSize, false)
// Do the check. By using unsigned greater than for the length check, signed
// negative values are also checked (which are very large numbers when
// interpreted as signed values).
zero := llvm.ConstInt(len.Type(), 0, false)
lenOutOfBounds := b.CreateICmp(llvm.IntUGT, len, maxSizeValue, "")
ptrIsNil := b.CreateICmp(llvm.IntEQ, ptr, llvm.ConstNull(ptr.Type()), "")
lenIsNotZero := b.CreateICmp(llvm.IntNE, len, zero, "")
assert := b.CreateAnd(ptrIsNil, lenIsNotZero, "")
assert = b.CreateOr(assert, lenOutOfBounds, "")
b.createRuntimeAssert(assert, "unsafe.Slice", "unsafeSlicePanic")
}
// createChanBoundsCheck creates a bounds check before creating a new channel to // createChanBoundsCheck creates a bounds check before creating a new channel to
// check that the value is not too big for runtime.chanMake. // check that the value is not too big for runtime.chanMake.
func (b *builder) createChanBoundsCheck(elementSize uint64, bufSize llvm.Value, bufSizeType *types.Basic, pos token.Pos) { func (b *builder) createChanBoundsCheck(elementSize uint64, bufSize llvm.Value, bufSizeType *types.Basic, pos token.Pos) {
+120 -15
View File
@@ -23,7 +23,7 @@ import (
// Version of the compiler pacakge. Must be incremented each time the compiler // Version of the compiler pacakge. Must be incremented each time the compiler
// package changes in a way that affects the generated LLVM module. // package changes in a way that affects the generated LLVM module.
// This version is independent of the TinyGo version number. // This version is independent of the TinyGo version number.
const Version = 12 // last change: implement syscall.rawSyscallNoError const Version = 18 // last change: fix duplicated named structs
func init() { func init() {
llvm.InitializeAllTargets() llvm.InitializeAllTargets()
@@ -74,6 +74,7 @@ type compilerContext struct {
cu llvm.Metadata cu llvm.Metadata
difiles map[string]llvm.Metadata difiles map[string]llvm.Metadata
ditypes map[types.Type]llvm.Metadata ditypes map[types.Type]llvm.Metadata
llvmTypes map[types.Type]llvm.Type
machine llvm.TargetMachine machine llvm.TargetMachine
targetData llvm.TargetData targetData llvm.TargetData
intType llvm.Type intType llvm.Type
@@ -94,6 +95,7 @@ func newCompilerContext(moduleName string, machine llvm.TargetMachine, config *C
DumpSSA: dumpSSA, DumpSSA: dumpSSA,
difiles: make(map[string]llvm.Metadata), difiles: make(map[string]llvm.Metadata),
ditypes: make(map[types.Type]llvm.Metadata), ditypes: make(map[types.Type]llvm.Metadata),
llvmTypes: make(map[types.Type]llvm.Type),
machine: machine, machine: machine,
targetData: machine.CreateTargetData(), targetData: machine.CreateTargetData(),
astComments: map[string]*ast.CommentGroup{}, astComments: map[string]*ast.CommentGroup{},
@@ -315,10 +317,23 @@ func (c *compilerContext) getLLVMRuntimeType(name string) llvm.Type {
return c.getLLVMType(typ) return c.getLLVMType(typ)
} }
// getLLVMType creates and returns a LLVM type for a Go type. In the case of // getLLVMType returns a LLVM type for a Go type. It doesn't recreate already
// named struct types (or Go types implemented as named LLVM structs such as // created types. This is somewhat important for performance, but especially
// strings) it also creates it first if necessary. // important for named struct types (which should only be created once).
func (c *compilerContext) getLLVMType(goType types.Type) llvm.Type { func (c *compilerContext) getLLVMType(goType types.Type) llvm.Type {
// Try to load the LLVM type from the cache.
if t, ok := c.llvmTypes[goType]; ok {
return t
}
// Not already created, so adding this type to the cache.
llvmType := c.makeLLVMType(goType)
c.llvmTypes[goType] = llvmType
return llvmType
}
// makeLLVMType creates a LLVM type for a Go type. Don't call this, use
// getLLVMType instead.
func (c *compilerContext) makeLLVMType(goType types.Type) llvm.Type {
switch typ := goType.(type) { switch typ := goType.(type) {
case *types.Array: case *types.Array:
elemType := c.getLLVMType(typ.Elem()) elemType := c.getLLVMType(typ.Elem())
@@ -367,12 +382,10 @@ func (c *compilerContext) getLLVMType(goType types.Type) llvm.Type {
// LLVM. This is because it is otherwise impossible to create // LLVM. This is because it is otherwise impossible to create
// self-referencing types such as linked lists. // self-referencing types such as linked lists.
llvmName := typ.Obj().Pkg().Path() + "." + typ.Obj().Name() llvmName := typ.Obj().Pkg().Path() + "." + typ.Obj().Name()
llvmType := c.mod.GetTypeByName(llvmName) llvmType := c.ctx.StructCreateNamed(llvmName)
if llvmType.IsNil() { c.llvmTypes[goType] = llvmType // avoid infinite recursion
llvmType = c.ctx.StructCreateNamed(llvmName) underlying := c.getLLVMType(st)
underlying := c.getLLVMType(st) llvmType.StructSetBody(underlying.StructElementTypes(), false)
llvmType.StructSetBody(underlying.StructElementTypes(), false)
}
return llvmType return llvmType
} }
return c.getLLVMType(typ.Underlying()) return c.getLLVMType(typ.Underlying())
@@ -768,6 +781,29 @@ func (c *compilerContext) createPackage(irbuilder llvm.Builder, pkg *ssa.Package
} }
} }
} }
// Add forwarding functions for functions that would otherwise be
// implemented in assembly.
for _, name := range members {
member := pkg.Members[name]
switch member := member.(type) {
case *ssa.Function:
if member.Blocks != nil {
continue // external function
}
info := c.getFunctionInfo(member)
if aliasName, ok := stdlibAliases[info.linkName]; ok {
alias := c.mod.NamedFunction(aliasName)
if alias.IsNil() {
// Shouldn't happen, but perhaps best to just ignore.
// The error will be a link error, if there is an error.
continue
}
b := newBuilder(c, irbuilder, member)
b.createAlias(alias)
}
}
}
} }
// createFunction builds the LLVM IR implementation for this function. The // createFunction builds the LLVM IR implementation for this function. The
@@ -1270,6 +1306,38 @@ func (b *builder) createBuiltin(argTypes []types.Type, argValues []llvm.Value, c
case "ssa:wrapnilchk": case "ssa:wrapnilchk":
// TODO: do an actual nil check? // TODO: do an actual nil check?
return argValues[0], nil return argValues[0], nil
// Builtins from the unsafe package.
case "Add": // unsafe.Add
// This is basically just a GEP operation.
// Note: the pointer is always of type *i8.
ptr := argValues[0]
len := argValues[1]
return b.CreateGEP(ptr, []llvm.Value{len}, ""), nil
case "Slice": // unsafe.Slice
// This creates a slice from a pointer and a length.
// Note that the exception mentioned in the documentation (if the
// pointer and length are nil, the slice is also nil) is trivially
// already the case.
ptr := argValues[0]
len := argValues[1]
slice := llvm.Undef(b.ctx.StructType([]llvm.Type{
ptr.Type(),
b.uintptrType,
b.uintptrType,
}, false))
b.createUnsafeSliceCheck(ptr, len, argTypes[1].Underlying().(*types.Basic))
if len.Type().IntTypeWidth() < b.uintptrType.IntTypeWidth() {
// Too small, zero-extend len.
len = b.CreateZExt(len, b.uintptrType, "")
} else if len.Type().IntTypeWidth() > b.uintptrType.IntTypeWidth() {
// Too big, truncate len.
len = b.CreateTrunc(len, b.uintptrType, "")
}
slice = b.CreateInsertValue(slice, ptr, 0, "")
slice = b.CreateInsertValue(slice, len, 1, "")
slice = b.CreateInsertValue(slice, len, 2, "")
return slice, nil
default: default:
return llvm.Value{}, b.makeError(pos, "todo: builtin: "+callName) return llvm.Value{}, b.makeError(pos, "todo: builtin: "+callName)
} }
@@ -1298,6 +1366,11 @@ func (b *builder) createFunctionCall(instr *ssa.CallCommon) (llvm.Value, error)
return b.createMemoryCopyCall(fn, instr.Args) return b.createMemoryCopyCall(fn, instr.Args)
case name == "runtime.memzero": case name == "runtime.memzero":
return b.createMemoryZeroCall(instr.Args) return b.createMemoryZeroCall(instr.Args)
case name == "math.Ceil" || name == "math.Floor" || name == "math.Sqrt" || name == "math.Trunc":
result, ok := b.createMathOp(instr)
if ok {
return result, nil
}
case name == "device.Asm" || name == "device/arm.Asm" || name == "device/arm64.Asm" || name == "device/avr.Asm" || name == "device/riscv.Asm": case name == "device.Asm" || name == "device/arm.Asm" || name == "device/arm64.Asm" || name == "device/avr.Asm" || name == "device/riscv.Asm":
return b.createInlineAsm(instr.Args) return b.createInlineAsm(instr.Args)
case name == "device.AsmFull" || name == "device/arm.AsmFull" || name == "device/arm64.AsmFull" || name == "device/avr.AsmFull" || name == "device/riscv.AsmFull": case name == "device.AsmFull" || name == "device/arm.AsmFull" || name == "device/arm64.AsmFull" || name == "device/avr.AsmFull" || name == "device/riscv.AsmFull":
@@ -1411,6 +1484,28 @@ func (b *builder) getValue(expr ssa.Value) llvm.Value {
} }
} }
// maxSliceSize determines the maximum size a slice of the given element type
// can be.
func (c *compilerContext) maxSliceSize(elementType llvm.Type) uint64 {
// Calculate ^uintptr(0), which is the max value that fits in uintptr.
maxPointerValue := llvm.ConstNot(llvm.ConstInt(c.uintptrType, 0, false)).ZExtValue()
// Calculate (^uint(0))/2, which is the max value that fits in an int.
maxIntegerValue := llvm.ConstNot(llvm.ConstInt(c.intType, 0, false)).ZExtValue() / 2
// Determine the maximum allowed size for a slice. The biggest possible
// pointer (starting from 0) would be maxPointerValue*sizeof(elementType) so
// divide by the element type to get the real maximum size.
maxSize := maxPointerValue / c.targetData.TypeAllocSize(elementType)
// len(slice) is an int. Make sure the length remains small enough to fit in
// an int.
if maxSize > maxIntegerValue {
maxSize = maxIntegerValue
}
return maxSize
}
// createExpr translates a Go SSA expression to LLVM IR. This can be zero, one, // createExpr translates a Go SSA expression to LLVM IR. This can be zero, one,
// or multiple LLVM IR instructions and/or runtime calls. // or multiple LLVM IR instructions and/or runtime calls.
func (b *builder) createExpr(expr ssa.Value) (llvm.Value, error) { func (b *builder) createExpr(expr ssa.Value) (llvm.Value, error) {
@@ -1624,10 +1719,8 @@ func (b *builder) createExpr(expr ssa.Value) (llvm.Value, error) {
elemSize := b.targetData.TypeAllocSize(llvmElemType) elemSize := b.targetData.TypeAllocSize(llvmElemType)
elemSizeValue := llvm.ConstInt(b.uintptrType, elemSize, false) elemSizeValue := llvm.ConstInt(b.uintptrType, elemSize, false)
// Calculate (^uintptr(0)) >> 1, which is the max value that fits in maxSize := b.maxSliceSize(llvmElemType)
// uintptr if uintptr were signed. if elemSize > maxSize {
maxSize := llvm.ConstLShr(llvm.ConstNot(llvm.ConstInt(b.uintptrType, 0, false)), llvm.ConstInt(b.uintptrType, 1, false))
if elemSize > maxSize.ZExtValue() {
// This seems to be checked by the typechecker already, but let's // This seems to be checked by the typechecker already, but let's
// check it again just to be sure. // check it again just to be sure.
return llvm.Value{}, b.makeError(expr.Pos(), fmt.Sprintf("slice element type is too big (%v bytes)", elemSize)) return llvm.Value{}, b.makeError(expr.Pos(), fmt.Sprintf("slice element type is too big (%v bytes)", elemSize))
@@ -1636,7 +1729,8 @@ func (b *builder) createExpr(expr ssa.Value) (llvm.Value, error) {
// Bounds checking. // Bounds checking.
lenType := expr.Len.Type().Underlying().(*types.Basic) lenType := expr.Len.Type().Underlying().(*types.Basic)
capType := expr.Cap.Type().Underlying().(*types.Basic) capType := expr.Cap.Type().Underlying().(*types.Basic)
b.createSliceBoundsCheck(maxSize, sliceLen, sliceCap, sliceCap, lenType, capType, capType) maxSizeValue := llvm.ConstInt(b.uintptrType, maxSize, false)
b.createSliceBoundsCheck(maxSizeValue, sliceLen, sliceCap, sliceCap, lenType, capType, capType)
// Allocate the backing array. // Allocate the backing array.
sliceCapCast, err := b.createConvert(expr.Cap.Type(), types.Typ[types.Uintptr], sliceCap, expr.Pos()) sliceCapCast, err := b.createConvert(expr.Cap.Type(), types.Typ[types.Uintptr], sliceCap, expr.Pos())
@@ -1879,6 +1973,17 @@ func (b *builder) createExpr(expr ssa.Value) (llvm.Value, error) {
default: default:
return llvm.Value{}, b.makeError(expr.Pos(), "unknown slice type: "+typ.String()) return llvm.Value{}, b.makeError(expr.Pos(), "unknown slice type: "+typ.String())
} }
case *ssa.SliceToArrayPointer:
// Conversion from a slice to an array pointer, as the name clearly
// says. This requires a runtime check to make sure the slice is at
// least as big as the array.
slice := b.getValue(expr.X)
sliceLen := b.CreateExtractValue(slice, 1, "")
arrayLen := expr.Type().Underlying().(*types.Pointer).Elem().Underlying().(*types.Array).Len()
b.createSliceToArrayPointerCheck(sliceLen, arrayLen)
ptr := b.CreateExtractValue(slice, 0, "")
ptr = b.CreateBitCast(ptr, b.getLLVMType(expr.Type()), "")
return ptr, nil
case *ssa.TypeAssert: case *ssa.TypeAssert:
return b.createTypeAssert(expr), nil return b.createTypeAssert(expr), nil
case *ssa.UnOp: case *ssa.UnOp:
+17 -4
View File
@@ -9,6 +9,7 @@ import (
"testing" "testing"
"github.com/tinygo-org/tinygo/compileopts" "github.com/tinygo-org/tinygo/compileopts"
"github.com/tinygo-org/tinygo/goenv"
"github.com/tinygo-org/tinygo/loader" "github.com/tinygo-org/tinygo/loader"
"tinygo.org/x/go-llvm" "tinygo.org/x/go-llvm"
) )
@@ -16,6 +17,11 @@ import (
// Pass -update to go test to update the output of the test files. // Pass -update to go test to update the output of the test files.
var flagUpdate = flag.Bool("update", false, "update tests based on test output") var flagUpdate = flag.Bool("update", false, "update tests based on test output")
type testCase struct {
file string
target string
}
// Basic tests for the compiler. Build some Go files and compare the output with // Basic tests for the compiler. Build some Go files and compare the output with
// the expected LLVM IR for regression testing. // the expected LLVM IR for regression testing.
func TestCompiler(t *testing.T) { func TestCompiler(t *testing.T) {
@@ -34,10 +40,7 @@ func TestCompiler(t *testing.T) {
t.Skip("compiler tests require LLVM 11 or above, got LLVM ", llvm.Version) t.Skip("compiler tests require LLVM 11 or above, got LLVM ", llvm.Version)
} }
tests := []struct { tests := []testCase{
file string
target string
}{
{"basic.go", ""}, {"basic.go", ""},
{"pointer.go", ""}, {"pointer.go", ""},
{"slice.go", ""}, {"slice.go", ""},
@@ -48,6 +51,16 @@ func TestCompiler(t *testing.T) {
{"pragma.go", ""}, {"pragma.go", ""},
{"goroutine.go", "wasm"}, {"goroutine.go", "wasm"},
{"goroutine.go", "cortex-m-qemu"}, {"goroutine.go", "cortex-m-qemu"},
{"intrinsics.go", "cortex-m-qemu"},
{"intrinsics.go", "wasm"},
}
_, minor, err := goenv.GetGorootVersion(goenv.Get("GOROOT"))
if err != nil {
t.Fatal("could not read Go version:", err)
}
if minor >= 17 {
tests = append(tests, testCase{"go1.17.go", ""})
} }
for _, tc := range tests { for _, tc := range tests {
+54
View File
@@ -48,3 +48,57 @@ func (b *builder) createMemoryZeroCall(args []ssa.Value) (llvm.Value, error) {
b.CreateCall(llvmFn, params, "") b.CreateCall(llvmFn, params, "")
return llvm.Value{}, nil return llvm.Value{}, nil
} }
var mathToLLVMMapping = map[string]string{
"math.Sqrt": "llvm.sqrt.f64",
"math.Floor": "llvm.floor.f64",
"math.Ceil": "llvm.ceil.f64",
"math.Trunc": "llvm.trunc.f64",
}
// createMathOp tries to lower the given call as a LLVM math intrinsic, if
// possible. It returns the call result if possible, and a boolean whether it
// succeeded. If it doesn't succeed, the architecture doesn't support the given
// intrinsic.
func (b *builder) createMathOp(call *ssa.CallCommon) (llvm.Value, bool) {
// Check whether this intrinsic is supported on the given GOARCH.
// If it is unsupported, this can have two reasons:
//
// 1. LLVM can expand the intrinsic inline (using float instructions), but
// the result doesn't pass the tests of the math package.
// 2. LLVM cannot expand the intrinsic inline, will therefore lower it as a
// libm function call, but the libm function call also fails the math
// package tests.
//
// Whatever the implementation, it must pass the tests in the math package
// so unfortunately only the below intrinsic+architecture combinations are
// supported.
name := call.StaticCallee().RelString(nil)
switch name {
case "math.Ceil", "math.Floor", "math.Trunc":
if b.GOARCH != "wasm" && b.GOARCH != "arm64" {
return llvm.Value{}, false
}
case "math.Sqrt":
if b.GOARCH != "wasm" && b.GOARCH != "amd64" && b.GOARCH != "386" {
return llvm.Value{}, false
}
default:
return llvm.Value{}, false // only the above functions are supported.
}
llvmFn := b.mod.NamedFunction(mathToLLVMMapping[name])
if llvmFn.IsNil() {
// The intrinsic doesn't exist yet, so declare it.
// At the moment, all supported intrinsics have the form "double
// foo(double %x)" so we can hardcode the signature here.
llvmType := llvm.FunctionType(b.ctx.DoubleType(), []llvm.Type{b.ctx.DoubleType()}, false)
llvmFn = llvm.AddFunction(b.mod, mathToLLVMMapping[name], llvmType)
}
// Create a call to the intrinsic.
args := make([]llvm.Value, len(call.Args))
for i, arg := range call.Args {
args[i] = b.getValue(arg)
}
return b.CreateCall(llvmFn, args, ""), true
}
+15
View File
@@ -55,3 +55,18 @@ func complexMul(x, y complex64) complex64 {
} }
// TODO: complexDiv (requires runtime call) // TODO: complexDiv (requires runtime call)
// A type 'kv' also exists in function foo. Test that these two types don't
// conflict with each other.
type kv struct {
v float32
}
func foo(a *kv) {
// Define a new 'kv' type.
type kv struct {
v byte
}
// Use this type.
func(b *kv) {}(nil)
}
+14
View File
@@ -3,6 +3,9 @@ source_filename = "basic.go"
target datalayout = "e-m:e-p:32:32-i64:64-n32:64-S128" target datalayout = "e-m:e-p:32:32-i64:64-n32:64-S128"
target triple = "wasm32--wasi" target triple = "wasm32--wasi"
%main.kv = type { float }
%main.kv.0 = type { i8 }
declare noalias nonnull i8* @runtime.alloc(i32, i8*, i8*) declare noalias nonnull i8* @runtime.alloc(i32, i8*, i8*)
define hidden void @main.init(i8* %context, i8* %parentHandle) unnamed_addr { define hidden void @main.init(i8* %context, i8* %parentHandle) unnamed_addr {
@@ -98,3 +101,14 @@ entry:
%7 = insertvalue { float, float } %6, float %5, 1 %7 = insertvalue { float, float } %6, float %5, 1
ret { float, float } %7 ret { float, float } %7
} }
define hidden void @main.foo(%main.kv* dereferenceable_or_null(4) %a, i8* %context, i8* %parentHandle) unnamed_addr {
entry:
call void @"main.foo$1"(%main.kv.0* null, i8* undef, i8* undef)
ret void
}
define hidden void @"main.foo$1"(%main.kv.0* dereferenceable_or_null(1) %b, i8* %context, i8* %parentHandle) unnamed_addr {
entry:
ret void
}
+41
View File
@@ -0,0 +1,41 @@
package main
// Test changes to the language introduced in Go 1.17.
// For details, see: https://tip.golang.org/doc/go1.17#language
// These tests should be merged into the regular slice tests once Go 1.17 is the
// minimun Go version for TinyGo.
import "unsafe"
func Add32(p unsafe.Pointer, len int) unsafe.Pointer {
return unsafe.Add(p, len)
}
func Add64(p unsafe.Pointer, len int64) unsafe.Pointer {
return unsafe.Add(p, len)
}
func SliceToArray(s []int) *[4]int {
return (*[4]int)(s)
}
func SliceToArrayConst() *[4]int {
s := make([]int, 6)
return (*[4]int)(s)
}
func SliceInt(ptr *int, len int) []int {
return unsafe.Slice(ptr, len)
}
func SliceUint16(ptr *byte, len uint16) []byte {
return unsafe.Slice(ptr, len)
}
func SliceUint64(ptr *int, len uint64) []int {
return unsafe.Slice(ptr, len)
}
func SliceInt64(ptr *int, len int64) []int {
return unsafe.Slice(ptr, len)
}
+136
View File
@@ -0,0 +1,136 @@
; ModuleID = 'go1.17.go'
source_filename = "go1.17.go"
target datalayout = "e-m:e-p:32:32-i64:64-n32:64-S128"
target triple = "wasm32--wasi"
declare noalias nonnull i8* @runtime.alloc(i32, i8*, i8*)
define hidden void @main.init(i8* %context, i8* %parentHandle) unnamed_addr {
entry:
ret void
}
define hidden i8* @main.Add32(i8* %p, i32 %len, i8* %context, i8* %parentHandle) unnamed_addr {
entry:
%0 = getelementptr i8, i8* %p, i32 %len
ret i8* %0
}
define hidden i8* @main.Add64(i8* %p, i64 %len, i8* %context, i8* %parentHandle) unnamed_addr {
entry:
%0 = trunc i64 %len to i32
%1 = getelementptr i8, i8* %p, i32 %0
ret i8* %1
}
define hidden [4 x i32]* @main.SliceToArray(i32* %s.data, i32 %s.len, i32 %s.cap, i8* %context, i8* %parentHandle) unnamed_addr {
entry:
%0 = icmp ult i32 %s.len, 4
br i1 %0, label %slicetoarray.throw, label %slicetoarray.next
slicetoarray.throw: ; preds = %entry
call void @runtime.sliceToArrayPointerPanic(i8* undef, i8* null)
unreachable
slicetoarray.next: ; preds = %entry
%1 = bitcast i32* %s.data to [4 x i32]*
ret [4 x i32]* %1
}
declare void @runtime.sliceToArrayPointerPanic(i8*, i8*)
define hidden [4 x i32]* @main.SliceToArrayConst(i8* %context, i8* %parentHandle) unnamed_addr {
entry:
%makeslice = call i8* @runtime.alloc(i32 24, i8* undef, i8* null)
br i1 false, label %slicetoarray.throw, label %slicetoarray.next
slicetoarray.throw: ; preds = %entry
unreachable
slicetoarray.next: ; preds = %entry
%0 = bitcast i8* %makeslice to [4 x i32]*
ret [4 x i32]* %0
}
define hidden { i32*, i32, i32 } @main.SliceInt(i32* dereferenceable_or_null(4) %ptr, i32 %len, i8* %context, i8* %parentHandle) unnamed_addr {
entry:
%0 = icmp ugt i32 %len, 1073741823
%1 = icmp eq i32* %ptr, null
%2 = icmp ne i32 %len, 0
%3 = and i1 %1, %2
%4 = or i1 %3, %0
br i1 %4, label %unsafe.Slice.throw, label %unsafe.Slice.next
unsafe.Slice.throw: ; preds = %entry
call void @runtime.unsafeSlicePanic(i8* undef, i8* null)
unreachable
unsafe.Slice.next: ; preds = %entry
%5 = insertvalue { i32*, i32, i32 } undef, i32* %ptr, 0
%6 = insertvalue { i32*, i32, i32 } %5, i32 %len, 1
%7 = insertvalue { i32*, i32, i32 } %6, i32 %len, 2
ret { i32*, i32, i32 } %7
}
declare void @runtime.unsafeSlicePanic(i8*, i8*)
define hidden { i8*, i32, i32 } @main.SliceUint16(i8* dereferenceable_or_null(1) %ptr, i16 %len, i8* %context, i8* %parentHandle) unnamed_addr {
entry:
%0 = icmp eq i8* %ptr, null
%1 = icmp ne i16 %len, 0
%2 = and i1 %0, %1
br i1 %2, label %unsafe.Slice.throw, label %unsafe.Slice.next
unsafe.Slice.throw: ; preds = %entry
call void @runtime.unsafeSlicePanic(i8* undef, i8* null)
unreachable
unsafe.Slice.next: ; preds = %entry
%3 = zext i16 %len to i32
%4 = insertvalue { i8*, i32, i32 } undef, i8* %ptr, 0
%5 = insertvalue { i8*, i32, i32 } %4, i32 %3, 1
%6 = insertvalue { i8*, i32, i32 } %5, i32 %3, 2
ret { i8*, i32, i32 } %6
}
define hidden { i32*, i32, i32 } @main.SliceUint64(i32* dereferenceable_or_null(4) %ptr, i64 %len, i8* %context, i8* %parentHandle) unnamed_addr {
entry:
%0 = icmp ugt i64 %len, 1073741823
%1 = icmp eq i32* %ptr, null
%2 = icmp ne i64 %len, 0
%3 = and i1 %1, %2
%4 = or i1 %3, %0
br i1 %4, label %unsafe.Slice.throw, label %unsafe.Slice.next
unsafe.Slice.throw: ; preds = %entry
call void @runtime.unsafeSlicePanic(i8* undef, i8* null)
unreachable
unsafe.Slice.next: ; preds = %entry
%5 = trunc i64 %len to i32
%6 = insertvalue { i32*, i32, i32 } undef, i32* %ptr, 0
%7 = insertvalue { i32*, i32, i32 } %6, i32 %5, 1
%8 = insertvalue { i32*, i32, i32 } %7, i32 %5, 2
ret { i32*, i32, i32 } %8
}
define hidden { i32*, i32, i32 } @main.SliceInt64(i32* dereferenceable_or_null(4) %ptr, i64 %len, i8* %context, i8* %parentHandle) unnamed_addr {
entry:
%0 = icmp ugt i64 %len, 1073741823
%1 = icmp eq i32* %ptr, null
%2 = icmp ne i64 %len, 0
%3 = and i1 %1, %2
%4 = or i1 %3, %0
br i1 %4, label %unsafe.Slice.throw, label %unsafe.Slice.next
unsafe.Slice.throw: ; preds = %entry
call void @runtime.unsafeSlicePanic(i8* undef, i8* null)
unreachable
unsafe.Slice.next: ; preds = %entry
%5 = trunc i64 %len to i32
%6 = insertvalue { i32*, i32, i32 } undef, i32* %ptr, 0
%7 = insertvalue { i32*, i32, i32 } %6, i32 %5, 1
%8 = insertvalue { i32*, i32, i32 } %7, i32 %5, 2
ret { i32*, i32, i32 } %8
}
+27
View File
@@ -0,0 +1,27 @@
; ModuleID = 'intrinsics.go'
source_filename = "intrinsics.go"
target datalayout = "e-m:e-p:32:32-Fi8-i64:64-v128:64:128-a:0:32-n32-S64"
target triple = "armv7m-none-eabi"
declare noalias nonnull i8* @runtime.alloc(i32, i8*, i8*)
define hidden void @main.init(i8* %context, i8* %parentHandle) unnamed_addr {
entry:
ret void
}
define hidden double @main.mySqrt(double %x, i8* %context, i8* %parentHandle) unnamed_addr {
entry:
%0 = call double @math.Sqrt(double %x, i8* undef, i8* undef)
ret double %0
}
declare double @math.Sqrt(double, i8*, i8*)
define hidden double @main.myTrunc(double %x, i8* %context, i8* %parentHandle) unnamed_addr {
entry:
%0 = call double @math.Trunc(double %x, i8* undef, i8* undef)
ret double %0
}
declare double @math.Trunc(double, i8*, i8*)
+31
View File
@@ -0,0 +1,31 @@
; ModuleID = 'intrinsics.go'
source_filename = "intrinsics.go"
target datalayout = "e-m:e-p:32:32-i64:64-n32:64-S128"
target triple = "wasm32--wasi"
declare noalias nonnull i8* @runtime.alloc(i32, i8*, i8*)
define hidden void @main.init(i8* %context, i8* %parentHandle) unnamed_addr {
entry:
ret void
}
define hidden double @main.mySqrt(double %x, i8* %context, i8* %parentHandle) unnamed_addr {
entry:
%0 = call double @llvm.sqrt.f64(double %x)
ret double %0
}
; Function Attrs: nounwind readnone speculatable willreturn
declare double @llvm.sqrt.f64(double) #0
define hidden double @main.myTrunc(double %x, i8* %context, i8* %parentHandle) unnamed_addr {
entry:
%0 = call double @llvm.trunc.f64(double %x)
ret double %0
}
; Function Attrs: nounwind readnone speculatable willreturn
declare double @llvm.trunc.f64(double) #0
attributes #0 = { nounwind readnone speculatable willreturn }
+14
View File
@@ -0,0 +1,14 @@
package main
// Test how intrinsics are lowered: either as regular calls to the math
// functions or as LLVM builtins (such as llvm.sqrt.f64).
import "math"
func mySqrt(x float64) float64 {
return math.Sqrt(x)
}
func myTrunc(x float64) float64 {
return math.Trunc(x)
}
+18
View File
@@ -23,3 +23,21 @@ func sliceAppendSlice(ints, added []int) []int {
func sliceCopy(dst, src []int) int { func sliceCopy(dst, src []int) int {
return copy(dst, src) return copy(dst, src)
} }
// Test bounds checking in *ssa.MakeSlice instruction.
func makeByteSlice(len int) []byte {
return make([]byte, len)
}
func makeInt16Slice(len int) []int16 {
return make([]int16, len)
}
func makeArraySlice(len int) [][3]byte {
return make([][3]byte, len) // slice with element size of 3
}
func makeInt32Slice(len int) []int32 {
return make([]int32, len)
}
+76
View File
@@ -86,3 +86,79 @@ entry:
} }
declare i32 @runtime.sliceCopy(i8* nocapture writeonly, i8* nocapture readonly, i32, i32, i32, i8*, i8*) declare i32 @runtime.sliceCopy(i8* nocapture writeonly, i8* nocapture readonly, i32, i32, i32, i8*, i8*)
define hidden { i8*, i32, i32 } @main.makeByteSlice(i32 %len, i8* %context, i8* %parentHandle) unnamed_addr {
entry:
%slice.maxcap = icmp slt i32 %len, 0
br i1 %slice.maxcap, label %slice.throw, label %slice.next
slice.throw: ; preds = %entry
call void @runtime.slicePanic(i8* undef, i8* null)
unreachable
slice.next: ; preds = %entry
%makeslice.buf = call i8* @runtime.alloc(i32 %len, i8* undef, i8* null)
%0 = insertvalue { i8*, i32, i32 } undef, i8* %makeslice.buf, 0
%1 = insertvalue { i8*, i32, i32 } %0, i32 %len, 1
%2 = insertvalue { i8*, i32, i32 } %1, i32 %len, 2
ret { i8*, i32, i32 } %2
}
declare void @runtime.slicePanic(i8*, i8*)
define hidden { i16*, i32, i32 } @main.makeInt16Slice(i32 %len, i8* %context, i8* %parentHandle) unnamed_addr {
entry:
%slice.maxcap = icmp slt i32 %len, 0
br i1 %slice.maxcap, label %slice.throw, label %slice.next
slice.throw: ; preds = %entry
call void @runtime.slicePanic(i8* undef, i8* null)
unreachable
slice.next: ; preds = %entry
%makeslice.cap = shl i32 %len, 1
%makeslice.buf = call i8* @runtime.alloc(i32 %makeslice.cap, i8* undef, i8* null)
%makeslice.array = bitcast i8* %makeslice.buf to i16*
%0 = insertvalue { i16*, i32, i32 } undef, i16* %makeslice.array, 0
%1 = insertvalue { i16*, i32, i32 } %0, i32 %len, 1
%2 = insertvalue { i16*, i32, i32 } %1, i32 %len, 2
ret { i16*, i32, i32 } %2
}
define hidden { [3 x i8]*, i32, i32 } @main.makeArraySlice(i32 %len, i8* %context, i8* %parentHandle) unnamed_addr {
entry:
%slice.maxcap = icmp ugt i32 %len, 1431655765
br i1 %slice.maxcap, label %slice.throw, label %slice.next
slice.throw: ; preds = %entry
call void @runtime.slicePanic(i8* undef, i8* null)
unreachable
slice.next: ; preds = %entry
%makeslice.cap = mul i32 %len, 3
%makeslice.buf = call i8* @runtime.alloc(i32 %makeslice.cap, i8* undef, i8* null)
%makeslice.array = bitcast i8* %makeslice.buf to [3 x i8]*
%0 = insertvalue { [3 x i8]*, i32, i32 } undef, [3 x i8]* %makeslice.array, 0
%1 = insertvalue { [3 x i8]*, i32, i32 } %0, i32 %len, 1
%2 = insertvalue { [3 x i8]*, i32, i32 } %1, i32 %len, 2
ret { [3 x i8]*, i32, i32 } %2
}
define hidden { i32*, i32, i32 } @main.makeInt32Slice(i32 %len, i8* %context, i8* %parentHandle) unnamed_addr {
entry:
%slice.maxcap = icmp ugt i32 %len, 1073741823
br i1 %slice.maxcap, label %slice.throw, label %slice.next
slice.throw: ; preds = %entry
call void @runtime.slicePanic(i8* undef, i8* null)
unreachable
slice.next: ; preds = %entry
%makeslice.cap = shl i32 %len, 2
%makeslice.buf = call i8* @runtime.alloc(i32 %makeslice.cap, i8* undef, i8* null)
%makeslice.array = bitcast i8* %makeslice.buf to i32*
%0 = insertvalue { i32*, i32, i32 } undef, i32* %makeslice.array, 0
%1 = insertvalue { i32*, i32, i32 } %0, i32 %len, 1
%2 = insertvalue { i32*, i32, i32 } %1, i32 %len, 2
ret { i32*, i32, i32 } %2
}
+3 -3
View File
@@ -1,6 +1,6 @@
module github.com/tinygo-org/tinygo module github.com/tinygo-org/tinygo
go 1.13 go 1.15
require ( require (
github.com/blakesmith/ar v0.0.0-20150311145944-8bd4349a67f2 github.com/blakesmith/ar v0.0.0-20150311145944-8bd4349a67f2
@@ -10,7 +10,7 @@ require (
github.com/marcinbor85/gohex v0.0.0-20200531091804-343a4b548892 github.com/marcinbor85/gohex v0.0.0-20200531091804-343a4b548892
github.com/mattn/go-colorable v0.1.8 github.com/mattn/go-colorable v0.1.8
go.bug.st/serial v1.1.2 go.bug.st/serial v1.1.2
golang.org/x/sys v0.0.0-20210113181707-4bcb84eeeb78 golang.org/x/sys v0.0.0-20210510120138-977fb7262007
golang.org/x/tools v0.0.0-20200216192241-b320d3a0f5a2 golang.org/x/tools v0.1.6-0.20210813165731-45389f592fe9
tinygo.org/x/go-llvm v0.0.0-20210325115028-e7b85195e81c tinygo.org/x/go-llvm v0.0.0-20210325115028-e7b85195e81c
) )
+17 -6
View File
@@ -30,6 +30,8 @@ github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Ky
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/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/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/yuin/goldmark v1.3.5 h1:dPmz1Snjq0kmkz159iL7S6WzdahUTHnHB5M56WFVifs=
github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
go.bug.st/serial v1.0.0 h1:ogEPzrllCsnG00EqKRjeYvPRsO7NJW6DqykzkdD6E/k= go.bug.st/serial v1.0.0 h1:ogEPzrllCsnG00EqKRjeYvPRsO7NJW6DqykzkdD6E/k=
go.bug.st/serial v1.0.0/go.mod h1:rpXPISGjuNjPTRTcMlxi9lN6LoIPxd1ixVjBd8aSk/Q= go.bug.st/serial v1.0.0/go.mod h1:rpXPISGjuNjPTRTcMlxi9lN6LoIPxd1ixVjBd8aSk/Q=
go.bug.st/serial v1.1.2 h1:6xDpbta8KJ+VLRTeM8ghhxXRMLE/Lr8h9iDKwydarAY= go.bug.st/serial v1.1.2 h1:6xDpbta8KJ+VLRTeM8ghhxXRMLE/Lr8h9iDKwydarAY=
@@ -38,23 +40,32 @@ golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACk
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/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 h1:WG0RUwxtNT4qqaXX3DPA8zHFNm/D9xaBpxzHt1WcA/E=
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-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-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191128015809-6d18c012aee9 h1:ZBzSG/7F4eNKz2L3GE9o300RX0Az1Bw5HF7PDraD+qU=
golang.org/x/sys v0.0.0-20191128015809-6d18c012aee9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200909081042-eff7692f9009/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200909081042-eff7692f9009/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210113181707-4bcb84eeeb78 h1:nVuTkr9L6Bq62qpUqKo/RnZCFfzDBL0bYo6w9OJUqZY= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210113181707-4bcb84eeeb78/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210113181707-4bcb84eeeb78/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210510120138-977fb7262007 h1:gG67DSER+11cZvqIMb8S8bt0vZtiN6xWYARwirrOSfE=
golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/tools v0.0.0-20200216192241-b320d3a0f5a2 h1:0sfSpGSa544Fwnbot3Oxq/U6SXqjty6Jy/3wRhVS7ig= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/tools v0.0.0-20200216192241-b320d3a0f5a2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898 h1:/atklqdjdhuosWIl6AIbOeHJjicWYPqR9bpxqxYG2pA= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.1.6-0.20210813165731-45389f592fe9 h1:nvvuMxmx1q0gfRki3T0hjG8EwAcVCs91oWAXvyt4zhI=
golang.org/x/tools v0.1.6-0.20210813165731-45389f592fe9/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/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= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
tinygo.org/x/go-llvm v0.0.0-20210308112806-9ef958b6bed4 h1:CMUHxVTb+UuUePuMf8vkWjZ3gTp9BBK91KrgOCwoNHs= tinygo.org/x/go-llvm v0.0.0-20210308112806-9ef958b6bed4 h1:CMUHxVTb+UuUePuMf8vkWjZ3gTp9BBK91KrgOCwoNHs=
+4 -4
View File
@@ -48,7 +48,10 @@ func GetGorootVersion(goroot string) (major, minor int, err error) {
// toolchain for the given GOROOT path. It is usually of the form `go1.x.y` but // 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). // can have some variations (for beta releases, for example).
func GorootVersionString(goroot string) (string, error) { func GorootVersionString(goroot string) (string, error) {
if data, err := ioutil.ReadFile(filepath.Join( if data, err := ioutil.ReadFile(filepath.Join(goroot, "VERSION")); err == nil {
return string(data), nil
} else if data, err := ioutil.ReadFile(filepath.Join(
goroot, "src", "runtime", "internal", "sys", "zversion.go")); err == nil { goroot, "src", "runtime", "internal", "sys", "zversion.go")); err == nil {
r := regexp.MustCompile("const TheVersion = `(.*)`") r := regexp.MustCompile("const TheVersion = `(.*)`")
@@ -59,9 +62,6 @@ func GorootVersionString(goroot string) (string, error) {
return string(matches[1]), nil return string(matches[1]), nil
} else if data, err := ioutil.ReadFile(filepath.Join(goroot, "VERSION")); err == nil {
return string(data), nil
} else { } else {
return "", err return "", err
} }
-4
View File
@@ -590,18 +590,14 @@ func (r *runner) run(fn *function, params []value, parentMem *memoryView, indent
// GetElementPtr does pointer arithmetic, changing the offset of the // GetElementPtr does pointer arithmetic, changing the offset of the
// pointer into the underlying object. // pointer into the underlying object.
var offset uint64 var offset uint64
var gepOperands []uint64
for i := 2; i < len(operands); i += 2 { for i := 2; i < len(operands); i += 2 {
index := operands[i].Uint() index := operands[i].Uint()
elementSize := operands[i+1].Uint() elementSize := operands[i+1].Uint()
if int64(elementSize) < 0 { if int64(elementSize) < 0 {
// This is a struct field. // This is a struct field.
// The field number is encoded by flipping all the bits.
gepOperands = append(gepOperands, ^elementSize)
offset += index offset += index
} else { } else {
// This is a normal GEP, probably an array index. // This is a normal GEP, probably an array index.
gepOperands = append(gepOperands, index)
offset += elementSize * index offset += elementSize * index
} }
} }
+1 -1
View File
@@ -208,7 +208,7 @@ func mergeDirectory(goroot, tinygoroot, tmpgoroot, importPath string, overrides
// with the TinyGo version. This is the case on some targets. // with the TinyGo version. This is the case on some targets.
func needsSyscallPackage(buildTags []string) bool { func needsSyscallPackage(buildTags []string) bool {
for _, tag := range buildTags { for _, tag := range buildTags {
if tag == "baremetal" || tag == "darwin" || tag == "nintendoswitch" || tag == "wasi" { if tag == "baremetal" || tag == "darwin" || tag == "nintendoswitch" || tag == "tinygo.wasm" {
return true return true
} }
} }
+85 -6
View File
@@ -15,6 +15,7 @@ import (
"path/filepath" "path/filepath"
"regexp" "regexp"
"runtime" "runtime"
"runtime/pprof"
"strconv" "strconv"
"strings" "strings"
"sync/atomic" "sync/atomic"
@@ -158,7 +159,7 @@ func Build(pkgName, outpath string, options *compileopts.Options) error {
// Test runs the tests in the given package. Returns whether the test passed and // Test runs the tests in the given package. Returns whether the test passed and
// possibly an error if the test failed to run. // possibly an error if the test failed to run.
func Test(pkgName string, options *compileopts.Options, testCompileOnly bool, outpath string) (bool, error) { func Test(pkgName string, options *compileopts.Options, testCompileOnly, testVerbose bool, outpath string) (bool, error) {
options.TestConfig.CompileTestBinary = true options.TestConfig.CompileTestBinary = true
config, err := builder.NewConfig(options) config, err := builder.NewConfig(options)
if err != nil { if err != nil {
@@ -184,7 +185,7 @@ func Test(pkgName string, options *compileopts.Options, testCompileOnly bool, ou
// Run the test. // Run the test.
start := time.Now() start := time.Now()
var err error var err error
passed, err = runPackageTest(config, result) passed, err = runPackageTest(config, result, testVerbose)
if err != nil { if err != nil {
return err return err
} }
@@ -210,10 +211,14 @@ func Test(pkgName string, options *compileopts.Options, testCompileOnly bool, ou
// runPackageTest runs a test binary that was previously built. The return // runPackageTest runs a test binary that was previously built. The return
// values are whether the test passed and any errors encountered while trying to // values are whether the test passed and any errors encountered while trying to
// run the binary. // run the binary.
func runPackageTest(config *compileopts.Config, result builder.BuildResult) (bool, error) { func runPackageTest(config *compileopts.Config, result builder.BuildResult, testVerbose bool) (bool, error) {
if len(config.Target.Emulator) == 0 { if len(config.Target.Emulator) == 0 {
// Run directly. // Run directly.
cmd := executeCommand(config.Options, result.Binary) var flags []string
if testVerbose {
flags = append(flags, "-test.v")
}
cmd := executeCommand(config.Options, result.Binary, flags...)
cmd.Stdout = os.Stdout cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr cmd.Stderr = os.Stderr
cmd.Dir = result.MainDir cmd.Dir = result.MainDir
@@ -229,6 +234,7 @@ func runPackageTest(config *compileopts.Config, result builder.BuildResult) (boo
return true, nil return true, nil
} else { } else {
// Run in an emulator. // Run in an emulator.
// TODO: pass the -test.v flag if needed.
args := append(config.Target.Emulator[1:], result.Binary) args := append(config.Target.Emulator[1:], result.Binary)
cmd := executeCommand(config.Options, config.Target.Emulator[0], args...) cmd := executeCommand(config.Options, config.Target.Emulator[0], args...)
buf := &bytes.Buffer{} buf := &bytes.Buffer{}
@@ -288,6 +294,8 @@ func Flash(pkgName, port string, options *compileopts.Options) error {
fileExt = filepath.Ext(config.Target.FlashFilename) fileExt = filepath.Ext(config.Target.FlashFilename)
case "openocd": case "openocd":
fileExt = ".hex" fileExt = ".hex"
case "bmp":
fileExt = ".elf"
case "native": case "native":
return errors.New("unknown flash method \"native\" - did you miss a -target flag?") return errors.New("unknown flash method \"native\" - did you miss a -target flag?")
default: default:
@@ -380,6 +388,25 @@ func Flash(pkgName, port string, options *compileopts.Options) error {
return &commandError{"failed to flash", result.Binary, err} return &commandError{"failed to flash", result.Binary, err}
} }
return nil return nil
case "bmp":
gdb, err := config.Target.LookupGDB()
if err != nil {
return err
}
var bmpGDBPort string
bmpGDBPort, _, err = getBMPPorts()
if err != nil {
return err
}
args := []string{"-ex", "target extended-remote " + bmpGDBPort, "-ex", "monitor swdp_scan", "-ex", "attach 1", "-ex", "load", filepath.ToSlash(result.Binary)}
cmd := executeCommand(config.Options, gdb, args...)
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
err = cmd.Run()
if err != nil {
return &commandError{"failed to flash", result.Binary, err}
}
return nil
default: default:
return fmt.Errorf("unknown flash method: %s", flashMethod) return fmt.Errorf("unknown flash method: %s", flashMethod)
} }
@@ -434,6 +461,13 @@ func FlashGDB(pkgName string, ocdOutput bool, options *compileopts.Options) erro
switch gdbInterface { switch gdbInterface {
case "native": case "native":
// Run GDB directly. // Run GDB directly.
case "bmp":
var bmpGDBPort string
bmpGDBPort, _, err = getBMPPorts()
if err != nil {
return err
}
gdbCommands = append(gdbCommands, "target extended-remote "+bmpGDBPort, "monitor swdp_scan", "compare-sections", "attach 1", "load")
case "openocd": case "openocd":
gdbCommands = append(gdbCommands, "target extended-remote :3333", "monitor halt", "load", "monitor reset halt") gdbCommands = append(gdbCommands, "target extended-remote :3333", "monitor halt", "load", "monitor reset halt")
@@ -841,6 +875,35 @@ func getDefaultPort(portFlag string, usbInterfaces []string) (port string, err e
return "", errors.New("port you specified '" + strings.Join(portCandidates, ",") + "' does not exist, available ports are " + strings.Join(ports, ", ")) return "", errors.New("port you specified '" + strings.Join(portCandidates, ",") + "' does not exist, available ports are " + strings.Join(ports, ", "))
} }
// getBMPPorts returns BlackMagicProbe's serial ports if any
func getBMPPorts() (gdbPort, uartPort string, err error) {
var portsList []*enumerator.PortDetails
portsList, err = enumerator.GetDetailedPortsList()
if err != nil {
return "", "", err
}
var ports []string
for _, p := range portsList {
if !p.IsUSB {
continue
}
if p.VID != "" && p.PID != "" {
vid, vidErr := strconv.ParseUint(p.VID, 16, 16)
pid, pidErr := strconv.ParseUint(p.PID, 16, 16)
if vidErr == nil && pidErr == nil && vid == 0x1d50 && pid == 0x6018 {
ports = append(ports, p.Name)
}
}
}
if len(ports) == 2 {
return ports[0], ports[1], nil
} else if len(ports) == 0 {
return "", "", errors.New("no BMP detected")
} else {
return "", "", fmt.Errorf("expected 2 BMP serial ports, found %d - did you perhaps connect more than one BMP?", len(ports))
}
}
func usage() { func usage() {
fmt.Fprintln(os.Stderr, "TinyGo is a Go compiler for small places.") fmt.Fprintln(os.Stderr, "TinyGo is a Go compiler for small places.")
fmt.Fprintln(os.Stderr, "version:", goenv.Version) fmt.Fprintln(os.Stderr, "version:", goenv.Version)
@@ -1027,6 +1090,7 @@ func main() {
ldflags := flag.String("ldflags", "", "Go link tool compatible ldflags") ldflags := flag.String("ldflags", "", "Go link tool compatible ldflags")
wasmAbi := flag.String("wasm-abi", "", "WebAssembly ABI conventions: js (no i64 params) or generic") wasmAbi := flag.String("wasm-abi", "", "WebAssembly ABI conventions: js (no i64 params) or generic")
llvmFeatures := flag.String("llvm-features", "", "comma separated LLVM features to enable") llvmFeatures := flag.String("llvm-features", "", "comma separated LLVM features to enable")
cpuprofile := flag.String("cpuprofile", "", "cpuprofile output")
var flagJSON, flagDeps, flagTest *bool var flagJSON, flagDeps, flagTest *bool
if command == "help" || command == "list" { if command == "help" || command == "list" {
@@ -1038,9 +1102,10 @@ func main() {
if command == "help" || command == "build" || command == "build-library" || command == "test" { if command == "help" || command == "build" || command == "build-library" || command == "test" {
flag.StringVar(&outpath, "o", "", "output filename") flag.StringVar(&outpath, "o", "", "output filename")
} }
var testCompileOnlyFlag *bool var testCompileOnlyFlag, testVerboseFlag *bool
if command == "help" || command == "test" { if command == "help" || command == "test" {
testCompileOnlyFlag = flag.Bool("c", false, "compile the test binary but do not run it") testCompileOnlyFlag = flag.Bool("c", false, "compile the test binary but do not run it")
testVerboseFlag = flag.Bool("v", false, "verbose: print additional output")
} }
// Early command processing, before commands are interpreted by the Go flag // Early command processing, before commands are interpreted by the Go flag
@@ -1110,6 +1175,20 @@ func main() {
os.Exit(1) os.Exit(1)
} }
if *cpuprofile != "" {
f, err := os.Create(*cpuprofile)
if err != nil {
fmt.Fprintln(os.Stderr, "could not create CPU profile: ", err)
os.Exit(1)
}
defer f.Close()
if err := pprof.StartCPUProfile(f); err != nil {
fmt.Fprintln(os.Stderr, "could not start CPU profile: ", err)
os.Exit(1)
}
defer pprof.StopCPUProfile()
}
switch command { switch command {
case "build": case "build":
if outpath == "" { if outpath == "" {
@@ -1201,7 +1280,7 @@ func main() {
allTestsPassed := true allTestsPassed := true
for _, pkgName := range pkgNames { for _, pkgName := range pkgNames {
// TODO: parallelize building the test binaries // TODO: parallelize building the test binaries
passed, err := Test(pkgName, options, *testCompileOnlyFlag, outpath) passed, err := Test(pkgName, options, *testCompileOnlyFlag, *testVerboseFlag, outpath)
handleCompilerError(err) handleCompilerError(err)
if !passed { if !passed {
allTestsPassed = false allTestsPassed = false
+73 -49
View File
@@ -51,9 +51,18 @@ func TestCompiler(t *testing.T) {
"stdlib.go", "stdlib.go",
"string.go", "string.go",
"structs.go", "structs.go",
"testing.go",
"zeroalloc.go", "zeroalloc.go",
} }
_, minor, err := goenv.GetGorootVersion(goenv.Get("GOROOT"))
if err != nil {
t.Fatal("could not read version from GOROOT:", err)
}
if minor >= 17 {
tests = append(tests, "go1.17.go")
}
if *testTarget != "" { if *testTarget != "" {
// This makes it possible to run one specific test (instead of all), // This makes it possible to run one specific test (instead of all),
// which is especially useful to quickly check whether some changes // which is especially useful to quickly check whether some changes
@@ -94,21 +103,9 @@ func TestCompiler(t *testing.T) {
t.Run("ARM64Linux", func(t *testing.T) { t.Run("ARM64Linux", func(t *testing.T) {
runPlatTests("aarch64--linux-gnu", tests, t) runPlatTests("aarch64--linux-gnu", tests, t)
}) })
goVersion, err := goenv.GorootVersionString(goenv.Get("GOROOT")) t.Run("WebAssembly", func(t *testing.T) {
if err != nil { runPlatTests("wasm", tests, t)
t.Error("could not get Go version:", err) })
return
}
minorVersion := strings.Split(goVersion, ".")[1]
if minorVersion != "13" {
// WebAssembly tests fail on Go 1.13, so skip them there. Versions
// below that are also not supported but still seem to pass, so
// include them in the tests for now.
t.Run("WebAssembly", func(t *testing.T) {
runPlatTests("wasm", tests, t)
})
}
t.Run("WASI", func(t *testing.T) { t.Run("WASI", func(t *testing.T) {
runPlatTests("wasi", tests, t) runPlatTests("wasi", tests, t)
}) })
@@ -162,14 +159,20 @@ func runPlatTests(target string, tests []string, t *testing.T) {
runTest(name, target, t, nil, nil) runTest(name, target, t, nil, nil)
}) })
} }
if target == "wasi" || target == "" { t.Run("env.go", func(t *testing.T) {
t.Parallel()
runTest("env.go", target, t, []string{"first", "second"}, []string{"ENV1=VALUE1", "ENV2=VALUE2"})
})
if target == "" || target == "wasi" {
t.Run("filesystem.go", func(t *testing.T) { t.Run("filesystem.go", func(t *testing.T) {
t.Parallel() t.Parallel()
runTest("filesystem.go", target, t, nil, nil) runTest("filesystem.go", target, t, nil, nil)
}) })
t.Run("env.go", func(t *testing.T) { }
if target == "" || target == "wasi" || target == "wasm" {
t.Run("rand.go", func(t *testing.T) {
t.Parallel() t.Parallel()
runTest("env.go", target, t, []string{"first", "second"}, []string{"ENV1=VALUE1", "ENV2=VALUE2"}) runTest("rand.go", target, t, nil, nil)
}) })
} }
} }
@@ -216,16 +219,43 @@ func runTestWithConfig(name, target string, t *testing.T, options compileopts.Op
} }
// Create a temporary directory for test output files. // Create a temporary directory for test output files.
tmpdir, err := ioutil.TempDir("", "tinygo-test") tmpdir := t.TempDir()
// Determine whether we're on a system that supports environment variables
// and command line parameters (operating systems, WASI) or not (baremetal,
// WebAssembly in the browser). If we're on a system without an environment,
// we need to pass command line arguments and environment variables through
// global variables (built into the binary directly) instead of the
// conventional way.
spec, err := compileopts.LoadTarget(target)
if err != nil { if err != nil {
t.Fatal("could not create temporary directory:", err) t.Fatal("failed to load target spec:", err)
} }
defer func() { needsEnvInVars := spec.GOOS == "js"
rerr := os.RemoveAll(tmpdir) for _, tag := range spec.BuildTags {
if rerr != nil { if tag == "baremetal" {
t.Errorf("failed to remove temporary directory %q: %s", tmpdir, rerr.Error()) needsEnvInVars = true
} }
}() }
if needsEnvInVars {
runtimeGlobals := make(map[string]string)
if len(cmdArgs) != 0 {
runtimeGlobals["osArgs"] = strings.Join(cmdArgs, "\x00")
}
if len(environmentVars) != 0 {
runtimeGlobals["osEnv"] = strings.Join(environmentVars, "\x00")
}
if len(runtimeGlobals) != 0 {
// This sets the global variables like they would be set with
// `-ldflags="-X=runtime.osArgs=first\x00second`.
// The runtime package has two variables (osArgs and osEnv) that are
// both strings, from which the parameters and environment variables
// are read.
options.GlobalValues = map[string]map[string]string{
"runtime": runtimeGlobals,
}
}
}
// Build the test binary. // Build the test binary.
binary := filepath.Join(tmpdir, "test") binary := filepath.Join(tmpdir, "test")
@@ -236,37 +266,31 @@ func runTestWithConfig(name, target string, t *testing.T, options compileopts.Op
return return
} }
// Run the test. // Create the test command, taking care of emulators etc.
runComplete := make(chan struct{})
var cmd *exec.Cmd var cmd *exec.Cmd
ranTooLong := false if len(spec.Emulator) == 0 {
if target == "" {
cmd = exec.Command(binary) cmd = exec.Command(binary)
cmd.Env = append(cmd.Env, environmentVars...) } else {
args := append(spec.Emulator[1:], binary)
cmd = exec.Command(spec.Emulator[0], args...)
}
if len(spec.Emulator) != 0 && spec.Emulator[0] == "wasmtime" {
// Allow reading from the current directory.
cmd.Args = append(cmd.Args, "--dir=.")
for _, v := range environmentVars {
cmd.Args = append(cmd.Args, "--env", v)
}
cmd.Args = append(cmd.Args, cmdArgs...) cmd.Args = append(cmd.Args, cmdArgs...)
} else { } else {
spec, err := compileopts.LoadTarget(target) if !needsEnvInVars {
if err != nil { cmd.Args = append(cmd.Args, cmdArgs...) // works on qemu-aarch64 etc
t.Fatal("failed to load target spec:", err)
}
if len(spec.Emulator) == 0 {
cmd = exec.Command(binary)
} else {
args := append(spec.Emulator[1:], binary)
cmd = exec.Command(spec.Emulator[0], args...)
}
if len(spec.Emulator) != 0 && spec.Emulator[0] == "wasmtime" {
// Allow reading from the current directory.
cmd.Args = append(cmd.Args, "--dir=.")
for _, v := range environmentVars {
cmd.Args = append(cmd.Args, "--env", v)
}
cmd.Args = append(cmd.Args, cmdArgs...)
} else {
cmd.Env = append(cmd.Env, environmentVars...) cmd.Env = append(cmd.Env, environmentVars...)
} }
} }
// Run the test.
runComplete := make(chan struct{})
ranTooLong := false
stdout := &bytes.Buffer{} stdout := &bytes.Buffer{}
cmd.Stdout = stdout cmd.Stdout = stdout
cmd.Stderr = os.Stderr cmd.Stderr = os.Stderr
+30
View File
@@ -0,0 +1,30 @@
// +build darwin freebsd tinygo.wasm
// This implementation of crypto/rand uses the arc4random_buf function
// (available on both MacOS and WASI) to generate random numbers.
//
// Note: arc4random_buf (unlike what the name suggets) does not use the insecure
// RC4 cipher. Instead, it uses a high-quality cipher, varying by the libc
// implementation.
package rand
import "unsafe"
func init() {
Reader = &reader{}
}
type reader struct {
}
func (r *reader) Read(b []byte) (n int, err error) {
if len(b) != 0 {
libc_arc4random_buf(unsafe.Pointer(&b[0]), uint(len(b)))
}
return len(b), nil
}
// void arc4random_buf(void *buf, size_t buflen);
//export arc4random_buf
func libc_arc4random_buf(buf unsafe.Pointer, buflen uint)
-38
View File
@@ -1,38 +0,0 @@
// +build darwin freebsd wasi
// This implementation of crypto/rand uses the getentropy system call (available
// on both MacOS and WASI) to generate random numbers.
package rand
import (
"errors"
"unsafe"
)
var errReadFailed = errors.New("rand: could not read random bytes")
func init() {
Reader = &reader{}
}
type reader struct {
}
func (r *reader) Read(b []byte) (n int, err error) {
if len(b) != 0 {
if len(b) > 256 {
b = b[:256]
}
result := libc_getentropy(unsafe.Pointer(&b[0]), len(b))
if result < 0 {
// Maybe we should return a syscall.Errno here?
return 0, errReadFailed
}
}
return len(b), nil
}
// int getentropy(void *buf, size_t buflen);
//export getentropy
func libc_getentropy(buf unsafe.Pointer, buflen int) int
+11
View File
@@ -0,0 +1,11 @@
// +build pico
package main
import "machine"
var (
pwm = machine.PWM4 // Pin 25 (LED on pico) corresponds to PWM4.
pinA = machine.LED
pinB = machine.GPIO24
)
+3
View File
@@ -16,6 +16,9 @@ type calleeSavedRegs struct {
ebp uintptr ebp uintptr
pc uintptr pc uintptr
// Pad this struct so that tasks start on a 16-byte aligned stack.
_ [3]uintptr
} }
// archInit runs architecture-specific setup for the goroutine startup. // archInit runs architecture-specific setup for the goroutine startup.
+5
View File
@@ -72,3 +72,8 @@ tinygo_swapTask:
// Return into the new task, as if tinygo_swapTask was a regular call. // Return into the new task, as if tinygo_swapTask was a regular call.
ret ret
#ifdef __MACH__ // Darwin
// allow these symbols to stripped as dead code
.subsections_via_symbols
#endif
+154
View File
@@ -0,0 +1,154 @@
// +build arduino_mkrwifi1010
// This contains the pin mappings for the Arduino MKR WiFi 1010 board.
//
// For more information, see: https://store.arduino.cc/usa/mkr-wifi-1010
//
package machine
import (
"device/sam"
"runtime/interrupt"
)
// used to reset into bootloader
const RESET_MAGIC_VALUE = 0x07738135
// GPIO Pins
const (
RX0 Pin = PB23 // UART1 RX
TX1 Pin = PB22 // UART1 TX
D0 Pin = PA22 // PWM available
D1 Pin = PA23 // PWM available
D2 Pin = PA10 // PWM available
D3 Pin = PA11 // PWM available
D4 Pin = PB10 // PWM available
D5 Pin = PB11 // PWM available
D6 Pin = PA20 // PWM available
D7 Pin = PA21 // PWM available
D8 Pin = PA16 // PWM available
D9 Pin = PA17
D10 Pin = PA19 // PWM available
D11 Pin = PA08 // SDA
D12 Pin = PA09 // PWM available, SCL
D13 Pin = PB23 // RX
D14 Pin = PB22 // TX
)
// Analog pins
const (
A0 Pin = PA02 // ADC0/AIN[0]
A1 Pin = PB02 // AIN[10]
A2 Pin = PB03 // AIN[11]
A3 Pin = PA04 // AIN[04]
A4 Pin = PA05 // AIN[05]
A5 Pin = PA06 // AIN[06]
A6 Pin = PA07 // AIN[07]
)
const (
LED = D6
)
// USBCDC pins
const (
USBCDC_DM_PIN Pin = PA24
USBCDC_DP_PIN Pin = PA25
)
// UART1 pins
const (
UART_TX_PIN Pin = PB22
UART_RX_PIN Pin = PB23
)
// I2C pins
const (
SDA_PIN Pin = D11 // SDA
SCL_PIN Pin = D12 // SCL
)
// SPI pins
const (
SPI0_SCK_PIN Pin = D9 // SCK: S1
SPI0_SDO_PIN Pin = D8 // SDO: S1
SPI0_SDI_PIN Pin = D10 // SDI: S1
)
// I2S pins
const (
I2S_SCK_PIN Pin = PA10
I2S_SD_PIN Pin = PA07
I2S_WS_PIN = NoPin // TODO: figure out what this is on Arduino MKR WiFi 1010.
)
// NINA-W102 Pins
const (
NINA_SDO Pin = PA12
NINA_SDI Pin = PA13
NINA_CS Pin = PA14
NINA_SCK Pin = PA15
NINA_GPIO0 Pin = PA27
NINA_RESETN Pin = PB08
NINA_ACK Pin = PA28
NINA_TX Pin = PA22
NINA_RX Pin = PA23
)
// UART on the Arduino MKR WiFi 1010.
var (
UART1 = &_UART1
_UART1 = UART{
Buffer: NewRingBuffer(),
Bus: sam.SERCOM5_USART,
SERCOM: 5,
}
)
func init() {
UART1.Interrupt = interrupt.New(sam.IRQ_SERCOM5, _UART1.handleInterrupt)
}
// I2C on the Arduino MKR WiFi 1010.
var (
I2C0 = &I2C{
Bus: sam.SERCOM2_I2CM,
SERCOM: 2,
}
)
// SPI on the Arduino MKR WiFi 1010.
var (
SPI0 = SPI{
Bus: sam.SERCOM1_SPI,
SERCOM: 1,
}
SPI1 = SPI{
Bus: sam.SERCOM4_SPI,
SERCOM: 4,
}
NINA_SPI = SPI1
)
// I2S on the Arduino MKR WiFi 1010.
var (
I2S0 = I2S{Bus: sam.I2S}
)
// USB CDC identifiers
const (
usb_STRING_PRODUCT = "Arduino MKR WiFi 1010"
usb_STRING_MANUFACTURER = "Arduino"
)
var (
usb_VID uint16 = 0x2341
usb_PID uint16 = 0x8054
)
var (
DefaultUART = UART1
)
+31
View File
@@ -9,6 +9,37 @@ const (
xoscFreq = 12 // MHz xoscFreq = 12 // MHz
) )
// GPIO Pins
const (
D4 = GPIO6
D5 = GPIO7
D6 = GPIO8
D9 = GPIO9
D10 = GPIO10
D11 = GPIO11
D12 = GPIO12
D13 = GPIO13
D24 = GPIO24
D25 = GPIO25
)
// Analog pins
const (
A0 = GPIO26
A1 = GPIO27
A2 = GPIO28
A3 = GPIO29
)
// I2C Pins.
const (
I2C0_SDA_PIN = GPIO24
I2C0_SCL_PIN = GPIO25
I2C1_SDA_PIN = GPIO2
I2C1_SCL_PIN = GPIO3
)
// SPI default pins // SPI default pins
const ( const (
// Default Serial Clock Bus 0 for SPI communications // Default Serial Clock Bus 0 for SPI communications
+40
View File
@@ -0,0 +1,40 @@
// +build mdbt50qrx
package machine
const HasLowFrequencyCrystal = false
// GPIO Pins
const (
D0 = P1_13 // LED1
D1 = P1_11 // LED2 (not populated by default)
D2 = P0_15 // Button
)
const (
LED = D0
)
// MDBT50Q-RX dongle does not have pins broken out for the peripherals below,
// however the machine_nrf*.go implementations of I2C/SPI/etc expect the pin
// constants to be defined, so we are defining them all as 0
const (
UART_TX_PIN = 0
UART_RX_PIN = 0
SDA_PIN = 0
SCL_PIN = 0
SPI0_SCK_PIN = 0
SPI0_SDO_PIN = 0
SPI0_SDI_PIN = 0
)
// USB CDC identifiers
const (
usb_STRING_PRODUCT = "Raytac MDBT50Q - RX"
usb_STRING_MANUFACTURER = "Raytac Corporation"
)
var (
usb_VID uint16 = 0x239A
usb_PID uint16 = 0x810B
)
+10 -3
View File
@@ -49,8 +49,11 @@ const (
// I2C pins // I2C pins
const ( const (
SDA_PIN Pin = GPIO12 I2C0_SDA_PIN Pin = GPIO12
SCL_PIN Pin = GPIO13 I2C0_SCL_PIN Pin = GPIO13
I2C1_SDA_PIN Pin = GPIO18
I2C1_SCL_PIN Pin = GPIO19
) )
// SPI pins. SPI1 not available on Nano RP2040 Connect. // SPI pins. SPI1 not available on Nano RP2040 Connect.
@@ -65,6 +68,10 @@ const (
SPI1_SDI_PIN Pin = GPIO22 SPI1_SDI_PIN Pin = GPIO22
) )
var (
NINA_SPI = SPI1
)
// NINA-W102 Pins // NINA-W102 Pins
const ( const (
NINA_SCK Pin = GPIO14 NINA_SCK Pin = GPIO14
@@ -73,7 +80,7 @@ const (
NINA_CS Pin = GPIO9 NINA_CS Pin = GPIO9
NINA_ACK Pin = GPIO10 NINA_ACK Pin = GPIO10
NINA_GPIO0 Pin = GPIO0 NINA_GPIO0 Pin = GPIO2
NINA_RESETN Pin = GPIO3 NINA_RESETN Pin = GPIO3
NINA_TX Pin = GPIO9 NINA_TX Pin = GPIO9
+9
View File
@@ -38,6 +38,15 @@ const (
xoscFreq = 12 // MHz xoscFreq = 12 // MHz
) )
// I2C Default pins on Raspberry Pico.
const (
I2C0_SDA_PIN = GP4
I2C0_SCL_PIN = GP5
I2C1_SDA_PIN = GP2
I2C1_SCL_PIN = GP3
)
// SPI default pins // SPI default pins
const ( const (
// Default Serial Clock Bus 0 for SPI communications // Default Serial Clock Bus 0 for SPI communications
+1 -1
View File
@@ -1,4 +1,4 @@
// +build atmega nrf sam stm32 fe310 k210 // +build atmega nrf sam stm32 fe310 k210 rp2040
package machine package machine
+40 -6
View File
@@ -47,14 +47,27 @@ type pinFunc uint8
// GPIO function selectors // GPIO function selectors
const ( const (
fnJTAG pinFunc = 0 fnJTAG pinFunc = 0
fnSPI pinFunc = 1 fnSPI pinFunc = 1 // Connect one of the internal PL022 SPI peripherals to GPIO
fnUART pinFunc = 2 fnUART pinFunc = 2
fnI2C pinFunc = 3 fnI2C pinFunc = 3
fnPWM pinFunc = 4 // Connect a PWM slice to GPIO. There are eight PWM slices,
fnSIO pinFunc = 5 // each with two outputchannels (A/B). The B pin can also be used as an input,
fnPIO0 pinFunc = 6 // for frequency and duty cyclemeasurement
fnPIO1 pinFunc = 7 fnPWM pinFunc = 4
// Software control of GPIO, from the single-cycle IO (SIO) block.
// The SIO function (F5)must be selected for the processors to drive a GPIO,
// but the input is always connected,so software can check the state of GPIOs at any time.
fnSIO pinFunc = 5
// Connect one of the programmable IO blocks (PIO) to GPIO. PIO can implement a widevariety of interfaces,
// and has its own internal pin mapping hardware, allowing flexibleplacement of digital interfaces on bank 0 GPIOs.
// The PIO function (F6, F7) must beselected for PIO to drive a GPIO, but the input is always connected,
// so the PIOs canalways see the state of all pins.
fnPIO0, fnPIO1 pinFunc = 6, 7
// General purpose clock inputs/outputs. Can be routed to a number of internal clock domains onRP2040,
// e.g. Input: to provide a 1 Hz clock for the RTC, or can be connected to an internalfrequency counter.
// e.g. Output: optional integer divide
fnGPCK pinFunc = 8 fnGPCK pinFunc = 8
// USB power control signals to/from the internal USB controller
fnUSB pinFunc = 9 fnUSB pinFunc = 9
fnNULL pinFunc = 0x1f fnNULL pinFunc = 0x1f
@@ -68,6 +81,8 @@ const (
PinInputPullup PinInputPullup
PinAnalog PinAnalog
PinUART PinUART
PinPWM
PinI2C
PinSPI PinSPI
) )
@@ -91,7 +106,7 @@ func (p Pin) xor() {
// get returns the pin value // get returns the pin value
func (p Pin) get() bool { func (p Pin) get() bool {
return rp.SIO.GPIO_IN.HasBits(uint32(1) << p) return rp.SIO.GPIO_IN.HasBits(1 << p)
} }
func (p Pin) ioCtrl() *volatile.Register32 { func (p Pin) ioCtrl() *volatile.Register32 {
@@ -117,6 +132,17 @@ func (p Pin) pulloff() {
p.padCtrl().ClearBits(rp.PADS_BANK0_GPIO0_PUE) p.padCtrl().ClearBits(rp.PADS_BANK0_GPIO0_PUE)
} }
// setSlew sets pad slew rate control.
// true sets to fast. false sets to slow.
func (p Pin) setSlew(sr bool) {
p.padCtrl().ReplaceBits(boolToBit(sr)<<rp.PADS_BANK0_GPIO0_SLEWFAST_Pos, rp.PADS_BANK0_GPIO0_SLEWFAST_Msk, 0)
}
// setSchmitt enables or disables Schmitt trigger.
func (p Pin) setSchmitt(trigger bool) {
p.padCtrl().ReplaceBits(boolToBit(trigger)<<rp.PADS_BANK0_GPIO0_SCHMITT_Pos, rp.PADS_BANK0_GPIO0_SCHMITT_Msk, 0)
}
// setFunc will set pin function to fn. // setFunc will set pin function to fn.
func (p Pin) setFunc(fn pinFunc) { func (p Pin) setFunc(fn pinFunc) {
// Set input enable, Clear output disable // Set input enable, Clear output disable
@@ -156,6 +182,14 @@ func (p Pin) Configure(config PinConfig) {
p.pulloff() p.pulloff()
case PinUART: case PinUART:
p.setFunc(fnUART) p.setFunc(fnUART)
case PinPWM:
p.setFunc(fnPWM)
case PinI2C:
// IO config according to 4.3.1.3 of rp2040 datasheet.
p.setFunc(fnI2C)
p.pullup()
p.setSchmitt(true)
p.setSlew(false)
case PinSPI: case PinSPI:
p.setFunc(fnSPI) p.setFunc(fnSPI)
} }
+461
View File
@@ -0,0 +1,461 @@
// +build rp2040
package machine
import (
"device/rp"
"errors"
"strconv"
)
// I2C on the RP2040.
var (
I2C0 = &_I2C0
_I2C0 = I2C{
Bus: rp.I2C0,
}
I2C1 = &_I2C1
_I2C1 = I2C{
Bus: rp.I2C1,
}
)
// Features: Taken from datasheet.
// Default master mode, with slave mode available (not simulataneously).
// Default slave address of RP2040: 0x055
// Supports 10-bit addressing in Master mode
// 16-element transmit buffer
// 16-element receive buffer
// Can be driven from DMA
// Can generate interrupts
// Fast mode plus max transfer speed (1000kb/s)
// GPIO config
// Each controller must connect its clock SCL and data SDA to one pair of GPIOs.
// The I2C standard requires that drivers drivea signal low, or when not driven the signal will be pulled high.
// This applies to SCL and SDA. The GPIO pads should beconfigured for:
// Pull-up enabled
// Slew rate limited
// Schmitt trigger enabled
// Note: There should also be external pull-ups on the board as the internal pad pull-ups may not be strong enough to pull upexternal circuits.
// I2CConfig is used to store config info for I2C.
type I2CConfig struct {
Frequency uint32
// SDA/SCL Serial Data and clock pins. Refer to datasheet to see
// which pins match the desired bus.
SDA, SCL Pin
}
type I2C struct {
Bus *rp.I2C0_Type
restartOnNext bool
}
var (
ErrInvalidI2CBaudrate = errors.New("invalid i2c baudrate")
ErrInvalidTgtAddr = errors.New("invalid target i2c address not in 0..0x80 or is reserved")
ErrI2CGeneric = errors.New("i2c error")
ErrRP2040I2CDisable = errors.New("i2c rp2040 peripheral timeout in disable")
)
// Tx performs a write and then a read transfer placing the result in
// in r.
//
// Passing a nil value for w or r skips the transfer corresponding to write
// or read, respectively.
//
// i2c.Tx(addr, nil, r)
// Performs only a read transfer.
//
// i2c.Tx(addr, w, nil)
// Performs only a write transfer.
func (i2c *I2C) Tx(addr uint16, w, r []byte) error {
// timeout in microseconds.
const timeout = 40 * 1000 // 40ms is a reasonable time for a real-time system.
if len(w) > 0 {
if err := i2c.tx(uint8(addr), w, false, timeout); nil != err {
return err
}
}
if len(r) > 0 {
if err := i2c.rx(uint8(addr), r, false, timeout); nil != err {
return err
}
}
return nil
}
// Configure initializes i2c peripheral and configures I2C config's pins passed.
// Here's a list of valid SDA and SCL GPIO pins on bus I2C0 of the rp2040:
// SDA: 0, 4, 8, 12, 16, 20
// SCL: 1, 5, 9, 13, 17, 21
// Same as above for I2C1 bus:
// SDA: 2, 6, 10, 14, 18, 26
// SCL: 3, 7, 11, 15, 19, 27
func (i2c *I2C) Configure(config I2CConfig) error {
if config.SCL == 0 {
// If config pins are zero valued or clock pin is invalid then we set default values.
switch i2c.Bus {
case rp.I2C0:
config.SCL = I2C0_SCL_PIN
config.SDA = I2C0_SDA_PIN
case rp.I2C1:
config.SCL = I2C1_SCL_PIN
config.SDA = I2C1_SDA_PIN
}
}
config.SDA.Configure(PinConfig{PinI2C})
config.SCL.Configure(PinConfig{PinI2C})
return i2c.init(config)
}
// SetBaudrate sets the I2C frequency. It has the side effect of also
// enabling the I2C hardware if disabled beforehand.
//go:inline
func (i2c *I2C) SetBaudrate(br uint32) error {
const freqin uint32 = 125 * MHz
// Find smallest prescale value which puts o
// TODO there are some subtleties to I2C timing which we are completely ignoring here
period := (freqin + br/2) / br
lcnt := period * 3 / 5 // oof this one hurts
hcnt := period - lcnt
// Check for out-of-range divisors:
if hcnt > rp.I2C0_IC_FS_SCL_HCNT_IC_FS_SCL_HCNT_Msk || hcnt < 8 || lcnt > rp.I2C0_IC_FS_SCL_LCNT_IC_FS_SCL_LCNT_Msk || lcnt < 8 {
return ErrInvalidI2CBaudrate
}
// Per I2C-bus specification a device in standard or fast mode must
// internally provide a hold time of at least 300ns for the SDA signal to
// bridge the undefined region of the falling edge of SCL. A smaller hold
// time of 120ns is used for fast mode plus.
// sda_tx_hold_count = freq_in [cycles/s] * 300ns * (1s / 1e9ns)
// Reduce 300/1e9 to 3/1e7 to avoid numbers that don't fit in uint.
// Add 1 to avoid division truncation.
sdaTxHoldCnt := ((freqin * 3) / 10000000) + 1
if br >= 1_000_000 {
// sda_tx_hold_count = freq_in [cycles/s] * 120ns * (1s / 1e9ns)
// Reduce 120/1e9 to 3/25e6 to avoid numbers that don't fit in uint.
// Add 1 to avoid division truncation.
sdaTxHoldCnt = ((freqin * 3) / 25000000) + 1
}
if sdaTxHoldCnt > lcnt-2 {
return ErrInvalidI2CBaudrate
}
err := i2c.disable()
if err != nil {
return err
}
// Always use "fast" mode (<= 400 kHz, works fine for standard mode too)
i2c.Bus.IC_CON.ReplaceBits(rp.I2C0_IC_CON_SPEED_FAST<<rp.I2C0_IC_CON_SPEED_Pos, rp.I2C0_IC_CON_SPEED_Msk, 0)
i2c.Bus.IC_FS_SCL_HCNT.Set(hcnt)
i2c.Bus.IC_FS_SCL_LCNT.Set(lcnt)
i2c.Bus.IC_FS_SPKLEN.Set(u32max(1, lcnt/16))
i2c.Bus.IC_SDA_HOLD.ReplaceBits(sdaTxHoldCnt<<rp.I2C0_IC_SDA_HOLD_IC_SDA_TX_HOLD_Pos, rp.I2C0_IC_SDA_HOLD_IC_SDA_TX_HOLD_Msk, 0)
i2c.enable()
return nil
}
//go:inline
func (i2c *I2C) enable() {
i2c.Bus.IC_ENABLE.ReplaceBits(rp.I2C0_IC_ENABLE_ENABLE<<rp.I2C0_IC_ENABLE_ENABLE_Pos, rp.I2C0_IC_ENABLE_ENABLE_Msk, 0)
}
// Implemented as per 4.3.10.3. Disabling DW_apb_i2c section.
//go:inline
func (i2c *I2C) disable() error {
const MAX_T_POLL_COUNT = 64 // 64 us timeout corresponds to around 1000kb/s i2c transfer rate.
deadline := ticks() + MAX_T_POLL_COUNT
i2c.Bus.IC_ENABLE.Set(0)
for i2c.Bus.IC_ENABLE_STATUS.Get()&1 != 0 {
if ticks() > deadline {
return ErrRP2040I2CDisable
}
}
return nil
}
//go:inline
func (i2c *I2C) init(config I2CConfig) error {
i2c.reset()
if err := i2c.disable(); err != nil {
return err
}
i2c.restartOnNext = false
// Configure as a fast-mode master with RepStart support, 7-bit addresses
i2c.Bus.IC_CON.Set((rp.I2C0_IC_CON_SPEED_FAST << rp.I2C0_IC_CON_SPEED_Pos) |
rp.I2C0_IC_CON_MASTER_MODE | rp.I2C0_IC_CON_IC_SLAVE_DISABLE |
rp.I2C0_IC_CON_IC_RESTART_EN | rp.I2C0_IC_CON_TX_EMPTY_CTRL) // sets TX_EMPTY_CTRL to enable TX_EMPTY interrupt status
// Set FIFO watermarks to 1 to make things simpler. This is encoded by a register value of 0.
i2c.Bus.IC_TX_TL.Set(0)
i2c.Bus.IC_RX_TL.Set(0)
// Always enable the DREQ signalling -- harmless if DMA isn't listening
i2c.Bus.IC_DMA_CR.Set(rp.I2C0_IC_DMA_CR_TDMAE | rp.I2C0_IC_DMA_CR_RDMAE)
return i2c.SetBaudrate(config.Frequency)
}
// reset sets I2C register RESET bits in the reset peripheral and then clears them.
//go:inline
func (i2c *I2C) reset() {
resetVal := i2c.deinit()
rp.RESETS.RESET.ClearBits(resetVal)
// Wait until reset is done.
for !rp.RESETS.RESET_DONE.HasBits(resetVal) {
}
}
// deinit sets reset bit for I2C. Must call reset to reenable I2C after deinit.
//go:inline
func (i2c *I2C) deinit() (resetVal uint32) {
switch {
case i2c.Bus == rp.I2C0:
resetVal = rp.RESETS_RESET_I2C0
case i2c.Bus == rp.I2C1:
resetVal = rp.RESETS_RESET_I2C1
}
// Perform I2C reset.
rp.RESETS.RESET.SetBits(resetVal)
return resetVal
}
// tx is a primitive i2c blocking write to bus routine. timeout is time to wait
// in microseconds since calling this function for write to finish.
func (i2c *I2C) tx(addr uint8, tx []byte, nostop bool, timeout uint64) (err error) {
deadline := ticks() + timeout
if addr >= 0x80 || isReservedI2CAddr(addr) {
return ErrInvalidTgtAddr
}
tlen := len(tx)
// Quick return if possible.
if tlen == 0 {
return nil
}
err = i2c.disable()
if err != nil {
return err
}
i2c.Bus.IC_TAR.Set(uint32(addr))
i2c.enable()
// If no timeout was passed timeoutCheck is false.
abort := false
var abortReason uint32
byteCtr := 0
for ; byteCtr < tlen; byteCtr++ {
first := byteCtr == 0
last := byteCtr == tlen-1
i2c.Bus.IC_DATA_CMD.Set(
(boolToBit(first && i2c.restartOnNext) << rp.I2C0_IC_DATA_CMD_RESTART_Pos) |
(boolToBit(last && !nostop) << rp.I2C0_IC_DATA_CMD_STOP_Pos) |
uint32(tx[byteCtr]))
// Wait until the transmission of the address/data from the internal
// shift register has completed. For this to function correctly, the
// TX_EMPTY_CTRL flag in IC_CON must be set. The TX_EMPTY_CTRL flag
// was set in i2c_init.
// IC_RAW_INTR_STAT_TX_EMPTY: This bit is set to 1 when the transmit buffer is at or below
// the threshold value set in the IC_TX_TL register and the
// transmission of the address/data from the internal shift
// register for the most recently popped command is
// completed. It is automatically cleared by hardware when
// the buffer level goes above the threshold. When
// IC_ENABLE[0] is set to 0, the TX FIFO is flushed and held
// in reset. There the TX FIFO looks like it has no data within
// it, so this bit is set to 1, provided there is activity in the
// master or slave state machines. When there is no longer
// any activity, then with ic_en=0, this bit is set to 0.
for !i2c.interrupted(rp.I2C0_IC_RAW_INTR_STAT_TX_EMPTY) {
if ticks() > deadline {
i2c.restartOnNext = nostop
println(1)
return errI2CWriteTimeout // If there was a timeout, don't attempt to do anything else.
}
}
abortReason = i2c.getAbortReason()
if abortReason != 0 {
i2c.clearAbortReason()
abort = true
}
if abort || (last && !nostop) {
// If the transaction was aborted or if it completed
// successfully wait until the STOP condition has occured.
// TODO Could there be an abort while waiting for the STOP
// condition here? If so, additional code would be needed here
// to take care of the abort.
for !i2c.interrupted(rp.I2C0_IC_RAW_INTR_STAT_STOP_DET) {
if ticks() > deadline {
println(2)
i2c.restartOnNext = nostop
return errI2CWriteTimeout
}
}
i2c.Bus.IC_CLR_STOP_DET.Get()
}
}
// From Pico SDK: A lot of things could have just happened due to the ingenious and
// creative design of I2C. Try to figure things out.
if abort {
switch {
case abortReason == 0 || abortReason&rp.I2C0_IC_TX_ABRT_SOURCE_ABRT_7B_ADDR_NOACK != 0:
// No reported errors - seems to happen if there is nothing connected to the bus.
// Address byte not acknowledged
err = ErrI2CGeneric
case abortReason&rp.I2C0_IC_TX_ABRT_SOURCE_ABRT_TXDATA_NOACK != 0:
// Address acknowledged, some data not acknowledged
fallthrough
default:
// panic("unknown i2c abortReason:" + strconv.Itoa(abortReason)
err = makeI2CBuffError(byteCtr)
}
}
// nostop means we are now at the end of a *message* but not the end of a *transfer*
i2c.restartOnNext = nostop
return err
}
// rx is a primitive i2c blocking read routine. timeout is time to wait
// in microseconds since calling this function for read to finish.
func (i2c *I2C) rx(addr uint8, rx []byte, nostop bool, timeout uint64) (err error) {
deadline := ticks() + timeout
if addr >= 0x80 || isReservedI2CAddr(addr) {
return ErrInvalidTgtAddr
}
rlen := len(rx)
// Quick return if possible.
if rlen == 0 {
return nil
}
err = i2c.disable()
if err != nil {
return err
}
i2c.Bus.IC_TAR.Set(uint32(addr))
i2c.enable()
// If no timeout was passed timeoutCheck is false.
abort := false
var abortReason uint32
byteCtr := 0
for ; byteCtr < rlen; byteCtr++ {
first := byteCtr == 0
last := byteCtr == rlen-1
for i2c.writeAvailable() == 0 {
}
i2c.Bus.IC_DATA_CMD.Set(
boolToBit(first && i2c.restartOnNext)<<rp.I2C0_IC_DATA_CMD_RESTART_Pos |
boolToBit(last && !nostop)<<rp.I2C0_IC_DATA_CMD_STOP_Pos |
rp.I2C0_IC_DATA_CMD_CMD) // -> 1 for read
for !abort && i2c.readAvailable() == 0 {
abortReason = i2c.getAbortReason()
i2c.clearAbortReason()
if abortReason != 0 {
abort = true
}
if ticks() > deadline {
i2c.restartOnNext = nostop
return errI2CReadTimeout // If there was a timeout, don't attempt to do anything else.
}
}
if abort {
break
}
rx[byteCtr] = uint8(i2c.Bus.IC_DATA_CMD.Get())
}
if abort {
switch {
case abortReason == 0 || abortReason&rp.I2C0_IC_TX_ABRT_SOURCE_ABRT_7B_ADDR_NOACK != 0:
// No reported errors - seems to happen if there is nothing connected to the bus.
// Address byte not acknowledged
err = ErrI2CGeneric
default:
// undefined abort sequence
err = makeI2CBuffError(byteCtr)
}
}
i2c.restartOnNext = nostop
return err
}
// writeAvailable determines non-blocking write space available
//go:inline
func (i2c *I2C) writeAvailable() uint32 {
return rp.I2C0_IC_COMP_PARAM_1_TX_BUFFER_DEPTH_Pos - i2c.Bus.IC_TXFLR.Get()
}
// readAvailable determines number of bytes received
//go:inline
func (i2c *I2C) readAvailable() uint32 {
return i2c.Bus.IC_RXFLR.Get()
}
// Equivalent to IC_CLR_TX_ABRT.Get() (side effect clears ABORT_REASON)
//go:inline
func (i2c *I2C) clearAbortReason() {
// Note clearing the abort flag also clears the reason, and
// this instance of flag is clear-on-read! Note also the
// IC_CLR_TX_ABRT register always reads as 0.
i2c.Bus.IC_CLR_TX_ABRT.Get()
}
//go:inline
func (i2c *I2C) getAbortReason() uint32 {
return i2c.Bus.IC_TX_ABRT_SOURCE.Get()
}
// returns true if RAW_INTR_STAT bits in mask are all set. performs:
// RAW_INTR_STAT & mask == mask
//go:inline
func (i2c *I2C) interrupted(mask uint32) bool {
reg := i2c.Bus.IC_RAW_INTR_STAT.Get()
return reg&mask == mask
}
type i2cBuffError int
func (b i2cBuffError) Error() string {
return "i2c err after addr ack at data " + strconv.Itoa(int(b))
}
//go:inline
func makeI2CBuffError(idx int) error {
return i2cBuffError(idx)
}
//go:inline
func boolToBit(a bool) uint32 {
if a {
return 1
}
return 0
}
//go:inline
func u32max(a, b uint32) uint32 {
if a > b {
return a
}
return b
}
//go:inline
func isReservedI2CAddr(addr uint8) bool {
return (addr&0x78) == 0 || (addr&0x78) == 0x78
}
+384
View File
@@ -0,0 +1,384 @@
// +build rp2040
package machine
import (
"device/rp"
"errors"
"runtime/volatile"
"unsafe"
)
var (
ErrPeriodTooBig = errors.New("period outside valid range 1..4e9ns")
)
const (
maxPWMPins = 29
)
// pwmGroup is one PWM peripheral, which consists of a counter and two output
// channels. You can set the frequency using SetPeriod,
// but only for all the channels in this PWM peripheral at once.
//
// div: integer value to reduce counting rate by. Must be greater than or equal to 1.
//
// cc: counter compare level. Contains 2 channel levels. The 16 LSBs are Channel A's level (Duty Cycle)
// and the 16 MSBs are Channel B's level.
//
// top: Wrap. Highest number counter will reach before wrapping over. usually 0xffff.
//
// csr: Clock mode. PWM_CH0_CSR_DIVMODE_xxx registers have 4 possible modes, of which Free-running is used.
// csr contains output polarity bit at PWM_CH0_CSR_x_INV where x is the channel.
// csr contains phase correction bit at PWM_CH0_CSR_PH_CORRECT_Msk.
// csr contains PWM enable bit at PWM_CH0_CSR_EN. If not enabled PWM will not be active.
//
// ctr: PWM counter value.
type pwmGroup struct {
CSR volatile.Register32
DIV volatile.Register32
CTR volatile.Register32
CC volatile.Register32
TOP volatile.Register32
}
// Equivalent of
// var pwmSlice []pwmGroup = (*[8]pwmGroup)(unsafe.Pointer(rp.PWM))[:]
// return &pwmSlice[index]
// 0x14 is the size of a pwmGroup.
func getPWMGroup(index uintptr) *pwmGroup {
return (*pwmGroup)(unsafe.Pointer(uintptr(unsafe.Pointer(rp.PWM)) + 0x14*index))
}
// PWM peripherals available on RP2040. Each peripheral has 2 pins available for
// a total of 16 available PWM outputs. Some pins may not be available on some boards.
var (
PWM0 = getPWMGroup(0)
PWM1 = getPWMGroup(1)
PWM2 = getPWMGroup(2)
PWM3 = getPWMGroup(3)
PWM4 = getPWMGroup(4)
PWM5 = getPWMGroup(5)
PWM6 = getPWMGroup(6)
PWM7 = getPWMGroup(7)
)
// Configure enables and configures this PWM.
func (pwm *pwmGroup) Configure(config PWMConfig) error {
return pwm.init(config, true)
}
// Channel returns a PWM channel for the given pin. If pin does
// not belong to PWM peripheral ErrInvalidOutputPin error is returned.
// It also configures pin as PWM output.
func (pwm *pwmGroup) Channel(pin Pin) (channel uint8, err error) {
if pin > maxPWMPins || pwmGPIOToSlice(pin) != pwm.peripheral() {
return 3, ErrInvalidOutputPin
}
pin.Configure(PinConfig{PinPWM})
return pwmGPIOToChannel(pin), nil
}
// Peripheral returns the RP2040 PWM peripheral which ranges from 0 to 7. Each
// PWM peripheral has 2 channels, A and B which correspond to 0 and 1 in the program.
// This number corresponds to the package's PWM0 throughout PWM7 handles
func PWMPeripheral(pin Pin) (sliceNum uint8, err error) {
if pin > maxPWMPins {
return 0, ErrInvalidOutputPin
}
return pwmGPIOToSlice(pin), nil
}
// returns the number of the pwm peripheral (0-7)
func (pwm *pwmGroup) peripheral() uint8 {
return uint8((uintptr(unsafe.Pointer(pwm)) - uintptr(unsafe.Pointer(rp.PWM))) / 0x14)
}
// SetPeriod updates the period of this PWM peripheral.
// To set a particular frequency, use the following formula:
//
// period = 1e9 / frequency
//
// If you use a period of 0, a period that works well for LEDs will be picked.
//
// SetPeriod will not change the prescaler, but also won't change the current
// value in any of the channels. This means that you may need to update the
// value for the particular channel.
//
// Note that you cannot pick any arbitrary period after the PWM peripheral has
// been configured. If you want to switch between frequencies, pick the lowest
// frequency (longest period) once when calling Configure and adjust the
// frequency here as needed.
func (p *pwmGroup) SetPeriod(period uint64) error {
if period > 0xffff_ffff {
return ErrPeriodTooBig
}
if period == 0 {
period = 1e5
}
p.setPeriod(period)
return nil
}
// Top returns the current counter top, for use in duty cycle calculation.
//
// The value returned here is hardware dependent. In general, it's best to treat
// it as an opaque value that can be divided by some number and passed to Set
// (see Set documentation for more information).
func (p *pwmGroup) Top() uint32 {
return p.getWrap()
}
// Counter returns the current counter value of the timer in this PWM
// peripheral. It may be useful for debugging.
func (p *pwmGroup) Counter() uint32 {
return (p.CTR.Get() & rp.PWM_CH0_CTR_CH0_CTR_Msk) >> rp.PWM_CH0_CTR_CH0_CTR_Pos
}
// Period returns the used PWM period in nanoseconds. It might deviate slightly
// from the configured period due to rounding.
func (p *pwmGroup) Period() uint64 {
periodPerCycle := getPeriod()
top := p.getWrap()
phc := p.getPhaseCorrect()
Int, frac := p.getClockDiv()
return uint64((Int + frac/16) * (top + 1) * (phc + 1) * periodPerCycle) // cycles = (TOP+1) * (CSRPHCorrect + 1) * (DIV_INT + DIV_FRAC/16)
}
// SetInverting sets whether to invert the output of this channel.
// Without inverting, a 25% duty cycle would mean the output is high for 25% of
// the time and low for the rest. Inverting flips the output as if a NOT gate
// was placed at the output, meaning that the output would be 25% low and 75%
// high with a duty cycle of 25%.
func (p *pwmGroup) SetInverting(channel uint8, inverting bool) {
channel &= 1
p.setInverting(channel, inverting)
}
// Set updates the channel value. This is used to control the channel duty
// cycle, in other words the fraction of time the channel output is high (or low
// when inverted). For example, to set it to a 25% duty cycle, use:
//
// pwm.Set(channel, pwm.Top() / 4)
//
// pwm.Set(channel, 0) will set the output to low and pwm.Set(channel,
// pwm.Top()) will set the output to high, assuming the output isn't inverted.
func (p *pwmGroup) Set(channel uint8, value uint32) {
val := uint16(value)
channel &= 1
p.setChanLevel(channel, val)
}
// Get current level (last set by Set). Default value on initialization is 0.
func (p *pwmGroup) Get(channel uint8) (value uint32) {
channel &= 1
return uint32(p.getChanLevel(channel))
}
// SetTop sets TOP control register. Max value is 16bit (0xffff).
func (p *pwmGroup) SetTop(top uint32) {
p.setWrap(uint16(top))
}
// Enable enables or disables PWM peripheral channels.
func (p *pwmGroup) Enable(enable bool) {
p.enable(enable)
}
// IsEnabled returns true if peripheral is enabled.
func (p *pwmGroup) IsEnabled() (enabled bool) {
return (p.CSR.Get()&rp.PWM_CH0_CSR_EN_Msk)>>rp.PWM_CH0_CSR_EN_Pos != 0
}
// Hardware Pulse Width Modulation (PWM) API
//
// The RP2040 PWM block has 8 identical slices. Each slice can drive two PWM output signals, or
// measure the frequency or duty cycle of an input signal. This gives a total of up to 16 controllable
// PWM outputs. All 30 GPIOs can be driven by the PWM block
//
// The PWM hardware functions by continuously comparing the input value to a free-running counter. This produces a
// toggling output where the amount of time spent at the high output level is proportional to the input value. The fraction of
// time spent at the high signal level is known as the duty cycle of the signal.
//
// The default behaviour of a PWM slice is to count upward until the wrap value (\ref pwm_config_set_wrap) is reached, and then
// immediately wrap to 0. PWM slices also offer a phase-correct mode, where the counter starts to count downward after
// reaching TOP, until it reaches 0 again.
type pwms struct {
slice pwmGroup
hw *rp.PWM_Type
}
// Handle to all pwm peripheral registers.
var _PWM = pwms{
hw: rp.PWM,
}
// Initialise a PWM with settings from a configuration object.
// If start is true then PWM starts on initialization.
func (pwm *pwmGroup) init(config PWMConfig, start bool) error {
// Not enable Phase correction
pwm.setPhaseCorrect(false)
// Clock mode set by default to Free running
pwm.setDivMode(rp.PWM_CH0_CSR_DIVMODE_DIV)
// Set Output polarity (false/false)
pwm.setInverting(0, false)
pwm.setInverting(1, false)
// Set wrap. The highest value the counter will reach before returning to zero, also known as TOP.
pwm.setWrap(0xffff)
// period is set after TOP (Wrap).
err := pwm.SetPeriod(config.Period)
if err != nil {
return err
}
// period already set beforea
// Reset counter and compare (pwm level set to zero)
pwm.CTR.ReplaceBits(0, rp.PWM_CH0_CTR_CH0_CTR_Msk, 0) // PWM_CH0_CTR_RESET
pwm.CC.Set(0) // PWM_CH0_CC_RESET
pwm.enable(start)
return nil
}
func (pwm *pwmGroup) setPhaseCorrect(correct bool) {
pwm.CSR.ReplaceBits(boolToBit(correct)<<rp.PWM_CH0_CSR_PH_CORRECT_Pos, rp.PWM_CH0_CSR_PH_CORRECT_Msk, 0)
}
// Takes any of the following:
// rp.PWM_CH0_CSR_DIVMODE_DIV, rp.PWM_CH0_CSR_DIVMODE_FALL,
// rp.PWM_CH0_CSR_DIVMODE_LEVEL, rp.PWM_CH0_CSR_DIVMODE_RISE
func (pwm *pwmGroup) setDivMode(mode uint32) {
pwm.CSR.ReplaceBits(mode<<rp.PWM_CH0_CSR_DIVMODE_Pos, rp.PWM_CH0_CSR_DIVMODE_Msk, 0)
}
// setPeriod sets the pwm peripheral period (frequency). Calculates DIV_INT and sets it from following equation:
// cycles = (TOP+1) * (CSRPHCorrect + 1) * (DIV_INT + DIV_FRAC/16)
// where cycles is amount of clock cycles per PWM period.
func (pwm *pwmGroup) setPeriod(period uint64) {
targetPeriod := uint32(period)
periodPerCycle := getPeriod()
top := pwm.getWrap()
phc := pwm.getPhaseCorrect()
_, frac := pwm.getClockDiv()
// clearing above expression:
// DIV_INT = cycles / ( (TOP+1) * (CSRPHCorrect+1) ) - DIV_FRAC/16
// where cycles must be converted to time:
// target_period = cycles * period_per_cycle ==> cycles = target_period/period_per_cycle
Int := targetPeriod/((1+phc)*periodPerCycle*(1+top)) - frac/16
if Int > 0xff {
Int = 0xff
}
pwm.setClockDiv(uint8(Int), 0)
}
// Int is integer value to reduce counting rate by. Must be greater than or equal to 1. DIV_INT is bits 4:11 (8 bits).
// frac's (DIV_FRAC) default value on reset is 0. Max value for frac is 15 (4 bits). This is known as a fixed-point
// fractional number.
//
// cycles = (TOP+1) * (CSRPHCorrect + 1) * (DIV_INT + DIV_FRAC/16)
func (pwm *pwmGroup) setClockDiv(Int, frac uint8) {
pwm.DIV.ReplaceBits((uint32(frac)<<rp.PWM_CH0_DIV_FRAC_Pos)|
u32max(uint32(Int), 1)<<rp.PWM_CH0_DIV_INT_Pos, rp.PWM_CH0_DIV_FRAC_Msk|rp.PWM_CH0_DIV_INT_Msk, 0)
}
// Set the highest value the counter will reach before returning to 0. Also
// known as TOP.
//
// The counter wrap value is double-buffered in hardware. This means that,
// when the PWM is running, a write to the counter wrap value does not take
// effect until after the next time the PWM slice wraps (or, in phase-correct
// mode, the next time the slice reaches 0). If the PWM is not running, the
// write is latched in immediately.
func (pwm *pwmGroup) setWrap(wrap uint16) {
pwm.TOP.ReplaceBits(uint32(wrap)<<rp.PWM_CH0_TOP_CH0_TOP_Pos, rp.PWM_CH0_TOP_CH0_TOP_Msk, 0)
}
// enables/disables the PWM peripheral with rp.PWM_CH0_CSR_EN bit.
func (pwm *pwmGroup) enable(enable bool) {
pwm.CSR.ReplaceBits(boolToBit(enable)<<rp.PWM_CH0_CSR_EN_Pos, rp.PWM_CH0_CSR_EN_Msk, 0)
}
func (pwm *pwmGroup) setInverting(channel uint8, invert bool) {
var pos uint8
var msk uint32
switch channel {
case 0:
pos = rp.PWM_CH0_CSR_A_INV_Pos
msk = rp.PWM_CH0_CSR_A_INV_Msk
case 1:
pos = rp.PWM_CH0_CSR_B_INV_Pos
msk = rp.PWM_CH0_CSR_B_INV_Msk
}
pwm.CSR.ReplaceBits(boolToBit(invert)<<pos, msk, 0)
}
// Set the current PWM counter compare value for one channel
//
// The counter compare register is double-buffered in hardware. This means
// that, when the PWM is running, a write to the counter compare values does
// not take effect until the next time the PWM slice wraps (or, in
// phase-correct mode, the next time the slice reaches 0). If the PWM is not
// running, the write is latched in immediately.
// Channel is 0 for A, 1 for B.
func (pwm *pwmGroup) setChanLevel(channel uint8, level uint16) {
var pos uint8
var mask uint32
switch channel {
case 0:
pos = rp.PWM_CH0_CC_A_Pos
mask = rp.PWM_CH0_CC_A_Msk
case 1:
pos = rp.PWM_CH0_CC_B_Pos
mask = rp.PWM_CH0_CC_B_Msk
}
pwm.CC.ReplaceBits(uint32(level)<<pos, mask, 0)
}
func (pwm *pwmGroup) getChanLevel(channel uint8) (level uint16) {
var pos uint8
var mask uint32
switch channel {
case 0:
pos = rp.PWM_CH0_CC_A_Pos
mask = rp.PWM_CH0_CC_A_Msk
case 1:
pos = rp.PWM_CH0_CC_B_Pos
mask = rp.PWM_CH0_CC_B_Msk
}
level = uint16((pwm.CC.Get() & mask) >> pos)
return level
}
func (pwm *pwmGroup) getWrap() (top uint32) {
return (pwm.TOP.Get() & rp.PWM_CH0_TOP_CH0_TOP_Msk) >> rp.PWM_CH0_TOP_CH0_TOP_Pos
}
func (pwm *pwmGroup) getPhaseCorrect() (phCorrect uint32) {
return (pwm.CSR.Get() & rp.PWM_CH0_CSR_PH_CORRECT_Msk) >> rp.PWM_CH0_CSR_PH_CORRECT_Pos
}
func (pwm *pwmGroup) getClockDiv() (Int, frac uint32) {
div := pwm.DIV.Get()
return (div & rp.PWM_CH0_DIV_INT_Msk) >> rp.PWM_CH0_DIV_INT_Pos, (div & rp.PWM_CH0_DIV_FRAC_Msk) >> rp.PWM_CH0_DIV_FRAC_Pos
}
// pwmGPIOToSlice Determine the PWM channel that is attached to the specified GPIO.
// gpio must be less than 30. Returns the PWM slice number that controls the specified GPIO.
func pwmGPIOToSlice(gpio Pin) (slicenum uint8) {
return (uint8(gpio) >> 1) & 7
}
// Determine the PWM channel that is attached to the specified GPIO.
// Each slice 0 to 7 has two channels, A and B.
func pwmGPIOToChannel(gpio Pin) (channel uint8) {
return uint8(gpio) & 1
}
// Returns the period of a clock cycle for the raspberry pi pico in nanoseconds.
func getPeriod() uint32 {
const periodIn uint32 = 1e9 / (125 * MHz)
return periodIn
}
+9
View File
@@ -0,0 +1,9 @@
// +build !linux
package os
import "errors"
func Executable() (string, error) {
return "", errors.New("Executable not implemented")
}
+25
View File
@@ -0,0 +1,25 @@
// The following is copied from Go 1.17 official implementation.
// Copyright 2016 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// +build linux
package os
func Executable() (string, error) {
path, err := Readlink("/proc/self/exe")
// When the executable has been deleted then Readlink returns a
// path appended with " (deleted)".
return stringsTrimSuffix(path, " (deleted)"), err
}
// stringsTrimSuffix is the same as strings.TrimSuffix.
func stringsTrimSuffix(s, suffix string) string {
if len(s) >= len(suffix) && s[len(s)-len(suffix):] == suffix {
return s[:len(s)-len(suffix)]
}
return s
}
+5
View File
@@ -24,6 +24,11 @@ func Mkdir(path string, perm FileMode) error {
return nil return nil
} }
// MkdirTemp is a stub, it will always return an error.
func MkdirTemp(dir, pattern string) (string, error) {
return "", &PathError{"mkdirtemp", dir, ErrNotImplemented}
}
// Remove removes a file or (empty) directory. If the operation fails, it will // Remove removes a file or (empty) directory. If the operation fails, it will
// return an error of type *PathError. // return an error of type *PathError.
func Remove(path string) error { func Remove(path string) error {
+5
View File
@@ -706,6 +706,11 @@ type StructField struct {
Offset uintptr Offset uintptr
} }
// IsExported reports whether the field is exported.
func (f StructField) IsExported() bool {
return f.PkgPath == ""
}
// rawStructField is the same as StructField but with the Type member replaced // rawStructField is the same as StructField but with the Type member replaced
// with rawType. For internal use only. Avoiding this conversion to the Type // with rawType. For internal use only. Avoiding this conversion to the Type
// interface improves code size in many cases. // interface improves code size in many cases.
+1 -1
View File
@@ -9,7 +9,7 @@ const TargetBits = 32
// Align on word boundary. // Align on word boundary.
func align(ptr uintptr) uintptr { func align(ptr uintptr) uintptr {
return (ptr + 3) &^ 3 return (ptr + 15) &^ 15
} }
func getCurrentStackPointer() uintptr { func getCurrentStackPointer() uintptr {
+2 -1
View File
@@ -13,10 +13,11 @@ tinygo_scanCurrentStack:
pushl %ebp pushl %ebp
// Scan the stack. // Scan the stack.
subl $8, %esp // adjust the stack before the call to maintain 16-byte alignment
pushl %esp pushl %esp
calll tinygo_scanstack calll tinygo_scanstack
// Restore the stack pointer. Registers do not need to be restored as they // Restore the stack pointer. Registers do not need to be restored as they
// were only pushed to be discoverable by the GC. // were only pushed to be discoverable by the GC.
addl $20, %esp addl $28, %esp
retl retl
+5
View File
@@ -27,3 +27,8 @@ _tinygo_scanCurrentStack:
// were only pushed to be discoverable by the GC. // were only pushed to be discoverable by the GC.
addq $56, %rsp addq $56, %rsp
retq retq
#ifdef __MACH__ // Darwin
// allow these symbols to stripped as dead code
.subsections_via_symbols
#endif
+2 -1
View File
@@ -52,7 +52,8 @@ func SetFinalizer(obj interface{}, finalizer interface{}) {
} }
func initHeap() { func initHeap() {
// Nothing to initialize. // preinit() may have moved heapStart; reset heapptr
heapptr = heapStart
} }
// setHeapEnd sets a new (larger) heapEnd pointer. // setHeapEnd sets a new (larger) heapEnd pointer.
-272
View File
@@ -1,272 +0,0 @@
package runtime
// This file redirects math stubs to their fallback implementation.
// TODO: use optimized versions if possible.
import (
_ "unsafe"
)
//go:linkname math_Asin math.Asin
func math_Asin(x float64) float64 { return math_asin(x) }
//go:linkname math_asin math.asin
func math_asin(x float64) float64
//go:linkname math_Asinh math.Asinh
func math_Asinh(x float64) float64 { return math_asinh(x) }
//go:linkname math_asinh math.asinh
func math_asinh(x float64) float64
//go:linkname math_Acos math.Acos
func math_Acos(x float64) float64 { return math_acos(x) }
//go:linkname math_acos math.acos
func math_acos(x float64) float64
//go:linkname math_Acosh math.Acosh
func math_Acosh(x float64) float64 { return math_acosh(x) }
//go:linkname math_acosh math.acosh
func math_acosh(x float64) float64
//go:linkname math_Atan math.Atan
func math_Atan(x float64) float64 { return math_atan(x) }
//go:linkname math_atan math.atan
func math_atan(x float64) float64
//go:linkname math_Atanh math.Atanh
func math_Atanh(x float64) float64 { return math_atanh(x) }
//go:linkname math_atanh math.atanh
func math_atanh(x float64) float64
//go:linkname math_Atan2 math.Atan2
func math_Atan2(y, x float64) float64 { return math_atan2(y, x) }
//go:linkname math_atan2 math.atan2
func math_atan2(y, x float64) float64
//go:linkname math_Cbrt math.Cbrt
func math_Cbrt(x float64) float64 { return math_cbrt(x) }
//go:linkname math_cbrt math.cbrt
func math_cbrt(x float64) float64
//go:linkname math_Ceil math.Ceil
func math_Ceil(x float64) float64 {
if GOARCH == "arm64" || GOARCH == "wasm" {
return llvm_ceil(x)
}
return math_ceil(x)
}
//export llvm.ceil.f64
func llvm_ceil(x float64) float64
//go:linkname math_ceil math.ceil
func math_ceil(x float64) float64
//go:linkname math_Cos math.Cos
func math_Cos(x float64) float64 { return math_cos(x) }
//go:linkname math_cos math.cos
func math_cos(x float64) float64
//go:linkname math_Cosh math.Cosh
func math_Cosh(x float64) float64 { return math_cosh(x) }
//go:linkname math_cosh math.cosh
func math_cosh(x float64) float64
//go:linkname math_Erf math.Erf
func math_Erf(x float64) float64 { return math_erf(x) }
//go:linkname math_erf math.erf
func math_erf(x float64) float64
//go:linkname math_Erfc math.Erfc
func math_Erfc(x float64) float64 { return math_erfc(x) }
//go:linkname math_erfc math.erfc
func math_erfc(x float64) float64
//go:linkname math_Exp math.Exp
func math_Exp(x float64) float64 { return math_exp(x) }
//go:linkname math_exp math.exp
func math_exp(x float64) float64
//go:linkname math_Expm1 math.Expm1
func math_Expm1(x float64) float64 { return math_expm1(x) }
//go:linkname math_expm1 math.expm1
func math_expm1(x float64) float64
//go:linkname math_Exp2 math.Exp2
func math_Exp2(x float64) float64 { return math_exp2(x) }
//go:linkname math_exp2 math.exp2
func math_exp2(x float64) float64
//go:linkname math_Floor math.Floor
func math_Floor(x float64) float64 {
if GOARCH == "arm64" || GOARCH == "wasm" {
return llvm_floor(x)
}
return math_floor(x)
}
//export llvm.floor.f64
func llvm_floor(x float64) float64
//go:linkname math_floor math.floor
func math_floor(x float64) float64
//go:linkname math_Frexp math.Frexp
func math_Frexp(x float64) (float64, int) { return math_frexp(x) }
//go:linkname math_frexp math.frexp
func math_frexp(x float64) (float64, int)
//go:linkname math_Hypot math.Hypot
func math_Hypot(p, q float64) float64 { return math_hypot(p, q) }
//go:linkname math_hypot math.hypot
func math_hypot(p, q float64) float64
//go:linkname math_Ldexp math.Ldexp
func math_Ldexp(frac float64, exp int) float64 { return math_ldexp(frac, exp) }
//go:linkname math_ldexp math.ldexp
func math_ldexp(frac float64, exp int) float64
//go:linkname math_Log math.Log
func math_Log(x float64) float64 { return math_log(x) }
//go:linkname math_log math.log
func math_log(x float64) float64
//go:linkname math_Log1p math.Log1p
func math_Log1p(x float64) float64 { return math_log1p(x) }
//go:linkname math_log1p math.log1p
func math_log1p(x float64) float64
//go:linkname math_Log10 math.Log10
func math_Log10(x float64) float64 { return math_log10(x) }
//go:linkname math_log10 math.log10
func math_log10(x float64) float64
//go:linkname math_Log2 math.Log2
func math_Log2(x float64) float64 { return math_log2(x) }
//go:linkname math_log2 math.log2
func math_log2(x float64) float64
//go:linkname math_Max math.Max
func math_Max(x, y float64) float64 {
if GOARCH == "arm64" || GOARCH == "wasm" {
return llvm_maximum(x, y)
}
return math_max(x, y)
}
//export llvm.maximum.f64
func llvm_maximum(x, y float64) float64
//go:linkname math_max math.max
func math_max(x, y float64) float64
//go:linkname math_Min math.Min
func math_Min(x, y float64) float64 {
if GOARCH == "arm64" || GOARCH == "wasm" {
return llvm_minimum(x, y)
}
return math_min(x, y)
}
//export llvm.minimum.f64
func llvm_minimum(x, y float64) float64
//go:linkname math_min math.min
func math_min(x, y float64) float64
//go:linkname math_Mod math.Mod
func math_Mod(x, y float64) float64 { return math_mod(x, y) }
//go:linkname math_mod math.mod
func math_mod(x, y float64) float64
//go:linkname math_Modf math.Modf
func math_Modf(x float64) (float64, float64) { return math_modf(x) }
//go:linkname math_modf math.modf
func math_modf(x float64) (float64, float64)
//go:linkname math_Pow math.Pow
func math_Pow(x, y float64) float64 { return math_pow(x, y) }
//go:linkname math_pow math.pow
func math_pow(x, y float64) float64
//go:linkname math_Remainder math.Remainder
func math_Remainder(x, y float64) float64 { return math_remainder(x, y) }
//go:linkname math_remainder math.remainder
func math_remainder(x, y float64) float64
//go:linkname math_Sin math.Sin
func math_Sin(x float64) float64 { return math_sin(x) }
//go:linkname math_sin math.sin
func math_sin(x float64) float64
//go:linkname math_Sinh math.Sinh
func math_Sinh(x float64) float64 { return math_sinh(x) }
//go:linkname math_sinh math.sinh
func math_sinh(x float64) float64
//go:linkname math_Sqrt math.Sqrt
func math_Sqrt(x float64) float64 {
if GOARCH == "x86" || GOARCH == "amd64" || GOARCH == "wasm" {
return llvm_sqrt(x)
}
return math_sqrt(x)
}
//export llvm.sqrt.f64
func llvm_sqrt(x float64) float64
//go:linkname math_sqrt math.sqrt
func math_sqrt(x float64) float64
//go:linkname math_Tan math.Tan
func math_Tan(x float64) float64 { return math_tan(x) }
//go:linkname math_tan math.tan
func math_tan(x float64) float64
//go:linkname math_Tanh math.Tanh
func math_Tanh(x float64) float64 { return math_tanh(x) }
//go:linkname math_tanh math.tanh
func math_tanh(x float64) float64
//go:linkname math_Trunc math.Trunc
func math_Trunc(x float64) float64 {
if GOARCH == "arm64" || GOARCH == "wasm" {
return llvm_trunc(x)
}
return math_trunc(x)
}
//export llvm.trunc.f64
func llvm_trunc(x float64) float64
//go:linkname math_trunc math.trunc
func math_trunc(x float64) float64
+48
View File
@@ -0,0 +1,48 @@
// +build baremetal js
package runtime
// This file is for non-hosted environments, that don't support command line
// parameters or environment variables. To still be able to run certain tests,
// command line parameters and environment variables can be passed to the binary
// by setting the variables `runtime.osArgs` and `runtime.osEnv`, both of which
// are strings separated by newlines.
//
// The primary use case is `tinygo test`, which takes some parameters (such as
// -test.v).
var env []string
//go:linkname syscall_runtime_envs syscall.runtime_envs
func syscall_runtime_envs() []string {
return env
}
var osArgs string
var osEnv string
func init() {
if osArgs != "" {
s := osArgs
start := 0
for i := 0; i < len(s); i++ {
if s[i] == 0 {
args = append(args, s[start:i])
start = i + 1
}
}
args = append(args, s[start:])
}
if osEnv != "" {
s := osEnv
start := 0
for i := 0; i < len(s); i++ {
if s[i] == 0 {
env = append(env, s[start:i])
start = i + 1
}
}
env = append(env, s[start:])
}
}
+12
View File
@@ -42,6 +42,18 @@ func slicePanic() {
runtimePanic("slice out of range") runtimePanic("slice out of range")
} }
// Panic when trying to convert a slice to an array pointer (Go 1.17+) and the
// slice is shorter than the array.
func sliceToArrayPointerPanic() {
runtimePanic("slice smaller than array")
}
// Panic when calling unsafe.Slice() (Go 1.17+) with a len that's too large
// (which includes if the ptr is nil and len is nonzero).
func unsafeSlicePanic() {
runtimePanic("unsafe.Slice: len out of range")
}
// Panic when trying to create a new channel that is too big. // Panic when trying to create a new channel that is too big.
func chanMakePanic() { func chanMakePanic() {
runtimePanic("new channel is too big") runtimePanic("new channel is too big")
-5
View File
@@ -15,11 +15,6 @@ func _start() {
run() run()
} }
//go:linkname syscall_runtime_envs syscall.runtime_envs
func syscall_runtime_envs() []string {
return nil
}
var handleEvent func() var handleEvent func()
//go:linkname setEventHandler syscall/js.setEventHandler //go:linkname setEventHandler syscall/js.setEventHandler
@@ -1,4 +1,4 @@
// +build baremetal // +build baremetal js
package syscall package syscall
@@ -47,8 +47,24 @@ const (
O_CLOEXEC = 0 O_CLOEXEC = 0
) )
func runtime_envs() []string
func Getenv(key string) (value string, found bool) { func Getenv(key string) (value string, found bool) {
return "", false // stub env := runtime_envs()
for _, keyval := range env {
// Split at '=' character.
var k, v string
for i := 0; i < len(keyval); i++ {
if keyval[i] == '=' {
k = keyval[:i]
v = keyval[i+1:]
}
}
if k == key {
return v, true
}
}
return "", false
} }
func Open(path string, mode int, perm uint32) (fd int, err error) { func Open(path string, mode int, perm uint32) (fd int, err error) {
@@ -2,7 +2,7 @@
// Use of this source code is governed by a BSD-style // Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file. // license that can be found in the LICENSE file.
// +build baremetal nintendoswitch // +build baremetal nintendoswitch js
package syscall package syscall
+192 -12
View File
@@ -6,29 +6,209 @@
package testing package testing
// B is a type passed to Benchmark functions to manage benchmark timing and to import (
// specify the number of iterations to run. "time"
// )
// TODO: Implement benchmarks. This struct allows test files containing
// benchmarks to compile and run, but will not run the benchmarks themselves. var (
type B struct { benchTime = benchTimeFlag{d: 1 * time.Second} // changed during test of testing package
common )
N int
type benchTimeFlag struct {
d time.Duration
} }
// B is a type passed to Benchmark functions to manage benchmark timing and to
// specify the number of iterations to run.
type B struct {
common
hasSub bool // TODO: should be in common, and atomic
start time.Time // TODO: should be in common
duration time.Duration // TODO: should be in common
N int
benchFunc func(b *B)
benchTime benchTimeFlag
timerOn bool
result BenchmarkResult
}
// InternalBenchmark is an internal type but exported because it is cross-package;
// it is part of the implementation of the "go test" command.
type InternalBenchmark struct { type InternalBenchmark struct {
Name string Name string
F func(b *B) F func(b *B)
} }
// BenchmarkResult contains the results of a benchmark run.
type BenchmarkResult struct {
N int // The number of iterations.
T time.Duration // The total time taken.
}
// NsPerOp returns the "ns/op" metric.
func (r BenchmarkResult) NsPerOp() int64 {
if r.N <= 0 {
return 0
}
return r.T.Nanoseconds() / int64(r.N)
}
// AllocsPerOp returns the "allocs/op" metric,
// which is calculated as r.MemAllocs / r.N.
func (r BenchmarkResult) AllocsPerOp() int64 {
return 0 // Dummy version to allow running e.g. golang.org/test/fibo.go
}
// AllocedBytesPerOp returns the "B/op" metric,
// which is calculated as r.MemBytes / r.N.
func (r BenchmarkResult) AllocedBytesPerOp() int64 {
return 0 // Dummy version to allow running e.g. golang.org/test/fibo.go
}
func (b *B) SetBytes(n int64) { func (b *B) SetBytes(n int64) {
panic("testing: unimplemented: B.SetBytes") panic("testing: unimplemented: B.SetBytes")
} }
func (b *B) ResetTimer() { // StartTimer starts timing a test. This function is called automatically
panic("testing: unimplemented: B.ResetTimer") // before a benchmark starts, but it can also be used to resume timing after
// a call to StopTimer.
func (b *B) StartTimer() {
if !b.timerOn {
b.start = time.Now()
b.timerOn = true
}
} }
func (b *B) Run(name string, f func(b *B)) bool { // StopTimer stops timing a test. This can be used to pause the timer
panic("testing: unimplemented: B.Run") // while performing complex initialization that you don't
// want to measure.
func (b *B) StopTimer() {
if b.timerOn {
b.duration += time.Since(b.start)
b.timerOn = false
}
}
// ResetTimer zeroes the elapsed benchmark time.
// It does not affect whether the timer is running.
func (b *B) ResetTimer() {
if b.timerOn {
b.start = time.Now()
}
b.duration = 0
}
// runN runs a single benchmark for the specified number of iterations.
func (b *B) runN(n int) {
b.N = n
b.ResetTimer()
b.StartTimer()
b.benchFunc(b)
b.StopTimer()
}
func min(x, y int64) int64 {
if x > y {
return y
}
return x
}
func max(x, y int64) int64 {
if x < y {
return y
}
return x
}
// run1 runs the first iteration of benchFunc. It reports whether more
// iterations of this benchmarks should be run.
func (b *B) run1() bool {
b.runN(1)
return !b.hasSub
}
// run executes the benchmark.
func (b *B) run() {
b.launch()
}
// launch launches the benchmark function. It gradually increases the number
// of benchmark iterations until the benchmark runs for the requested benchtime.
// run1 must have been called on b.
func (b *B) launch() {
d := b.benchTime.d
for n := int64(1); !b.failed && b.duration < d && n < 1e9; {
last := n
// Predict required iterations.
goalns := d.Nanoseconds()
prevIters := int64(b.N)
prevns := b.duration.Nanoseconds()
if prevns <= 0 {
// Round up, to avoid div by zero.
prevns = 1
}
// Order of operations matters.
// For very fast benchmarks, prevIters ~= prevns.
// If you divide first, you get 0 or 1,
// which can hide an order of magnitude in execution time.
// So multiply first, then divide.
n = goalns * prevIters / prevns
// Run more iterations than we think we'll need (1.2x).
n += n / 5
// Don't grow too fast in case we had timing errors previously.
n = min(n, 100*last)
// Be sure to run at least one more than last time.
n = max(n, last+1)
// Don't run more than 1e9 times. (This also keeps n in int range on 32 bit platforms.)
n = min(n, 1e9)
b.runN(int(n))
}
b.result = BenchmarkResult{b.N, b.duration}
}
// Run benchmarks f as a subbenchmark with the given name. It reports
// true if the subbenchmark succeeded.
//
// A subbenchmark is like any other benchmark. A benchmark that calls Run at
// least once will not be measured itself and will be called once with N=1.
func (b *B) Run(name string, f func(b *B)) bool {
b.hasSub = true
sub := &B{
common: common{name: name},
benchFunc: f,
benchTime: b.benchTime,
}
if sub.run1() {
sub.run()
}
b.add(sub.result)
return !sub.failed
}
// Benchmark benchmarks a single function. It is useful for creating
// custom benchmarks that do not use the "go test" command.
//
// If f calls Run, the result will be an estimate of running all its
// subbenchmarks that don't call Run in sequence in a single benchmark.
func Benchmark(f func(b *B)) BenchmarkResult {
b := &B{
benchFunc: f,
benchTime: benchTime,
}
if b.run1() {
b.run()
}
return b.result
}
// add simulates running benchmarks in sequence in a single iteration. It is
// used to give some meaningful results in case func Benchmark is used in
// combination with Run.
func (b *B) add(other BenchmarkResult) {
r := &b.result
// The aggregated BenchmarkResults resemble running all subbenchmarks as
// in sequence in a single benchmark.
r.N = 1
r.T += time.Duration(other.NsPerOp())
} }
+50
View File
@@ -0,0 +1,50 @@
// Copyright 2011 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package testing_test
import (
"testing"
)
var buf = make([]byte, 13579)
func NonASCII(b []byte, i int, offset int) int {
for i = offset; i < len(b)+offset; i++ {
if b[i%len(b)] >= 0x80 {
break
}
}
return i
}
func BenchmarkFastNonASCII(b *testing.B) {
var val int
for i := 0; i < b.N; i++ {
val += NonASCII(buf, 0, 0)
}
}
func BenchmarkSlowNonASCII(b *testing.B) {
var val int
for i := 0; i < b.N; i++ {
val += NonASCII(buf, 0, 0)
val += NonASCII(buf, 0, 1)
}
}
// TestBenchmark simply uses Benchmark twice and makes sure it does not crash.
func TestBenchmark(t *testing.T) {
// FIXME: reduce runtime from the current 3 seconds.
rslow := testing.Benchmark(BenchmarkSlowNonASCII)
rfast := testing.Benchmark(BenchmarkFastNonASCII)
tslow := rslow.NsPerOp()
tfast := rfast.NsPerOp()
// Be exceedingly forgiving; do not fail even if system gets busy.
speedup := float64(tslow) / float64(tfast)
if speedup < 0.3 {
t.Errorf("Expected speedup >= 0.3, got %f", speedup)
}
}
+62 -16
View File
@@ -10,15 +10,34 @@ package testing
import ( import (
"bytes" "bytes"
"flag"
"fmt" "fmt"
"io"
"os" "os"
"strings"
) )
// Testing flags.
var (
flagVerbose bool
)
var initRan bool
// Init registers testing flags. It has no effect if it has already run.
func Init() {
if initRan {
return
}
initRan = true
flag.BoolVar(&flagVerbose, "test.v", false, "verbose: print additional output")
}
// common holds the elements common between T and B and // common holds the elements common between T and B and
// captures common methods such as Errorf. // captures common methods such as Errorf.
type common struct { type common struct {
output io.Writer output bytes.Buffer
indent string
failed bool // Test or benchmark has failed. failed bool // Test or benchmark has failed.
skipped bool // Test of benchmark has been skipped. skipped bool // Test of benchmark has been skipped.
@@ -53,7 +72,6 @@ var _ TB = (*B)(nil)
// //
type T struct { type T struct {
common common
indent string
} }
// Name returns the name of the running test or benchmark. // Name returns the name of the running test or benchmark.
@@ -85,8 +103,22 @@ func (c *common) FailNow() {
// log generates the output. // log generates the output.
func (c *common) log(s string) { func (c *common) log(s string) {
// This doesn't print the same as in upstream go, but works for now. // This doesn't print the same as in upstream go, but works for now.
fmt.Fprintf(c.output, "\t") if len(s) != 0 && s[len(s)-1] == '\n' {
fmt.Fprintln(c.output, s) s = s[:len(s)-1]
}
lines := strings.Split(s, "\n")
// First line.
c.output.WriteString(c.indent)
c.output.WriteString(" ") // 4 spaces
c.output.WriteString(lines[0])
c.output.WriteByte('\n')
// More lines.
for _, line := range lines[1:] {
c.output.WriteString(c.indent)
c.output.WriteString(" ") // 8 spaces
c.output.WriteString(line)
c.output.WriteByte('\n')
}
} }
// Log formats its arguments using default formatting, analogous to Println, // Log formats its arguments using default formatting, analogous to Println,
@@ -165,25 +197,30 @@ func (c *common) Helper() {
func (t *T) Run(name string, f func(t *T)) bool { func (t *T) Run(name string, f func(t *T)) bool {
// Create a subtest. // Create a subtest.
sub := T{ sub := T{
indent: t.indent + " ",
common: common{ common: common{
name: t.name + "/" + name, name: t.name + "/" + name,
output: &bytes.Buffer{}, indent: t.indent + " ",
}, },
} }
// Run the test. // Run the test.
fmt.Printf("=== RUN %s\n", sub.name) if flagVerbose {
fmt.Fprintf(&t.output, "=== RUN %s\n", sub.name)
}
f(&sub) f(&sub)
// Process the result (pass or fail). // Process the result (pass or fail).
if sub.failed { if sub.failed {
t.failed = true t.failed = true
fmt.Printf(sub.indent+"--- FAIL: %s\n", sub.name) fmt.Fprintf(&t.output, sub.indent+"--- FAIL: %s\n", sub.name)
t.output.Write(sub.output.Bytes())
} else { } else {
fmt.Printf(sub.indent+"--- PASS: %s\n", sub.name) if flagVerbose {
fmt.Fprintf(&t.output, sub.indent+"--- PASS: %s\n", sub.name)
t.output.Write(sub.output.Bytes())
}
} }
fmt.Print(sub.output)
return !sub.failed return !sub.failed
} }
@@ -205,24 +242,32 @@ func (m *M) Run() int {
fmt.Fprintln(os.Stderr, "testing: warning: no tests to run") fmt.Fprintln(os.Stderr, "testing: warning: no tests to run")
} }
if !flag.Parsed() {
flag.Parse()
}
failures := 0 failures := 0
for _, test := range m.Tests { for _, test := range m.Tests {
t := &T{ t := &T{
common: common{ common: common{
name: test.Name, name: test.Name,
output: &bytes.Buffer{},
}, },
} }
fmt.Printf("=== RUN %s\n", test.Name) if flagVerbose {
fmt.Printf("=== RUN %s\n", test.Name)
}
test.F(t) test.F(t)
if t.failed { if t.failed {
fmt.Printf("--- FAIL: %s\n", test.Name) fmt.Printf("--- FAIL: %s\n", test.Name)
os.Stdout.Write(t.output.Bytes())
} else { } else {
fmt.Printf("--- PASS: %s\n", test.Name) if flagVerbose {
fmt.Printf("--- PASS: %s\n", test.Name)
os.Stdout.Write(t.output.Bytes())
}
} }
fmt.Print(t.output)
if t.failed { if t.failed {
failures++ failures++
@@ -242,6 +287,7 @@ func TestMain(m *M) {
} }
func MainStart(deps interface{}, tests []InternalTest, benchmarks []InternalBenchmark, examples []InternalExample) *M { func MainStart(deps interface{}, tests []InternalTest, benchmarks []InternalBenchmark, examples []InternalExample) *M {
Init()
return &M{ return &M{
Tests: tests, Tests: tests,
} }
+8
View File
@@ -0,0 +1,8 @@
{
"inherits": ["atsamd21g18a"],
"build-tags": ["arduino_mkrwifi1010"],
"serial": "usb",
"serial-port": ["acm:2341:8054", "acm:2341:0054"],
"flash-command": "bossac -i -e -w -v -R -U --port={port} --offset=0x2000 {bin}",
"flash-1200-bps-reset": "true"
}
+1 -1
View File
@@ -2,7 +2,7 @@
MEMORY MEMORY
{ {
FLASH_TEXT (rw) : ORIGIN = 0x00000000+0x26000, LENGTH = 0xED000-0x26000 /* SoftDevice S140. See https://learn.adafruit.com/introducing-the-adafruit-nrf52840-feather/hathach-memory-map. Application starts at 0x26000; user data starts at 0xED000 */ FLASH_TEXT (rw) : ORIGIN = 0x00000000+0x26000, LENGTH = 0xED000-0x26000 /* SoftDevice S140. See https://learn.adafruit.com/introducing-the-adafruit-nrf52840-feather/hathach-memory-map. Application starts at 0x26000; user data starts at 0xED000 */
RAM (xrw) : ORIGIN = 0x20004180, LENGTH = 37K RAM (xrw) : ORIGIN = 0x20004180, LENGTH = 0x20040000-0x20004180
} }
_stack_size = 2K; _stack_size = 2K;
+42 -10
View File
@@ -5,16 +5,48 @@
_start: _start:
b start_vector b start_vector
.fill 156,1,0 // Nintendo Logo Character Data (8000004h)
.fill 16,1,0 // Game Title // ROM header
.byte 0x30,0x31 // Maker Code (80000B0h) .byte 0x24,0xff,0xae,0x51,0x69,0x9a,0xa2,0x21,0x3d,0x84,0x82,0x0a,0x84,0xe4,0x09,0xad
.byte 0x96 // Fixed Value (80000B2h) .byte 0x11,0x24,0x8b,0x98,0xc0,0x81,0x7f,0x21,0xa3,0x52,0xbe,0x19,0x93,0x09,0xce,0x20
.byte 0x00 // Main Unit Code (80000B3h) .byte 0x10,0x46,0x4a,0x4a,0xf8,0x27,0x31,0xec,0x58,0xc7,0xe8,0x33,0x82,0xe3,0xce,0xbf
.byte 0x00 // Device Type (80000B4h) .byte 0x85,0xf4,0xdf,0x94,0xce,0x4b,0x09,0xc1,0x94,0x56,0x8a,0xc0,0x13,0x72,0xa7,0xfc
.fill 7,1,0 // unused .byte 0x9f,0x84,0x4d,0x73,0xa3,0xca,0x9a,0x61,0x58,0x97,0xa3,0x27,0xfc,0x03,0x98,0x76
.byte 0x00 // Software Version No (80000BCh) .byte 0x23,0x1d,0xc7,0x61,0x03,0x04,0xae,0x56,0xbf,0x38,0x84,0x00,0x40,0xa7,0x0e,0xfd
.byte 0xf0 // Complement Check (80000BDh) .byte 0xff,0x52,0xfe,0x03,0x6f,0x95,0x30,0xf1,0x97,0xfb,0xc0,0x85,0x60,0xd6,0x80,0x25
.byte 0x00,0x00 // Checksum (80000BEh) .byte 0xa9,0x63,0xbe,0x03,0x01,0x4e,0x38,0xe2,0xf9,0xa2,0x34,0xff,0xbb,0x3e,0x03,0x44
.byte 0x78,0x00,0x90,0xcb,0x88,0x11,0x3a,0x94,0x65,0xc0,0x7c,0x63,0x87,0xf0,0x3c,0xaf
.byte 0xd6,0x25,0xe4,0x8b,0x38,0x0a,0xac,0x72,0x21,0xd4,0xf8,0x07
// Game title
.byte 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00
// Game code
.byte 0x00,0x00,0x00,0x00
// Maker code
.byte 0x00,0x00
// Fixed value
.byte 0x96
// Main unit code
.byte 0x00
// Device type (0x00 retail, 0x80 debug)
.byte 0x00
// Reserved
.byte 0x00,0x00,0x00,0x00,0x00,0x00,0x00
// Software version
.byte 0x00
// Complement check
.byte 0x51
// Reserved area
.space 98
start_vector: start_vector:
// Configure stacks // Configure stacks
+12
View File
@@ -0,0 +1,12 @@
{
"inherits": ["nrf52840"],
"build-tags": ["mdbt50qrx", "nrf52840_reset_uf2", "softdevice", "s140v6"],
"serial": "usb",
"flash-1200-bps-reset": "true",
"flash-method": "msd",
"serial-port": ["acm:239a:801b", "acm:239a:010b", "acm:239a:810c"],
"msd-volume-name": "MDBT50QBOOT",
"msd-firmware-name": "firmware.uf2",
"uf2-family-id": "0xADA52840",
"linkerscript": "targets/circuitplay-bluefruit.ld"
}
+3 -1
View File
@@ -9,5 +9,7 @@
"rp2040-boot-patch": true, "rp2040-boot-patch": true,
"extra-files": [ "extra-files": [
"src/device/rp/rp2040.s" "src/device/rp/rp2040.s"
] ],
"openocd-transport": "swd",
"openocd-target": "rp2040"
} }
+4
View File
@@ -285,6 +285,10 @@
throw 'trying to exit with code ' + code; throw 'trying to exit with code ' + code;
} }
}, },
random_get: (bufPtr, bufLen) => {
crypto.getRandomValues(loadSlice(bufPtr, bufLen));
return 0;
},
}, },
env: { env: {
// func ticks() float64 // func ticks() float64
-23
View File
@@ -1,7 +1,6 @@
package main package main
import ( import (
"crypto/rand"
"os" "os"
) )
@@ -21,26 +20,4 @@ func main() {
for _, arg := range os.Args[1:] { for _, arg := range os.Args[1:] {
println("arg:", arg) println("arg:", arg)
} }
// Check for crypto/rand support.
checkRand()
}
func checkRand() {
buf := make([]byte, 500)
n, err := rand.Read(buf)
if n != len(buf) || err != nil {
println("could not read random numbers:", err)
}
// Very simple test that random numbers are at least somewhat random.
sum := 0
for _, b := range buf {
sum += int(b)
}
if sum < 95*len(buf) || sum > 159*len(buf) {
println("random numbers don't seem that random, the average byte is", sum/len(buf))
} else {
println("random number check was successful")
}
} }
-1
View File
@@ -3,4 +3,3 @@ ENV2: VALUE2
arg: first arg: first
arg: second arg: second
random number check was successful
+34
View File
@@ -0,0 +1,34 @@
package main
// Test new language features introduced in Go 1.17:
// https://tip.golang.org/doc/go1.17#language
// Once this becomes the minimum Go version of TinyGo, these tests should be
// merged with the regular slice tests.
import "unsafe"
func main() {
// Test conversion from array to slice.
slice1 := []int{1, 2, 3, 4}
arr1 := (*[4]int)(slice1)
arr1[1] = -2
arr1[2] = 20
println("slice to array pointer:", arr1[0], arr1[1], arr1[2], arr1[3])
// Test unsafe.Add.
arr2 := [...]int{1, 2, 3, 4}
*(*int)(unsafe.Add(unsafe.Pointer(&arr2[0]), unsafe.Sizeof(int(1))*1)) = 5
*addInt(&arr2[0], 2) = 8
println("unsafe.Add array:", arr2[0], arr2[1], arr2[2], arr2[3])
// Test unsafe.Slice.
arr3 := [...]int{1, 2, 3, 4}
slice3 := unsafe.Slice(&arr3[1], 3)
slice3[0] = 9
slice3[1] = 15
println("unsafe.Slice array:", len(slice3), cap(slice3), slice3[0], slice3[1], slice3[2])
}
func addInt(ptr *int, index uintptr) *int {
return (*int)(unsafe.Add(unsafe.Pointer(ptr), unsafe.Sizeof(int(1))*index))
}
+3
View File
@@ -0,0 +1,3 @@
slice to array pointer: 1 -2 20 4
unsafe.Add array: 1 5 8 4
unsafe.Slice array: 3 3 9 15 4
+12
View File
@@ -76,6 +76,8 @@ func main() {
testGoOnBuiltins() testGoOnBuiltins()
testCond() testCond()
testIssue1790()
} }
func acquire(m *sync.Mutex) { func acquire(m *sync.Mutex) {
@@ -198,3 +200,13 @@ func testCond() {
panic("missing queued notification") panic("missing queued notification")
} }
} }
var once sync.Once
// This tests a fix for issue 1790:
// https://github.com/tinygo-org/tinygo/issues/1790
func testIssue1790() *int {
once.Do(func() {})
i := 0
return &i
}
+24
View File
@@ -0,0 +1,24 @@
package main
import "crypto/rand"
// TODO: make this a test in the crypto/rand package.
func main() {
buf := make([]byte, 500)
n, err := rand.Read(buf)
if n != len(buf) || err != nil {
println("could not read random numbers:", err)
}
// Very simple test that random numbers are at least somewhat random.
sum := 0
for _, b := range buf {
sum += int(b)
}
if sum < 95*len(buf) || sum > 159*len(buf) {
println("random numbers don't seem that random, the average byte is", sum/len(buf))
} else {
println("random number check was successful")
}
}
+1
View File
@@ -0,0 +1 @@
random number check was successful
+1
View File
@@ -398,6 +398,7 @@ func showValue(rv reflect.Value, indent string) {
println(indent+" field:", i, field.Name) println(indent+" field:", i, field.Name)
println(indent+" tag:", field.Tag) println(indent+" tag:", field.Tag)
println(indent+" embedded:", field.Anonymous) println(indent+" embedded:", field.Anonymous)
println(indent+" exported:", field.IsExported())
showValue(rv.Field(i), indent+" ") showValue(rv.Field(i), indent+" ")
} }
default: default:
+13
View File
@@ -235,6 +235,7 @@ reflect type: struct
field: 0 error field: 0 error
tag: tag:
embedded: true embedded: true
exported: false
reflect type: interface reflect type: interface
interface interface
nil: true nil: true
@@ -243,16 +244,19 @@ reflect type: struct
field: 0 a field: 0 a
tag: tag:
embedded: false embedded: false
exported: false
reflect type: uint8 reflect type: uint8
uint: 42 uint: 42
field: 1 b field: 1 b
tag: tag:
embedded: false embedded: false
exported: false
reflect type: int16 reflect type: int16
int: 321 int: 321
field: 2 c field: 2 c
tag: tag:
embedded: false embedded: false
exported: false
reflect type: int8 reflect type: int8
int: 123 int: 123
reflect type: struct comparable=false reflect type: struct comparable=false
@@ -260,31 +264,37 @@ reflect type: struct comparable=false
field: 0 n field: 0 n
tag: foo:"bar" tag: foo:"bar"
embedded: false embedded: false
exported: false
reflect type: int reflect type: int
int: 5 int: 5
field: 1 some field: 1 some
tag: tag:
embedded: false embedded: false
exported: false
reflect type: struct reflect type: struct
struct: 2 struct: 2
field: 0 X field: 0 X
tag: tag:
embedded: false embedded: false
exported: true
reflect type: int16 reflect type: int16
int: -5 int: -5
field: 1 Y field: 1 Y
tag: tag:
embedded: false embedded: false
exported: true
reflect type: int16 reflect type: int16
int: 3 int: 3
field: 2 zero field: 2 zero
tag: tag:
embedded: false embedded: false
exported: false
reflect type: struct reflect type: struct
struct: 0 struct: 0
field: 3 buf field: 3 buf
tag: tag:
embedded: false embedded: false
exported: false
reflect type: slice comparable=false reflect type: slice comparable=false
slice: uint8 2 2 slice: uint8 2 2
pointer: true pointer: true
@@ -298,6 +308,7 @@ reflect type: struct comparable=false
field: 4 Buf field: 4 Buf
tag: tag:
embedded: false embedded: false
exported: true
reflect type: slice comparable=false reflect type: slice comparable=false
slice: uint8 1 1 slice: uint8 1 1
pointer: true pointer: true
@@ -313,12 +324,14 @@ reflect type: ptr
field: 0 next field: 0 next
tag: description:"chain" tag: description:"chain"
embedded: false embedded: false
exported: false
reflect type: ptr addrable=true reflect type: ptr addrable=true
pointer: false struct pointer: false struct
nil: true nil: true
field: 1 foo field: 1 foo
tag: tag:
embedded: false embedded: false
exported: false
reflect type: int addrable=true reflect type: int addrable=true
int: 42 int: 42
reflect type: ptr reflect type: ptr
+42
View File
@@ -0,0 +1,42 @@
package main
// TODO: also test the verbose version.
import (
"testing"
)
func TestFoo(t *testing.T) {
t.Log("log Foo.a")
t.Log("log Foo.b")
}
func TestBar(t *testing.T) {
t.Log("log Bar")
t.Log("log g\nh\ni\n")
t.Run("Bar1", func(t *testing.T) {})
t.Run("Bar2", func(t *testing.T) {
t.Log("log Bar2\na\nb\nc")
t.Error("failed")
t.Log("after failed")
})
t.Run("Bar3", func(t *testing.T) {})
t.Log("log Bar end")
}
var tests = []testing.InternalTest{
{"TestFoo", TestFoo},
{"TestBar", TestBar},
}
var benchmarks = []testing.InternalBenchmark{}
var examples = []testing.InternalExample{}
func main() {
m := testing.MainStart(nil, tests, benchmarks, examples)
exitcode := m.Run()
if exitcode != 0 {
println("exitcode:", exitcode)
}
}
+16
View File
@@ -0,0 +1,16 @@
--- FAIL: TestBar
log Bar
log g
h
i
--- FAIL: TestBar/Bar2
log Bar2
a
b
c
failed
after failed
log Bar end
FAIL
exitcode: 1
+1 -2
View File
@@ -11,8 +11,7 @@ func TestChan(t *testing.T) {
t.Parallel() t.Parallel()
wasmTmpDir, server, cleanup := startServer(t) wasmTmpDir, server := startServer(t)
defer cleanup()
err := run("tinygo build -o " + wasmTmpDir + "/chan.wasm -target wasm testdata/chan.go") err := run("tinygo build -o " + wasmTmpDir + "/chan.wasm -target wasm testdata/chan.go")
if err != nil { if err != nil {
+1 -4
View File
@@ -1,5 +1,3 @@
// +build go1.14
package wasm package wasm
import ( import (
@@ -13,8 +11,7 @@ func TestEvent(t *testing.T) {
t.Parallel() t.Parallel()
wasmTmpDir, server, cleanup := startServer(t) wasmTmpDir, server := startServer(t)
defer cleanup()
err := run("tinygo build -o " + wasmTmpDir + "/event.wasm -target wasm testdata/event.go") err := run("tinygo build -o " + wasmTmpDir + "/event.wasm -target wasm testdata/event.go")
if err != nil { if err != nil {
+1 -11
View File
@@ -1,14 +1,5 @@
// +build go1.14
package wasm 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 ( import (
"testing" "testing"
"time" "time"
@@ -20,8 +11,7 @@ func TestFmt(t *testing.T) {
t.Parallel() t.Parallel()
wasmTmpDir, server, cleanup := startServer(t) wasmTmpDir, server := startServer(t)
defer cleanup()
err := run("tinygo build -o " + wasmTmpDir + "/fmt.wasm -target wasm testdata/fmt.go") err := run("tinygo build -o " + wasmTmpDir + "/fmt.wasm -target wasm testdata/fmt.go")
if err != nil { if err != nil {
+1 -4
View File
@@ -1,5 +1,3 @@
// +build go1.14
package wasm package wasm
import ( import (
@@ -13,8 +11,7 @@ func TestFmtprint(t *testing.T) {
t.Parallel() t.Parallel()
wasmTmpDir, server, cleanup := startServer(t) wasmTmpDir, server := startServer(t)
defer cleanup()
err := run("tinygo build -o " + wasmTmpDir + "/fmtprint.wasm -target wasm testdata/fmtprint.go") err := run("tinygo build -o " + wasmTmpDir + "/fmtprint.wasm -target wasm testdata/fmtprint.go")
if err != nil { if err != nil {
+1 -4
View File
@@ -1,5 +1,3 @@
// +build go1.14
package wasm package wasm
import ( import (
@@ -13,8 +11,7 @@ func TestLog(t *testing.T) {
t.Parallel() t.Parallel()
wasmTmpDir, server, cleanup := startServer(t) wasmTmpDir, server := startServer(t)
defer cleanup()
err := run("tinygo build -o " + wasmTmpDir + "/log.wasm -target wasm testdata/log.go") err := run("tinygo build -o " + wasmTmpDir + "/log.wasm -target wasm testdata/log.go")
if err != nil { if err != nil {
+4 -19
View File
@@ -4,11 +4,9 @@ import (
"context" "context"
"errors" "errors"
"fmt" "fmt"
"io/ioutil"
"log" "log"
"net/http" "net/http"
"net/http/httptest" "net/http/httptest"
"os"
"os/exec" "os/exec"
"regexp" "regexp"
"strings" "strings"
@@ -47,12 +45,8 @@ func chromectx(timeout time.Duration) (context.Context, context.CancelFunc) {
return ctx, cancel return ctx, cancel
} }
func startServer(t *testing.T) (string, *httptest.Server, func()) { func startServer(t *testing.T) (string, *httptest.Server) {
// In Go 1.15, all this can be replaced by t.TempDir() tmpDir := t.TempDir()
tmpDir, err := ioutil.TempDir("", "wasm_test")
if err != nil {
t.Fatalf("unable to create temp dir: %v", err)
}
fsh := http.FileServer(http.Dir(tmpDir)) fsh := http.FileServer(http.Dir(tmpDir))
h := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { h := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
@@ -124,18 +118,9 @@ if (wasmSupported) {
server := httptest.NewServer(h) server := httptest.NewServer(h)
t.Logf("Started server at %q for dir: %s", server.URL, tmpDir) t.Logf("Started server at %q for dir: %s", server.URL, tmpDir)
t.Cleanup(server.Close)
// In Go 1.14+, this can be replaced by t.Cleanup() return tmpDir, server
cleanup := func() {
err := os.RemoveAll(tmpDir)
if err != nil {
t.Error(err)
}
server.Close()
}
return tmpDir, server, cleanup
} }
// waitLog blocks until the log output equals the text provided (ignoring whitespace before and after) // waitLog blocks until the log output equals the text provided (ignoring whitespace before and after)
+34 -2
View File
@@ -246,6 +246,7 @@ func MakeGCStackSlots(mod llvm.Module) bool {
} }
// Do a store to the stack object after each new pointer that is created. // Do a store to the stack object after each new pointer that is created.
pointerStores := make(map[llvm.Value]struct{})
for i, ptr := range pointers { for i, ptr := range pointers {
// Insert the store after the pointer value is created. // Insert the store after the pointer value is created.
insertionPoint := llvm.NextInstruction(ptr) insertionPoint := llvm.NextInstruction(ptr)
@@ -263,13 +264,44 @@ func MakeGCStackSlots(mod llvm.Module) bool {
}, "") }, "")
// Store the pointer into the stack slot. // Store the pointer into the stack slot.
builder.CreateStore(ptr, gep) store := builder.CreateStore(ptr, gep)
pointerStores[store] = struct{}{}
} }
// Make sure this stack object is popped from the linked list of stack // Make sure this stack object is popped from the linked list of stack
// objects at return. // objects at return.
for _, ret := range returns { for _, ret := range returns {
builder.SetInsertPointBefore(ret) inst := ret
// Try to do the popping of the stack object earlier, by inserting
// it not right before the return instruction but moving the insert
// position up.
// This is necessary so that the GC stack slot pass doesn't
// interfere with tail calls (in particular, musttail calls).
for {
prevInst := llvm.PrevInstruction(inst)
if prevInst == parent {
break
}
if _, ok := pointerStores[prevInst]; ok {
// Pop the stack object after the last store instruction.
// This can probably be made more efficient: storing to the
// stack chain object and then immediately popping isn't
// useful.
break
}
if prevInst.IsNil() {
// Start of basic block. Pop the stack object here.
break
}
if !prevInst.IsAPHINode().IsNil() {
// Do not insert before a PHI node. PHI nodes must be
// grouped at the beginning of a basic block before any
// other instruction.
break
}
inst = prevInst
}
builder.SetInsertPointBefore(inst)
builder.CreateStore(parent, stackChainStart) builder.CreateStore(parent, stackChainStart)
} }
} }
+8
View File
@@ -20,6 +20,10 @@ define i8* @needsStackSlots() {
; so tracking it is not really necessary. ; so tracking it is not really necessary.
%ptr = call i8* @runtime.alloc(i32 4) %ptr = call i8* @runtime.alloc(i32 4)
call void @runtime.trackPointer(i8* %ptr) call void @runtime.trackPointer(i8* %ptr)
; Restoring the stack pointer can happen at this position, before the return.
; This avoids issues with tail calls.
call void @someArbitraryFunction()
%val = load i8, i8* @someGlobal
ret i8* %ptr ret i8* %ptr
} }
@@ -95,3 +99,7 @@ define void @testGEPBitcast() {
call void @runtime.trackPointer(i8* %other) call void @runtime.trackPointer(i8* %other)
ret void ret void
} }
define void @someArbitraryFunction() {
ret void
}
+7 -1
View File
@@ -26,6 +26,8 @@ define i8* @needsStackSlots() {
%4 = getelementptr { %runtime.stackChainObject*, i32, i8* }, { %runtime.stackChainObject*, i32, i8* }* %gc.stackobject, i32 0, i32 2 %4 = getelementptr { %runtime.stackChainObject*, i32, i8* }, { %runtime.stackChainObject*, i32, i8* }* %gc.stackobject, i32 0, i32 2
store i8* %ptr, i8** %4 store i8* %ptr, i8** %4
store %runtime.stackChainObject* %1, %runtime.stackChainObject** @runtime.stackChainStart store %runtime.stackChainObject* %1, %runtime.stackChainObject** @runtime.stackChainStart
call void @someArbitraryFunction()
%val = load i8, i8* @someGlobal
ret i8* %ptr ret i8* %ptr
} }
@@ -73,8 +75,8 @@ define i8* @fibNext(i8* %x, i8* %y) {
%out.alloc = call i8* @runtime.alloc(i32 1) %out.alloc = call i8* @runtime.alloc(i32 1)
%4 = getelementptr { %runtime.stackChainObject*, i32, i8* }, { %runtime.stackChainObject*, i32, i8* }* %gc.stackobject, i32 0, i32 2 %4 = getelementptr { %runtime.stackChainObject*, i32, i8* }, { %runtime.stackChainObject*, i32, i8* }* %gc.stackobject, i32 0, i32 2
store i8* %out.alloc, i8** %4 store i8* %out.alloc, i8** %4
store i8 %out.val, i8* %out.alloc
store %runtime.stackChainObject* %1, %runtime.stackChainObject** @runtime.stackChainStart store %runtime.stackChainObject* %1, %runtime.stackChainObject** @runtime.stackChainStart
store i8 %out.val, i8* %out.alloc
ret i8* %out.alloc ret i8* %out.alloc
} }
@@ -135,3 +137,7 @@ define void @testGEPBitcast() {
store %runtime.stackChainObject* %1, %runtime.stackChainObject** @runtime.stackChainStart store %runtime.stackChainObject* %1, %runtime.stackChainObject** @runtime.stackChainStart
ret void ret void
} }
define void @someArbitraryFunction() {
ret void
}