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