Compare commits

...

67 Commits

Author SHA1 Message Date
Ayke van Laethem 7d5542dda7 main: version 0.7.1
This release contains a small fix for some atsamd21-based boards to make
them usable on macOS: it now allows to specify the serial port using the
-port flag.
2019-07-27 10:02:10 -07:00
Ayke van Laethem 8eb6039052 ci: fix Go image on the Debian Stretch images
The build broke because the images got upgraded from stretch to buster.
Specify the stretch images (for now) so that it works again.
We can upgrade to buster for go1.12 at a later time.
2019-07-27 09:51:38 -07:00
Trevor Rosen 64597de344 Device path at flash time for several boards 2019-07-25 10:17:16 -07:00
Ayke van Laethem 515daa7d3c main: version 0.7.0 2019-07-17 15:36:38 +02:00
Ron Evans ced964f039 docs: add Arduino Nano33 IoT to README
Signed-off-by: Ron Evans <ron@hybridgroup.com>
2019-07-17 06:30:58 -07:00
Ayke van Laethem 8a704e43fc os: gofmt 2019-07-15 17:53:01 +02:00
Justin Clift 847681457f runtime: add several os package stubs 2019-07-15 01:18:37 +02:00
Ayke van Laethem 66d8899aa7 main: set the current working directory when calling an external linker
In particular, while LLVM lld supports -L for linker scripts imported
with the `INCLUDE` command, GNU ld does not seem to support this.

This is a prerequisite for supporting the HiFive1 board in the TinyGo
Playground.
2019-07-08 00:37:53 +02:00
Ayke van Laethem b0cad7ed63 runtime: add support for math intrinsics where supported
In particular, add support for a few math intrinsics for WebAssembly,
but add a few intrinsics to other systems as well at the same time. Some
may be missing still but will be easy to add if needed.

This increases the performance of one example by 50% to 100% depending
on the browser: the bottleneck was the inefficient sqrt implementation.
2019-07-08 00:32:42 +02:00
Ayke van Laethem 00cc486619 wasm: set the stack at the start of linear memory
This makes sure that a stack overflow will cause a "memory access out of
bounds" error instead of a corruption of a global variable.

Here is more background on a very similar stack overflow protection:
https://blog.japaric.io/stack-overflow-protection/
2019-07-08 00:09:59 +02:00
Ayke van Laethem d627208c48 all: make WebAssembly initial linear memory size configurable
When the target supports it, allow the (initial) heap size to be
configured. Currently only supported in WebAssembly.

This also changes the default heap size of WebAssembly from 64kB to 1MB.
2019-07-08 00:09:59 +02:00
Ayke van Laethem 152caa3b0a compiler: do not create stack objects for functions that don't allocate
This is a useful optimization for targets with the portable garbage
collector. It isn't as big as you might guess but it does optimize
functions inside the garbage collector itself (which obviously should
not allocate). WebAssembly output in one test is about 1% smaller.
2019-07-08 00:02:28 +02:00
Ayke van Laethem 7ed6b45149 compiler: add the //go:noinline pragma
This is directly useful to avoid some unsafety around runtime.alloc and
should be useful in general.

This pragma has the same form as in the main Go compiler:
https://github.com/golang/go/issues/12312
2019-07-08 00:02:28 +02:00
Ayke van Laethem c66d979ba3 compiler: avoid some stack frames when this is unnecessary
Some instructions do not create new values, they transform existing
values in some way (bitcast, getelementptr, etc.). Do not store them in
the stack object.

This lowers the size of the repulsion demo from 100kB to 98kB (with a
baseline of 72kB for the leaking GC). That's a useful reduction in file
size.
2019-07-08 00:02:28 +02:00
Ron Evans fc9188a298 machine/samd21/arduino-nano33: adds support for Arduino Nano33 IoT along with mapping to NINA-W102 WiFi chip.
Also adds DTR and RTS functions along with timeouts to USBCDC functions to prevent lockups.

Signed-off-by: Ron Evans <ron@hybridgroup.com>
2019-07-07 18:09:05 +02:00
Ayke van Laethem ffa38b183b all: add HiFive1 rev B board with RISC-V architecture
This page has been a big help in adding support for this new chip:
https://wiki.osdev.org/HiFive-1_Bare_Bones
2019-07-07 14:03:24 +02:00
Ayke van Laethem f0eb4eef5a ci: make smoketests more flexible 2019-07-07 14:03:24 +02:00
Ayke van Laethem fa928e8cd3 tools/gen-device-svd: be a bit more forgiving for stm32 svd files
Some newer files have a few mistakes. Allow them to be processed.
2019-07-06 17:16:18 +02:00
Ayke van Laethem c49d80628c tools/gen-device-svd: refactor to make the code more declarative 2019-07-06 17:16:18 +02:00
Ayke van Laethem af523c63d5 ci: fix cache paths for LLVM source
This makes the LLVM source code cacheable again.
2019-07-06 14:57:54 +02:00
diamondburned aabb6ba22b main: use zversion.go and VERSION files to detect version, fixes #433 2019-07-06 02:31:49 +02:00
Ayke van Laethem 6611578ec8 compiler: remove some TODOs
These have long since been implemented.
2019-07-03 21:22:12 +02:00
Ayke van Laethem d49a363d0d compiler: track all pointers returned by runtime.alloc
In particular, track the pointers to the memory allocated for coroutine
frames. Before this change, the following code would show memory
corruption:
https://github.com/johanbrandhorst/wasm-experiments/blob/master/canvas/main.go
2019-07-03 21:22:12 +02:00
Ayke van Laethem 385d1d0a5d compiler,runtime: implement a portable conservative GC 2019-07-01 16:30:33 +02:00
Ayke van Laethem 00e91ec569 all: rename garbage collectors
dumb -> leaking:
  make it more clear what this "GC" does: leak everything.
marksweep -> conservative:
  "marksweep" is too generic, use "conservative" to differentiate
  between future garbage collectors: precise marksweep / mark-compact /
  refcounting.
2019-07-01 13:03:07 +02:00
Daniel Esteban 1fd0c8d48c adds PowerSupplyActive to enable supply voltages to nRF52840 and (#430)
* machine/reelboard: adds PowerSupplyActive to enable supply voltages to nRF52840 and
peripherals.
2019-06-30 12:23:44 +02:00
Daniel Esteban d34bb7e708 add reelboard pins for the epaper display and the reset pin (#429)
* machine/reelboard: added descriptive pin names for the epaper display and the reset pin
2019-06-30 09:57:07 +02:00
Ayke van Laethem a328bbdff3 main: small refactor in error printing
Use a type switch instead of an if/else chain. This results in much more
readable code.
2019-06-29 19:18:46 +02:00
Ayke van Laethem 4ecd478d82 machine: add generic board support on non-baremetal hardware
Instead of trying to modify periperhals directly, external functions are
called. For example, __tinygo_gpio_set sets a GPIO pin to a specified
value (high or low). It is expected that binaries made this way will be
linked with some extra libraries that implement support for these
functions.

One particularly interesting case is this experimental board simulator:
https://github.com/aykevl/tinygo-play
Compiling code to WebAssembly with the correct build tag for a board
will enable this board to be simulated in the browser.

Atmel/Microchip based SAMD boards are not currently supported, because
their I2C/SPI support is somewhat uncommon and harder to support in the
machine API. They may require a modification to the machine API for
proper support.
2019-06-28 10:00:14 +02:00
Justin Clift 40b193f1fa circleci: update source and build cache keys 2019-06-24 16:22:37 +02:00
Justin Clift 7fd5ec8661 Makefile: use the LLVM monorepo
The -DLLVM_TOOL_CLANG_TOOLS_EXTRA_BUILD=OFF option turning off the
building of the extra clang tools is required, otherwise linking
the tinygo binary fails.
2019-06-24 16:22:37 +02:00
m-chichikalov 84618c45eb Added supporting suctom TargetSpec json file via -target flag. 2019-06-20 19:10:15 +02:00
Carolyn Van Slyck c0ff4e566d Include test in helptext 2019-06-20 19:05:06 +02:00
Carolyn Van Slyck ce9b21a270 Default package name to . when not specified
When running tinygo build, and the positional argument for the
package is not specified, default it to "."
2019-06-20 19:05:06 +02:00
Ayke van Laethem ed7c242a09 runtime: fix a heap corruption where some blocks were not marked as reachable
This is a rather critical error and I wonder why it hasn't been
discovered earlier.
2019-06-20 13:32:45 +02:00
Justin Clift 46872a70b1 Trivial typo fix 2019-06-20 13:30:38 +02:00
scriptonist e9f6e51fc8 compiler,runtime: implement string to []rune conversion
Commit message by aykevl
2019-06-19 01:17:21 +02:00
Ayke van Laethem fa5855bff5 main: add support for -tags flags 2019-06-18 16:02:20 +02:00
Ron Evans 16201c41dc test: replace ExitStatus() with go1.11 compatible syntax
Signed-off-by: Ron Evans <ron@hybridgroup.com>
2019-06-18 13:45:11 +02:00
Carolyn Van Slyck 208e1719ad Add test command to tinygo (#243)
* Add test command to tinygo
2019-06-18 12:23:59 +02:00
Ayke van Laethem a3d1f1a514 all: try more locations to find Clang built-in headers
This commit fixes the case where TinyGo was built with `go build` and
LLVM sources were never downloaded into the TinyGo source directory.
2019-06-13 15:21:04 +02:00
Tomer Elmalem 3e1c0d90c5 Add implementation for os.Exit and syscall.Exit 2019-06-12 19:04:34 +02:00
Ayke van Laethem b7197bcaae compiler,runtime: implement non-blocking selects
Blocking selects are much more complicated, so let's do non-blocking
ones first.
2019-06-12 18:26:52 +02:00
Ayke van Laethem 8890a0f3c8 compiler,runtime: store channel size in the channel itself
This may have a small effect on code size sometimes, but will simplify
the implementation of the select statement.
2019-06-12 18:26:52 +02:00
Justin Clift 5be412dabe Makefile: using an alternative LLVM build dir now works 2019-06-12 14:05:45 +02:00
Ayke van Laethem 412ec5b789 Makefile: drop the -lclangToolingRefactor library
It is not necessary and does not exist in LLVM 9.
2019-06-12 08:43:08 +02:00
Ayke van Laethem b0e767c4a7 compiler: move global handling from ir to compiler package
This is part of a larger rafactor that tries to shrink the ir package
and in general tries to shrink the amount of state that is kept around
in the compiler. The end goal is being able to compile packages
independent of each other, linking them together in a later stage. Along
the way, it cleans up lots of old cruft that has accumulated over the
months.

This refactor also results in globals being loaded lazily. This may be a
problem for some specific programs but will probably change back in a
commit in the near future.
2019-06-08 22:17:09 +02:00
Ayke van Laethem 6b5b4a681d compiler: refactor named types to create them lazily
This commit refactors named types to be created lazily. Instead of
defining all types in advance, do it only when necessary.
2019-06-08 22:17:09 +02:00
Ayke van Laethem 88bb61f287 compiler: return a valid (undef) value from a unsupported select
Returning a nil value may lead to problems later on. Just return undef
here, so that further compilation will at least be safe (the result
will be discarded anyway).
2019-06-08 21:48:05 +02:00
Ayke van Laethem e169e3b996 compiler: remove superfluous 'err' result in decodeFuncValue 2019-06-08 21:48:05 +02:00
Ayke van Laethem aa8957dd05 compiler: support non-constant syscall numbers
Not all syscalls use constant numbers.
2019-06-08 21:48:05 +02:00
Ayke van Laethem 8e5731aee7 compiler: add support for pointers as map keys 2019-06-08 21:48:05 +02:00
Justin Clift 0cc35b6188 Add a note to use --recursive when cloning the TinyGo repo 2019-06-08 22:50:14 +10:00
Ayke van Laethem 5cf7fba1a4 compiler: remove //go:volatile support 2019-06-06 19:46:49 +02:00
Ayke van Laethem e67506ee68 arm: update to avoid //go:volatile
This change results in changes to all smoketests for Cortex-M based
chips: they get a bit smaller (32-48 bytes). I'm not sure why but
probably because the inliner made a different inlining decision. There
was a similar effect when files generated from SVD files switched to the
new volatile types so it's probably harmless.
2019-06-06 19:46:49 +02:00
Ayke van Laethem f2c205a008 machine: update ringbuffer to use runtime/volatile.Register8
This avoids the //go:volatile pragma, which will be removed soon.
There were no changes to the output of the smoke tests.
2019-06-06 19:46:49 +02:00
Ayke van Laethem c84c625585 runtime: update to avoid //go:volatile
There was exactly one change in the output of the smoke tests:
examples/test. However, it still runs just fine on a PCA10040.
2019-06-06 19:46:49 +02:00
Ayke van Laethem 9673ad3774 all: move Register{8,16,32} values into runtime/volatile
This avoids duplication of code. None of the smoke tests have changed
their output.
2019-06-06 19:46:49 +02:00
Ayke van Laethem 66aca428ba compiler: rename import path if it lies in TINYGOPATH
This allows importing (for example) both
"github.com/tinygo-org/tinygo/src/machine" and "machine" without issues.
The former is renamed to just "machine".
2019-06-06 16:01:25 +02:00
Ayke van Laethem ec87811420 compiler: do not panic on duplicate functions
Instead, show a regular error message. This is much more user-friendly.
2019-06-06 16:01:25 +02:00
Ayke van Laethem 776dc1e0d9 main: show a better error when version detection of GOROOT failed 2019-06-06 13:59:37 +02:00
Ron Evans beea2f1f30 docs: add note to current/future contributors to please start by opening a GH issue to avoid duplication of effort
Signed-off-by: Ron Evans <ron@hybridgroup.com>
2019-06-06 12:09:17 +02:00
Ayke van Laethem 5f9c683abf main: return an error when running a command failed 2019-06-05 09:08:44 +02:00
Ayke van Laethem 0ce4d90779 cgo: add support for anonymous structs 2019-06-03 20:01:47 +02:00
Tomer Elmalem 1047c9bd05 reflect: stub out reflect.New and reflect.Zero 2019-06-03 19:26:47 +02:00
Ayke van Laethem 1d7cc2c242 cgo: add support for bitfields using generated getters and setters 2019-06-03 16:13:19 +02:00
Ayke van Laethem 23c8d15847 main: add -dev suffix to version 2019-06-03 16:09:29 +02:00
126 changed files with 3846 additions and 997 deletions
+26 -22
View File
@@ -44,20 +44,14 @@ commands:
steps:
- restore_cache:
keys:
- llvm-source-8-v3
- llvm-source-8-v5
- run:
name: "Fetch LLVM source"
command: make llvm-source
- save_cache:
key: llvm-source-8-v3
key: llvm-source-8-v5
paths:
- llvm
smoketest:
steps:
- run: make smoketest
smoketest-no-avr:
steps:
- run: make smoketest-no-avr
- llvm-project
test-linux:
steps:
- checkout
@@ -73,7 +67,7 @@ commands:
- run: go install .
- run: go test -v
- run: make gen-device -j4
- smoketest
- run: make smoketest RISCV=0
- save_cache:
key: go-cache-{{ checksum "Gopkg.lock" }}-{{ .Environment.CIRCLE_BUILD_NUM }}
paths:
@@ -106,7 +100,7 @@ commands:
- llvm-source-linux
- restore_cache:
keys:
- llvm-build-8-linux-v5
- llvm-build-8-linux-v7
- run:
name: "Build LLVM"
command: |
@@ -124,7 +118,7 @@ commands:
make llvm-build
fi
- save_cache:
key: llvm-build-8-linux-v5
key: llvm-build-8-linux-v7
paths:
llvm-build
- run:
@@ -155,7 +149,12 @@ commands:
tar -C ~/lib -xf /tmp/tinygo.linux-amd64.tar.gz
ln -s ~/lib/tinygo/bin/tinygo /go/bin/tinygo
tinygo version
- smoketest
- run:
name: "Download SiFive GNU toolchain"
command: |
curl -O https://static.dev.sifive.com/dev-tools/riscv64-unknown-elf-gcc-8.2.0-2019.05.3-x86_64-linux-ubuntu14.tar.gz
sudo tar -C /usr/local --strip-components=1 -xf riscv64-unknown-elf-gcc-8.2.0-2019.05.3-x86_64-linux-ubuntu14.tar.gz
- run: make smoketest
build-macos:
steps:
- checkout
@@ -169,17 +168,17 @@ commands:
HOMEBREW_NO_AUTO_UPDATE=1 brew install qemu
- restore_cache:
keys:
- llvm-source-8-macos-v3
- llvm-source-8-macos-v5
- run:
name: "Fetch LLVM source"
command: make llvm-source
- save_cache:
key: llvm-source-8-macos-v3
key: llvm-source-8-macos-v5
paths:
- llvm
- llvm-project
- restore_cache:
keys:
- llvm-build-8-macos-v4
- llvm-build-8-macos-v6
- run:
name: "Build LLVM"
command: |
@@ -191,7 +190,7 @@ commands:
make llvm-build
fi
- save_cache:
key: llvm-build-8-macos-v4
key: llvm-build-8-macos-v6
paths:
llvm-build
- run:
@@ -215,23 +214,28 @@ commands:
tar -C /usr/local/opt -xf /tmp/tinygo.darwin-amd64.tar.gz
ln -s /usr/local/opt/tinygo/bin/tinygo /usr/local/bin/tinygo
tinygo version
- smoketest-no-avr
- run:
name: "Download SiFive GNU toolchain"
command: |
curl -O https://static.dev.sifive.com/dev-tools/riscv64-unknown-elf-gcc-8.2.0-2019.05.3-x86_64-apple-darwin.tar.gz
sudo tar -C /usr/local --strip-components=1 -xf riscv64-unknown-elf-gcc-8.2.0-2019.05.3-x86_64-apple-darwin.tar.gz
- run: make smoketest AVR=0
jobs:
test-llvm8-go111:
docker:
- image: circleci/golang:1.11
- image: circleci/golang:1.11-stretch
steps:
- test-linux
test-llvm8-go112:
docker:
- image: circleci/golang:1.12
- image: circleci/golang:1.12-stretch
steps:
- test-linux
build-linux:
docker:
- image: circleci/golang:1.12
- image: circleci/golang:1.12-stretch
steps:
- build-linux
build-macos:
+4 -2
View File
@@ -5,10 +5,12 @@ src/device/avr/*.ld
src/device/avr/*.s
src/device/nrf/*.go
src/device/nrf/*.s
src/device/stm32/*.go
src/device/stm32/*.s
src/device/sam/*.go
src/device/sam/*.s
src/device/sifive/*.go
src/device/sifive/*.s
src/device/stm32/*.go
src/device/stm32/*.s
vendor
llvm
llvm-build
+1 -1
View File
@@ -9,7 +9,7 @@
url = https://github.com/avr-rust/avr-mcu.git
[submodule "lib/cmsis-svd"]
path = lib/cmsis-svd
url = https://github.com/posborne/cmsis-svd
url = https://github.com/tinygo-org/cmsis-svd
[submodule "lib/compiler-rt"]
path = lib/compiler-rt
url = https://github.com/llvm-mirror/compiler-rt.git
+2 -2
View File
@@ -27,8 +27,8 @@ on a different system like Mac.
## Download the source
The first step is to download the TinyGo sources. Then, inside the directory,
perform these steps:
The first step is to download the TinyGo sources (use `--recursive` if you clone
the git repository). Then, inside the directory, perform these steps:
dep ensure -vendor-only # download Go dependencies
make llvm-source # download LLVM
+47
View File
@@ -1,3 +1,50 @@
0.7.1
---
* **targets**
- `atsamd21`: add support for the `-port` flag in the flash subcommand
0.7.0
---
* **command line**
- try more locations to find Clang built-in headers
- add support for `tinygo test`
- build current directory if no package is specified
- support custom .json target spec with `-target` flag
- use zversion.go to detect version of GOROOT version
- make initial heap size configurable for some targets (currently WebAssembly
only)
* **cgo**
- add support for bitfields using generated getters and setters
- add support for anonymous structs
* **compiler**
- show an error instead of panicking on duplicate function definitions
- allow packages like github.com/tinygo-org/tinygo/src/\* by aliasing it
- remove `//go:volatile` support
It has been replaced with the runtime/volatile package.
- allow poiners in map keys
- support non-constant syscall numbers
- implement non-blocking selects
- add support for the `-tags` flag
- add support for `string` to `[]rune` conversion
- implement a portable conservative garbage collector (with support for wasm)
- add the `//go:noinline` pragma
* **standard library**
- `os`: add `os.Exit` and `syscall.Exit`
- `os`: add several stubs
- `runtime`: fix heap corruption in conservative GC
- `runtime`: add support for math intrinsics where supported, massively
speeding up some benchmarks
- `testing`: add basic support for testing
* **targets**
- add support for a generic target that calls `__tinygo_*` functions for
peripheral access
- `arduino-nano33`: add support for this board
- `hifive1`: add support for this RISC-V board
- `reelboard`: add e-paper pins
- `reelboard`: add `PowerSupplyActive` to enable voltage for on-board devices
- `wasm`: put the stack at the start of linear memory, to detect stack
overflows
0.6.0
---
* **command line**
+4
View File
@@ -16,12 +16,16 @@ Please open a Github issue with your problem, and we will be happy to assist.
We probably have not implemented it yet. Please take a look at our [Roadmap](https://github.com/tinygo-org/tinygo/wiki/Roadmap). Your pull request adding the functionality to TinyGo would be greatly appreciated.
Please open a Github issue. We want to help, and also make sure that there is no duplications of efforts. Sometimes what you need is already being worked on by someone else.
A long tail of small (and large) language features haven't been implemented yet. In almost all cases, the compiler will show a `todo:` error from `compiler/compiler.go` when you try to use it. You can try implementing it, or open a bug report with a small code sample that fails to compile.
### Some specific hardware you want to use does not appear to be in TinyGo
As above, we probably have not implemented it yet. Your contribution adding the hardware support to TinyGo would be greatly appreciated.
Please start by opening a Github issue. We want to help you to help us to help you.
Lots of targets/boards are still unsupported. Adding an architecture often requires a few compiler changes, but if the architecture is supported you can try implementing support for a new chip or board in `src/runtime`. For details, see [this wiki entry on adding archs/chips/boards](https://github.com/tinygo-org/tinygo/wiki/Adding-a-new-board).
Microcontrollers have lots of peripherals (I2C, SPI, ADC, etc.) and many don't have an implementation yet in the `machine` package. Adding support for new peripherals is very useful.
+38 -27
View File
@@ -3,12 +3,12 @@
all: tinygo
tinygo: build/tinygo
.PHONY: all tinygo build/tinygo test llvm-build llvm-source clean fmt gen-device gen-device-nrf gen-device-avr
# Default build and source directories, as created by `make llvm-build`.
LLVM_BUILDDIR ?= llvm-build
CLANG_SRC ?= llvm/tools/clang
LLD_SRC ?= llvm/tools/lld
CLANG_SRC ?= llvm-project/clang
LLD_SRC ?= llvm-project/lld
.PHONY: all tinygo build/tinygo test $(LLVM_BUILDDIR) llvm-source clean fmt gen-device gen-device-nrf gen-device-avr
LLVM_COMPONENTS = all-targets analysis asmparser asmprinter bitreader bitwriter codegen core coroutines debuginfodwarf executionengine instrumentation interpreter ipo irreader linker lto mc mcjit objcarcopts option profiledata scalaropts support target
@@ -18,15 +18,17 @@ ifeq ($(UNAME_S),Linux)
END_GROUP = -Wl,--end-group
endif
CLANG_LIBS = $(START_GROUP) $(abspath $(LLVM_BUILDDIR))/lib/libclang.a -lclangAnalysis -lclangARCMigrate -lclangAST -lclangASTMatchers -lclangBasic -lclangCodeGen -lclangCrossTU -lclangDriver -lclangDynamicASTMatchers -lclangEdit -lclangFormat -lclangFrontend -lclangFrontendTool -lclangHandleCXX -lclangHandleLLVM -lclangIndex -lclangLex -lclangParse -lclangRewrite -lclangRewriteFrontend -lclangSema -lclangSerialization -lclangStaticAnalyzerCheckers -lclangStaticAnalyzerCore -lclangStaticAnalyzerFrontend -lclangTooling -lclangToolingASTDiff -lclangToolingCore -lclangToolingInclusions -lclangToolingRefactor $(END_GROUP) -lstdc++
CLANG_LIBS = $(START_GROUP) $(abspath $(LLVM_BUILDDIR))/lib/libclang.a -lclangAnalysis -lclangARCMigrate -lclangAST -lclangASTMatchers -lclangBasic -lclangCodeGen -lclangCrossTU -lclangDriver -lclangDynamicASTMatchers -lclangEdit -lclangFormat -lclangFrontend -lclangFrontendTool -lclangHandleCXX -lclangHandleLLVM -lclangIndex -lclangLex -lclangParse -lclangRewrite -lclangRewriteFrontend -lclangSema -lclangSerialization -lclangStaticAnalyzerCheckers -lclangStaticAnalyzerCore -lclangStaticAnalyzerFrontend -lclangTooling -lclangToolingASTDiff -lclangToolingCore -lclangToolingInclusions $(END_GROUP) -lstdc++
LLD_LIBS = $(START_GROUP) -llldCOFF -llldCommon -llldCore -llldDriver -llldELF -llldMachO -llldMinGW -llldReaderWriter -llldWasm -llldYAML $(END_GROUP)
# For static linking.
CGO_CPPFLAGS=$(shell $(LLVM_BUILDDIR)/bin/llvm-config --cppflags) -I$(abspath $(CLANG_SRC))/include -I$(abspath $(LLD_SRC))/include
CGO_CXXFLAGS=-std=c++11
CGO_LDFLAGS=-L$(LLVM_BUILDDIR)/lib $(CLANG_LIBS) $(LLD_LIBS) $(shell $(LLVM_BUILDDIR)/bin/llvm-config --ldflags --libs --system-libs $(LLVM_COMPONENTS))
ifneq ("$(wildcard $(LLVM_BUILDDIR)/bin/llvm-config)","")
CGO_CPPFLAGS=$(shell $(LLVM_BUILDDIR)/bin/llvm-config --cppflags) -I$(abspath $(CLANG_SRC))/include -I$(abspath $(LLD_SRC))/include
CGO_CXXFLAGS=-std=c++11
CGO_LDFLAGS=-L$(LLVM_BUILDDIR)/lib $(CLANG_LIBS) $(LLD_LIBS) $(shell $(LLVM_BUILDDIR)/bin/llvm-config --ldflags --libs --system-libs $(LLVM_COMPONENTS))
endif
clean:
@@ -38,7 +40,8 @@ fmt:
fmt-check:
@unformatted=$$(gofmt -l $(FMT_PATHS)); [ -z "$$unformatted" ] && exit 0; echo "Unformatted:"; for fn in $$unformatted; do echo " $$fn"; done; exit 1
gen-device: gen-device-avr gen-device-nrf gen-device-sam gen-device-stm32
gen-device: gen-device-avr gen-device-nrf gen-device-sam gen-device-sifive gen-device-stm32
gen-device-avr:
./tools/gen-device-avr.py lib/avr/packs/atmega src/device/avr/
@@ -53,42 +56,43 @@ gen-device-sam:
./tools/gen-device-svd.py lib/cmsis-svd/data/Atmel/ src/device/sam/ --source=https://github.com/posborne/cmsis-svd/tree/master/data/Atmel
go fmt ./src/device/sam
gen-device-sifive:
./tools/gen-device-svd.py lib/cmsis-svd/data/SiFive-Community/ src/device/sifive/ --source=https://github.com/AdaCore/svd2ada/tree/master/CMSIS-SVD/SiFive-Community
go fmt ./src/device/sifive
gen-device-stm32:
./tools/gen-device-svd.py lib/cmsis-svd/data/STMicro/ src/device/stm32/ --source=https://github.com/posborne/cmsis-svd/tree/master/data/STMicro
go fmt ./src/device/stm32
# Get LLVM sources.
llvm/README.txt:
git clone -b release_80 https://github.com/llvm-mirror/llvm.git llvm
llvm/tools/clang/README.txt:
git clone -b release_80 https://github.com/llvm-mirror/clang.git llvm/tools/clang
llvm/tools/lld/README.md:
git clone -b release_80 https://github.com/llvm-mirror/lld.git llvm/tools/lld
llvm-source: llvm/README.txt llvm/tools/clang/README.txt llvm/tools/lld/README.md
llvm-project/README.md:
git clone -b release/8.x https://github.com/llvm/llvm-project
llvm-source: llvm-project/README.md
# Configure LLVM.
llvm-build/build.ninja: llvm-source
mkdir -p llvm-build; cd llvm-build; cmake -G Ninja ../llvm "-DLLVM_TARGETS_TO_BUILD=X86;ARM;AArch64;WebAssembly" "-DLLVM_EXPERIMENTAL_TARGETS_TO_BUILD=AVR" -DCMAKE_BUILD_TYPE=Release -DLLVM_ENABLE_ASSERTIONS=OFF -DLIBCLANG_BUILD_STATIC=ON -DLLVM_ENABLE_TERMINFO=OFF -DLLVM_ENABLE_ZLIB=OFF
TINYGO_SOURCE_DIR=$(shell pwd)
$(LLVM_BUILDDIR)/build.ninja: llvm-source
mkdir -p $(LLVM_BUILDDIR); cd $(LLVM_BUILDDIR); cmake -G Ninja $(TINYGO_SOURCE_DIR)/llvm-project/llvm "-DLLVM_TARGETS_TO_BUILD=X86;ARM;AArch64;WebAssembly" "-DLLVM_EXPERIMENTAL_TARGETS_TO_BUILD=AVR;RISCV" -DCMAKE_BUILD_TYPE=Release -DLLVM_ENABLE_ASSERTIONS=OFF -DLIBCLANG_BUILD_STATIC=ON -DLLVM_ENABLE_TERMINFO=OFF -DLLVM_ENABLE_ZLIB=OFF -DLLVM_ENABLE_PROJECTS="clang;lld" -DLLVM_TOOL_CLANG_TOOLS_EXTRA_BUILD=OFF
# Build LLVM.
llvm-build: llvm-build/build.ninja
cd llvm-build; ninja
$(LLVM_BUILDDIR): $(LLVM_BUILDDIR)/build.ninja
cd $(LLVM_BUILDDIR); ninja
# Build the Go compiler.
build/tinygo:
@if [ ! -f llvm-build/bin/llvm-config ]; then echo "Fetch and build LLVM first by running:\n make llvm-source\n make llvm-build"; exit 1; fi
@if [ ! -f "$(LLVM_BUILDDIR)/bin/llvm-config" ]; then echo "Fetch and build LLVM first by running:"; echo " make llvm-source"; echo " make $(LLVM_BUILDDIR)"; exit 1; fi
CGO_CPPFLAGS="$(CGO_CPPFLAGS)" CGO_CXXFLAGS="$(CGO_CXXFLAGS)" CGO_LDFLAGS="$(CGO_LDFLAGS)" go build -o build/tinygo -tags byollvm .
test:
CGO_CPPFLAGS="$(CGO_CPPFLAGS)" CGO_CXXFLAGS="$(CGO_CXXFLAGS)" CGO_LDFLAGS="$(CGO_LDFLAGS)" go test -v -tags byollvm .
.PHONY: smoketest smoketest-no-avr
smoketest: smoketest-no-avr
tinygo build -size short -o test.elf -target=arduino examples/blinky1
tinygo build -size short -o test.elf -target=digispark examples/blinky1
smoketest-no-avr:
tinygo-test:
cd tests/tinygotest && tinygo test
.PHONY: smoketest
smoketest:
# test all examples
tinygo build -size short -o test.elf -target=pca10040 examples/blinky1
tinygo build -size short -o test.elf -target=pca10040 examples/adc
@@ -104,7 +108,7 @@ smoketest-no-avr:
tinygo build -size short -o test.elf -target=pca10040 examples/serial
tinygo build -size short -o test.elf -target=pca10040 examples/test
# test all targets/boards
tinygo build -o test.elf examples/blinky2 # TODO: re-enable -size flag with MachO support
tinygo build -o test.wasm -tags=pca10040 examples/blinky2
tinygo build -size short -o test.elf -target=microbit examples/echo
tinygo build -size short -o test.elf -target=nrf52840-mdk examples/blinky1
tinygo build -size short -o test.elf -target=pca10031 examples/blinky1
@@ -120,6 +124,13 @@ smoketest-no-avr:
tinygo build -size short -o test.elf -target=stm32f4disco examples/blinky1
tinygo build -size short -o test.elf -target=stm32f4disco examples/blinky2
tinygo build -size short -o test.elf -target=circuitplay-express examples/i2s
ifneq ($(AVR), 0)
tinygo build -size short -o test.elf -target=arduino examples/blinky1
tinygo build -size short -o test.elf -target=digispark examples/blinky1
endif
ifneq ($(RISCV), 0)
tinygo build -size short -o test.elf -target=hifive1b examples/blinky1
endif
tinygo build -o wasm.wasm -target=wasm examples/wasm/export
tinygo build -o wasm.wasm -target=wasm examples/wasm/main
+2 -1
View File
@@ -43,12 +43,13 @@ See the [getting started instructions](https://tinygo.org/getting-started/) for
You can compile TinyGo programs for microcontrollers, WebAssembly and Linux.
The following 14 microcontroller boards are currently supported:
The following 15 microcontroller boards are currently supported:
* [Adafruit Circuit Playground Express](https://www.adafruit.com/product/3333)
* [Adafruit Feather M0](https://www.adafruit.com/product/2772)
* [Adafruit ItsyBitsy M0](https://www.adafruit.com/product/3727)
* [Adafruit Trinket M0](https://www.adafruit.com/product/3500)
* [Arduino Nano33 IoT](https://store.arduino.cc/nano-33-iot)
* [Arduino Uno](https://store.arduino.cc/arduino-uno-rev3)
* [BBC micro:bit](https://microbit.org/)
* [ST Micro STM32F103XX "Bluepill"](http://wiki.stm32duino.com/index.php?title=Blue_Pill)
+303 -1
View File
@@ -36,6 +36,7 @@ type cgoPackage struct {
typedefs map[string]*typedefInfo
elaboratedTypes map[string]*elaboratedTypeInfo
enums map[string]enumInfo
anonStructNum int
}
// constantInfo stores some information about a CGo constant found by libclang
@@ -68,8 +69,20 @@ type typedefInfo struct {
// elaboratedTypeInfo contains some information about an elaborated type
// (struct, union) found in the C AST.
type elaboratedTypeInfo struct {
typeExpr ast.Expr
typeExpr *ast.StructType
pos token.Pos
bitfields []bitfieldInfo
}
// bitfieldInfo contains information about a single bitfield in a struct. It
// keeps information about the start, end, and the special (renamed) base field
// of this bitfield.
type bitfieldInfo struct {
field *ast.Field
name string
pos token.Pos
startBit int64
endBit int64 // may be 0 meaning "until the end of the field"
}
// enumInfo contains information about an enum in the C.
@@ -581,10 +594,299 @@ func (p *cgoPackage) addElaboratedTypes() {
}
obj.Decl = typeSpec
gen.Specs = append(gen.Specs, typeSpec)
// If this struct has bitfields, create getters for them.
for _, bitfield := range typ.bitfields {
p.createBitfieldGetter(bitfield, typeName)
p.createBitfieldSetter(bitfield, typeName)
}
}
p.generated.Decls = append(p.generated.Decls, gen)
}
// createBitfieldGetter creates a bitfield getter function like the following:
//
// func (s *C.struct_foo) bitfield_b() byte {
// return (s.__bitfield_1 >> 5) & 0x1
// }
func (p *cgoPackage) createBitfieldGetter(bitfield bitfieldInfo, typeName string) {
// The value to return from the getter.
// Not complete: this is just an expression to get the complete field.
var result ast.Expr = &ast.SelectorExpr{
X: &ast.Ident{
NamePos: bitfield.pos,
Name: "s",
Obj: nil,
},
Sel: &ast.Ident{
NamePos: bitfield.pos,
Name: bitfield.field.Names[0].Name,
},
}
if bitfield.startBit != 0 {
// Shift to the right by .startBit so that fields that come before are
// shifted off.
result = &ast.BinaryExpr{
X: result,
OpPos: bitfield.pos,
Op: token.SHR,
Y: &ast.BasicLit{
ValuePos: bitfield.pos,
Kind: token.INT,
Value: strconv.FormatInt(bitfield.startBit, 10),
},
}
}
if bitfield.endBit != 0 {
// Mask off the high bits so that fields that come after this field are
// masked off.
and := (uint64(1) << uint64(bitfield.endBit-bitfield.startBit)) - 1
result = &ast.BinaryExpr{
X: result,
OpPos: bitfield.pos,
Op: token.AND,
Y: &ast.BasicLit{
ValuePos: bitfield.pos,
Kind: token.INT,
Value: "0x" + strconv.FormatUint(and, 16),
},
}
}
// Create the getter function.
getter := &ast.FuncDecl{
Recv: &ast.FieldList{
Opening: bitfield.pos,
List: []*ast.Field{
&ast.Field{
Names: []*ast.Ident{
&ast.Ident{
NamePos: bitfield.pos,
Name: "s",
Obj: &ast.Object{
Kind: ast.Var,
Name: "s",
Decl: nil,
},
},
},
Type: &ast.StarExpr{
Star: bitfield.pos,
X: &ast.Ident{
NamePos: bitfield.pos,
Name: typeName,
Obj: nil,
},
},
},
},
Closing: bitfield.pos,
},
Name: &ast.Ident{
NamePos: bitfield.pos,
Name: "bitfield_" + bitfield.name,
},
Type: &ast.FuncType{
Func: bitfield.pos,
Params: &ast.FieldList{
Opening: bitfield.pos,
Closing: bitfield.pos,
},
Results: &ast.FieldList{
List: []*ast.Field{
&ast.Field{
Type: bitfield.field.Type,
},
},
},
},
Body: &ast.BlockStmt{
Lbrace: bitfield.pos,
List: []ast.Stmt{
&ast.ReturnStmt{
Return: bitfield.pos,
Results: []ast.Expr{
result,
},
},
},
Rbrace: bitfield.pos,
},
}
p.generated.Decls = append(p.generated.Decls, getter)
}
// createBitfieldSetter creates a bitfield setter function like the following:
//
// func (s *C.struct_foo) set_bitfield_b(value byte) {
// s.__bitfield_1 = s.__bitfield_1 ^ 0x60 | ((value & 1) << 5)
// }
//
// Or the following:
//
// func (s *C.struct_foo) set_bitfield_c(value byte) {
// s.__bitfield_1 = s.__bitfield_1 & 0x3f | (value << 6)
// }
func (p *cgoPackage) createBitfieldSetter(bitfield bitfieldInfo, typeName string) {
// The full field with all bitfields.
var field ast.Expr = &ast.SelectorExpr{
X: &ast.Ident{
NamePos: bitfield.pos,
Name: "s",
Obj: nil,
},
Sel: &ast.Ident{
NamePos: bitfield.pos,
Name: bitfield.field.Names[0].Name,
},
}
// The value to insert into the field.
var valueToInsert ast.Expr = &ast.Ident{
NamePos: bitfield.pos,
Name: "value",
}
if bitfield.endBit != 0 {
// Make sure the value is in range with a mask.
valueToInsert = &ast.BinaryExpr{
X: valueToInsert,
OpPos: bitfield.pos,
Op: token.AND,
Y: &ast.BasicLit{
ValuePos: bitfield.pos,
Kind: token.INT,
Value: "0x" + strconv.FormatUint((uint64(1)<<uint64(bitfield.endBit-bitfield.startBit))-1, 16),
},
}
// Create a mask for the AND NOT operation.
mask := ((uint64(1) << uint64(bitfield.endBit-bitfield.startBit)) - 1) << uint64(bitfield.startBit)
// Zero the bits in the field that will soon be inserted.
field = &ast.BinaryExpr{
X: field,
OpPos: bitfield.pos,
Op: token.AND_NOT,
Y: &ast.BasicLit{
ValuePos: bitfield.pos,
Kind: token.INT,
Value: "0x" + strconv.FormatUint(mask, 16),
},
}
} else { // bitfield.endBit == 0
// We don't know exactly how many high bits should be zeroed. So we do
// something different: keep the low bits with a mask and OR the new
// value with it.
mask := (uint64(1) << uint64(bitfield.startBit)) - 1
// Extract the lower bits.
field = &ast.BinaryExpr{
X: field,
OpPos: bitfield.pos,
Op: token.AND,
Y: &ast.BasicLit{
ValuePos: bitfield.pos,
Kind: token.INT,
Value: "0x" + strconv.FormatUint(mask, 16),
},
}
}
// Bitwise OR with the new value (after the new value has been shifted).
field = &ast.BinaryExpr{
X: field,
OpPos: bitfield.pos,
Op: token.OR,
Y: &ast.BinaryExpr{
X: valueToInsert,
OpPos: bitfield.pos,
Op: token.SHL,
Y: &ast.BasicLit{
ValuePos: bitfield.pos,
Kind: token.INT,
Value: strconv.FormatInt(bitfield.startBit, 10),
},
},
}
// Create the setter function.
setter := &ast.FuncDecl{
Recv: &ast.FieldList{
Opening: bitfield.pos,
List: []*ast.Field{
&ast.Field{
Names: []*ast.Ident{
&ast.Ident{
NamePos: bitfield.pos,
Name: "s",
Obj: &ast.Object{
Kind: ast.Var,
Name: "s",
Decl: nil,
},
},
},
Type: &ast.StarExpr{
Star: bitfield.pos,
X: &ast.Ident{
NamePos: bitfield.pos,
Name: typeName,
Obj: nil,
},
},
},
},
Closing: bitfield.pos,
},
Name: &ast.Ident{
NamePos: bitfield.pos,
Name: "set_bitfield_" + bitfield.name,
},
Type: &ast.FuncType{
Func: bitfield.pos,
Params: &ast.FieldList{
Opening: bitfield.pos,
List: []*ast.Field{
&ast.Field{
Names: []*ast.Ident{
&ast.Ident{
NamePos: bitfield.pos,
Name: "value",
Obj: nil,
},
},
Type: bitfield.field.Type,
},
},
Closing: bitfield.pos,
},
},
Body: &ast.BlockStmt{
Lbrace: bitfield.pos,
List: []ast.Stmt{
&ast.AssignStmt{
Lhs: []ast.Expr{
&ast.SelectorExpr{
X: &ast.Ident{
NamePos: bitfield.pos,
Name: "s",
Obj: nil,
},
Sel: &ast.Ident{
NamePos: bitfield.pos,
Name: bitfield.field.Names[0].Name,
},
},
},
TokPos: bitfield.pos,
Tok: token.ASSIGN,
Rhs: []ast.Expr{
field,
},
},
},
Rbrace: bitfield.pos,
},
}
p.generated.Decls = append(p.generated.Decls, setter)
}
// addEnumTypes adds C enums to the AST. For example, the following C code:
//
// enum option {
+158 -69
View File
@@ -51,6 +51,7 @@ CXSourceRange tinygo_clang_getCursorExtent(GoCXCursor c);
CXTranslationUnit tinygo_clang_Cursor_getTranslationUnit(GoCXCursor c);
long long tinygo_clang_getEnumConstantDeclValue(GoCXCursor c);
CXType tinygo_clang_getEnumDeclIntegerType(GoCXCursor c);
unsigned tinygo_clang_Cursor_isBitField(GoCXCursor c);
int tinygo_clang_globals_visitor(GoCXCursor c, GoCXCursor parent, CXClientData client_data);
int tinygo_clang_struct_visitor(GoCXCursor c, GoCXCursor parent, CXClientData client_data);
@@ -512,78 +513,48 @@ func (p *cgoPackage) makeASTType(typ C.CXType, pos token.Pos) ast.Expr {
case C.CXType_Record:
cursor := C.tinygo_clang_getTypeDeclaration(typ)
name := getString(C.tinygo_clang_getCursorSpelling(cursor))
var cgoName string
switch C.tinygo_clang_getCursorKind(cursor) {
case C.CXCursor_StructDecl:
cgoName = "struct_" + name
case C.CXCursor_UnionDecl:
cgoName = "union_" + name
default:
panic("unknown record declaration")
}
if _, ok := p.elaboratedTypes[cgoName]; !ok {
p.elaboratedTypes[cgoName] = nil // predeclare (to avoid endless recursion)
fieldList := &ast.FieldList{
Opening: pos,
Closing: pos,
if name == "" {
// Anonymous record, probably inside a typedef.
typeExpr, bitfieldList := p.makeASTRecordType(cursor, pos)
if bitfieldList != nil {
// This struct has bitfields, so we have to declare it as a
// named type (for bitfield getters/setters to work).
p.anonStructNum++
cgoName := "struct_" + strconv.Itoa(p.anonStructNum)
p.elaboratedTypes[cgoName] = &elaboratedTypeInfo{
typeExpr: typeExpr,
pos: pos,
bitfields: bitfieldList,
}
return &ast.Ident{
NamePos: pos,
Name: "C." + cgoName,
}
}
ref := storedRefs.Put(struct {
fieldList *ast.FieldList
pkg *cgoPackage
}{fieldList, p})
defer storedRefs.Remove(ref)
C.tinygo_clang_visitChildren(cursor, C.CXCursorVisitor(C.tinygo_clang_struct_visitor), C.CXClientData(ref))
return typeExpr
} else {
var cgoName string
switch C.tinygo_clang_getCursorKind(cursor) {
case C.CXCursor_StructDecl:
p.elaboratedTypes[cgoName] = &elaboratedTypeInfo{
typeExpr: &ast.StructType{
Struct: pos,
Fields: fieldList,
},
pos: pos,
}
cgoName = "struct_" + name
case C.CXCursor_UnionDecl:
if len(fieldList.List) > 1 {
// Insert a special field at the front (of zero width) as a
// marker that this is struct is actually a union. This is done
// by giving the field a name that cannot be expressed directly
// in Go.
// Other parts of the compiler look at the first element in a
// struct (of size > 2) to know whether this is a union.
// Note that we don't have to insert it for single-element
// unions as they're basically equivalent to a struct.
unionMarker := &ast.Field{
Type: &ast.StructType{
Struct: pos,
},
}
unionMarker.Names = []*ast.Ident{
&ast.Ident{
NamePos: pos,
Name: "C union",
Obj: &ast.Object{
Kind: ast.Var,
Name: "C union",
Decl: unionMarker,
},
},
}
fieldList.List = append([]*ast.Field{unionMarker}, fieldList.List...)
}
p.elaboratedTypes[cgoName] = &elaboratedTypeInfo{
typeExpr: &ast.StructType{
Struct: pos,
Fields: fieldList,
},
pos: pos,
}
cgoName = "union_" + name
default:
panic("unreachable")
panic("unknown record declaration")
}
if _, ok := p.elaboratedTypes[cgoName]; !ok {
p.elaboratedTypes[cgoName] = nil // predeclare (to avoid endless recursion)
typeExpr, bitfieldList := p.makeASTRecordType(cursor, pos)
p.elaboratedTypes[cgoName] = &elaboratedTypeInfo{
typeExpr: typeExpr,
pos: pos,
bitfields: bitfieldList,
}
}
return &ast.Ident{
NamePos: pos,
Name: "C." + cgoName,
}
}
return &ast.Ident{
NamePos: pos,
Name: "C." + cgoName,
}
case C.CXType_Enum:
cursor := C.tinygo_clang_getTypeDeclaration(typ)
@@ -629,25 +600,143 @@ func (p *cgoPackage) makeASTType(typ C.CXType, pos token.Pos) ast.Expr {
}
}
// makeASTRecordType parses a C record (struct or union) and translates it into
// a Go struct type. Unions are implemented by setting the first field to a
// zero-lengt "C union" field, which cannot be written in Go directly.
func (p *cgoPackage) makeASTRecordType(cursor C.GoCXCursor, pos token.Pos) (*ast.StructType, []bitfieldInfo) {
fieldList := &ast.FieldList{
Opening: pos,
Closing: pos,
}
var bitfieldList []bitfieldInfo
inBitfield := false
bitfieldNum := 0
ref := storedRefs.Put(struct {
fieldList *ast.FieldList
pkg *cgoPackage
inBitfield *bool
bitfieldNum *int
bitfieldList *[]bitfieldInfo
}{fieldList, p, &inBitfield, &bitfieldNum, &bitfieldList})
defer storedRefs.Remove(ref)
C.tinygo_clang_visitChildren(cursor, C.CXCursorVisitor(C.tinygo_clang_struct_visitor), C.CXClientData(ref))
switch C.tinygo_clang_getCursorKind(cursor) {
case C.CXCursor_StructDecl:
return &ast.StructType{
Struct: pos,
Fields: fieldList,
}, bitfieldList
case C.CXCursor_UnionDecl:
if bitfieldList != nil {
// This is valid C... but please don't do this.
p.errors = append(p.errors, scanner.Error{
Pos: p.fset.PositionFor(pos, true),
Msg: fmt.Sprintf("bitfield in a union is not supported"),
})
}
if len(fieldList.List) > 1 {
// Insert a special field at the front (of zero width) as a
// marker that this is struct is actually a union. This is done
// by giving the field a name that cannot be expressed directly
// in Go.
// Other parts of the compiler look at the first element in a
// struct (of size > 2) to know whether this is a union.
// Note that we don't have to insert it for single-element
// unions as they're basically equivalent to a struct.
unionMarker := &ast.Field{
Type: &ast.StructType{
Struct: pos,
},
}
unionMarker.Names = []*ast.Ident{
&ast.Ident{
NamePos: pos,
Name: "C union",
Obj: &ast.Object{
Kind: ast.Var,
Name: "C union",
Decl: unionMarker,
},
},
}
fieldList.List = append([]*ast.Field{unionMarker}, fieldList.List...)
}
return &ast.StructType{
Struct: pos,
Fields: fieldList,
}, bitfieldList
default:
panic("unknown record declaration")
}
}
//export tinygo_clang_struct_visitor
func tinygo_clang_struct_visitor(c, parent C.GoCXCursor, client_data C.CXClientData) C.int {
passed := storedRefs.Get(unsafe.Pointer(client_data)).(struct {
fieldList *ast.FieldList
pkg *cgoPackage
fieldList *ast.FieldList
pkg *cgoPackage
inBitfield *bool
bitfieldNum *int
bitfieldList *[]bitfieldInfo
})
fieldList := passed.fieldList
p := passed.pkg
inBitfield := passed.inBitfield
bitfieldNum := passed.bitfieldNum
bitfieldList := passed.bitfieldList
if C.tinygo_clang_getCursorKind(c) != C.CXCursor_FieldDecl {
panic("expected field inside cursor")
}
name := getString(C.tinygo_clang_getCursorSpelling(c))
if name == "" {
// Assume this is a bitfield of 0 bits.
// Warning: this is not necessarily true!
return C.CXChildVisit_Continue
}
typ := C.tinygo_clang_getCursorType(c)
pos := p.getCursorPosition(c)
field := &ast.Field{
Type: p.makeASTType(typ, p.getCursorPosition(c)),
}
offsetof := int64(C.clang_Type_getOffsetOf(C.tinygo_clang_getCursorType(parent), C.CString(name)))
alignOf := int64(C.clang_Type_getAlignOf(typ) * 8)
bitfieldOffset := offsetof % alignOf
if bitfieldOffset != 0 {
if C.tinygo_clang_Cursor_isBitField(c) != 1 {
panic("expected a bitfield")
}
if !*inBitfield {
*bitfieldNum++
}
bitfieldName := "__bitfield_" + strconv.Itoa(*bitfieldNum)
prevField := fieldList.List[len(fieldList.List)-1]
if !*inBitfield {
// The previous element also was a bitfield, but wasn't noticed
// then. Add it now.
*inBitfield = true
*bitfieldList = append(*bitfieldList, bitfieldInfo{
field: prevField,
name: prevField.Names[0].Name,
startBit: 0,
pos: prevField.Names[0].NamePos,
})
prevField.Names[0].Name = bitfieldName
prevField.Names[0].Obj.Name = bitfieldName
}
prevBitfield := &(*bitfieldList)[len(*bitfieldList)-1]
prevBitfield.endBit = bitfieldOffset
*bitfieldList = append(*bitfieldList, bitfieldInfo{
field: prevField,
name: name,
startBit: bitfieldOffset,
pos: pos,
})
return C.CXChildVisit_Continue
}
*inBitfield = false
field.Names = []*ast.Ident{
&ast.Ident{
NamePos: p.getCursorPosition(c),
NamePos: pos,
Name: name,
Obj: &ast.Object{
Kind: ast.Var,
+4
View File
@@ -64,3 +64,7 @@ long long tinygo_clang_getEnumConstantDeclValue(CXCursor c) {
CXType tinygo_clang_getEnumDeclIntegerType(CXCursor c) {
return clang_getEnumDeclIntegerType(c);
}
unsigned tinygo_clang_Cursor_isBitField(CXCursor c) {
return clang_Cursor_isBitField(c);
}
+1
View File
@@ -37,6 +37,7 @@ func execCommand(cmdNames []string, args ...string) error {
// this command was not found, try the next
continue
}
return err
}
return nil
}
+1 -1
View File
@@ -38,7 +38,7 @@ func (c *Compiler) createCall(fn llvm.Value, args []llvm.Value, name string) llv
}
// Expand an argument type to a list that can be used in a function call
// paramter list.
// parameter list.
func (c *Compiler) expandFormalParamType(t llvm.Type) []llvm.Type {
switch t.TypeKind() {
case llvm.StructTypeKind:
+169 -10
View File
@@ -4,6 +4,7 @@ package compiler
// or pseudo-operations that are lowered during goroutine lowering.
import (
"fmt"
"go/types"
"golang.org/x/tools/go/ssa"
@@ -12,11 +13,22 @@ import (
// emitMakeChan returns a new channel value for the given channel type.
func (c *Compiler) emitMakeChan(expr *ssa.MakeChan) (llvm.Value, error) {
chanType := c.mod.GetTypeByName("runtime.channel")
size := c.targetData.TypeAllocSize(chanType)
chanType := c.getLLVMType(expr.Type())
size := c.targetData.TypeAllocSize(chanType.ElementType())
sizeValue := llvm.ConstInt(c.uintptrType, size, false)
ptr := c.createRuntimeCall("alloc", []llvm.Value{sizeValue}, "chan.alloc")
ptr = c.builder.CreateBitCast(ptr, llvm.PointerType(chanType, 0), "chan")
ptr = c.builder.CreateBitCast(ptr, chanType, "chan")
// Set the elementSize field
elementSizePtr := c.builder.CreateGEP(ptr, []llvm.Value{
llvm.ConstInt(c.ctx.Int32Type(), 0, false),
llvm.ConstInt(c.ctx.Int32Type(), 0, false),
}, "")
elementSize := c.targetData.TypeAllocSize(c.getLLVMType(expr.Type().(*types.Chan).Elem()))
if elementSize > 0xffff {
return ptr, c.makeError(expr.Pos(), fmt.Sprintf("element size is %d bytes, which is bigger than the maximum of %d bytes", elementSize, 0xffff))
}
elementSizeValue := llvm.ConstInt(c.ctx.Int16Type(), elementSize, false)
c.builder.CreateStore(elementSizeValue, elementSizePtr)
return ptr, nil
}
@@ -33,8 +45,7 @@ func (c *Compiler) emitChanSend(frame *Frame, instr *ssa.Send) {
// Do the send.
coroutine := c.createRuntimeCall("getCoroutine", nil, "")
valueSize := llvm.ConstInt(c.uintptrType, c.targetData.TypeAllocSize(chanValue.Type()), false)
c.createRuntimeCall("chanSend", []llvm.Value{coroutine, ch, valueAllocaCast, valueSize}, "")
c.createRuntimeCall("chanSend", []llvm.Value{coroutine, ch, valueAllocaCast}, "")
// End the lifetime of the alloca.
// This also works around a bug in CoroSplit, at least in LLVM 8:
@@ -53,8 +64,7 @@ func (c *Compiler) emitChanRecv(frame *Frame, unop *ssa.UnOp) llvm.Value {
// Do the receive.
coroutine := c.createRuntimeCall("getCoroutine", nil, "")
valueSize := llvm.ConstInt(c.uintptrType, c.targetData.TypeAllocSize(valueType), false)
c.createRuntimeCall("chanRecv", []llvm.Value{coroutine, ch, valueAllocaCast, valueSize}, "")
c.createRuntimeCall("chanRecv", []llvm.Value{coroutine, ch, valueAllocaCast}, "")
received := c.builder.CreateLoad(valueAlloca, "chan.received")
c.emitLifetimeEnd(valueAllocaCast, valueAllocaSize)
@@ -72,8 +82,157 @@ func (c *Compiler) emitChanRecv(frame *Frame, unop *ssa.UnOp) llvm.Value {
// emitChanClose closes the given channel.
func (c *Compiler) emitChanClose(frame *Frame, param ssa.Value) {
valueType := c.getLLVMType(param.Type().(*types.Chan).Elem())
valueSize := llvm.ConstInt(c.uintptrType, c.targetData.TypeAllocSize(valueType), false)
ch := c.getValue(frame, param)
c.createRuntimeCall("chanClose", []llvm.Value{ch, valueSize}, "")
c.createRuntimeCall("chanClose", []llvm.Value{ch}, "")
}
// emitSelect emits all IR necessary for a select statements. That's a
// non-trivial amount of code because select is very complex to implement.
func (c *Compiler) emitSelect(frame *Frame, expr *ssa.Select) llvm.Value {
if len(expr.States) == 0 {
// Shortcuts for some simple selects.
llvmType := c.getLLVMType(expr.Type())
if expr.Blocking {
// Blocks forever:
// select {}
c.createRuntimeCall("deadlockStub", nil, "")
return llvm.Undef(llvmType)
} else {
// No-op:
// select {
// default:
// }
retval := llvm.Undef(llvmType)
retval = c.builder.CreateInsertValue(retval, llvm.ConstInt(c.intType, 0xffffffffffffffff, true), 0, "")
return retval // {-1, false}
}
}
// This code create a (stack-allocated) slice containing all the select
// cases and then calls runtime.chanSelect to perform the actual select
// statement.
// Simple selects (blocking and with just one case) are already transformed
// into regular chan operations during SSA construction so we don't have to
// optimize such small selects.
// Go through all the cases. Create the selectStates slice and and
// determine the receive buffer size and alignment.
recvbufSize := uint64(0)
recvbufAlign := 0
hasReceives := false
var selectStates []llvm.Value
chanSelectStateType := c.getLLVMRuntimeType("chanSelectState")
for _, state := range expr.States {
ch := c.getValue(frame, state.Chan)
selectState := c.getZeroValue(chanSelectStateType)
selectState = c.builder.CreateInsertValue(selectState, ch, 0, "")
switch state.Dir {
case types.RecvOnly:
// Make sure the receive buffer is big enough and has the correct alignment.
llvmType := c.getLLVMType(state.Chan.Type().(*types.Chan).Elem())
if size := c.targetData.TypeAllocSize(llvmType); size > recvbufSize {
recvbufSize = size
}
if align := c.targetData.ABITypeAlignment(llvmType); align > recvbufAlign {
recvbufAlign = align
}
hasReceives = true
case types.SendOnly:
// Store this value in an alloca and put a pointer to this alloca
// in the send state.
sendValue := c.getValue(frame, state.Send)
alloca := c.createEntryBlockAlloca(sendValue.Type(), "select.send.value")
c.builder.CreateStore(sendValue, alloca)
ptr := c.builder.CreateBitCast(alloca, c.i8ptrType, "")
selectState = c.builder.CreateInsertValue(selectState, ptr, 1, "")
default:
panic("unreachable")
}
selectStates = append(selectStates, selectState)
}
// Create a receive buffer, where the received value will be stored.
recvbuf := llvm.Undef(c.i8ptrType)
if hasReceives {
allocaType := llvm.ArrayType(c.ctx.Int8Type(), int(recvbufSize))
recvbufAlloca := c.builder.CreateAlloca(allocaType, "select.recvbuf.alloca")
recvbufAlloca.SetAlignment(recvbufAlign)
recvbuf = c.builder.CreateGEP(recvbufAlloca, []llvm.Value{
llvm.ConstInt(c.ctx.Int32Type(), 0, false),
llvm.ConstInt(c.ctx.Int32Type(), 0, false),
}, "select.recvbuf")
}
// Create the states slice (allocated on the stack).
statesAllocaType := llvm.ArrayType(chanSelectStateType, len(selectStates))
statesAlloca := c.builder.CreateAlloca(statesAllocaType, "select.states.alloca")
for i, state := range selectStates {
// Set each slice element to the appropriate channel.
gep := c.builder.CreateGEP(statesAlloca, []llvm.Value{
llvm.ConstInt(c.ctx.Int32Type(), 0, false),
llvm.ConstInt(c.ctx.Int32Type(), uint64(i), false),
}, "")
c.builder.CreateStore(state, gep)
}
statesPtr := c.builder.CreateGEP(statesAlloca, []llvm.Value{
llvm.ConstInt(c.ctx.Int32Type(), 0, false),
llvm.ConstInt(c.ctx.Int32Type(), 0, false),
}, "select.states")
statesLen := llvm.ConstInt(c.uintptrType, uint64(len(selectStates)), false)
// Convert the 'blocking' flag on this select into a LLVM value.
blockingInt := uint64(0)
if expr.Blocking {
blockingInt = 1
}
blockingValue := llvm.ConstInt(c.ctx.Int1Type(), blockingInt, false)
// Do the select in the runtime.
results := c.createRuntimeCall("chanSelect", []llvm.Value{
recvbuf,
statesPtr, statesLen, statesLen, // []chanSelectState
blockingValue,
}, "")
// The result value does not include all the possible received values,
// because we can't load them in advance. Instead, the *ssa.Extract
// instruction will treat a *ssa.Select specially and load it there inline.
// Store the receive alloca in a sidetable until we hit this extract
// instruction.
if frame.selectRecvBuf == nil {
frame.selectRecvBuf = make(map[*ssa.Select]llvm.Value)
}
frame.selectRecvBuf[expr] = recvbuf
return results
}
// getChanSelectResult returns the special values from a *ssa.Extract expression
// when extracting a value from a select statement (*ssa.Select). Because
// *ssa.Select cannot load all values in advance, it does this later in the
// *ssa.Extract expression.
func (c *Compiler) getChanSelectResult(frame *Frame, expr *ssa.Extract) llvm.Value {
if expr.Index == 0 {
// index
value := c.getValue(frame, expr.Tuple)
index := c.builder.CreateExtractValue(value, expr.Index, "")
if index.Type().IntTypeWidth() < c.intType.IntTypeWidth() {
index = c.builder.CreateSExt(index, c.intType, "")
}
return index
} else if expr.Index == 1 {
// comma-ok
value := c.getValue(frame, expr.Tuple)
return c.builder.CreateExtractValue(value, expr.Index, "")
} else {
// Select statements are (index, ok, ...) where ... is a number of
// received values, depending on how many receive statements there
// are. They are all combined into one alloca (because only one
// receive can proceed at a time) so we'll get that alloca, bitcast
// it to the correct type, and dereference it.
recvbuf := frame.selectRecvBuf[expr.Tuple.(*ssa.Select)]
typ := llvm.PointerType(c.getLLVMType(expr.Type()), 0)
ptr := c.builder.CreateBitCast(recvbuf, typ, "")
return c.builder.CreateLoad(ptr, "")
}
}
+110 -108
View File
@@ -3,6 +3,7 @@ package compiler
import (
"errors"
"fmt"
"go/ast"
"go/build"
"go/constant"
"go/token"
@@ -26,6 +27,9 @@ func init() {
llvm.InitializeAllAsmPrinters()
}
// The TinyGo import path.
const tinygoPath = "github.com/tinygo-org/tinygo"
// Configure the compiler.
type Config struct {
Triple string // LLVM target triple, e.g. x86_64-unknown-linux-gnu (empty string means default)
@@ -37,12 +41,19 @@ type Config struct {
PanicStrategy string // panic strategy ("abort" or "trap")
CFlags []string // cflags to pass to cgo
LDFlags []string // ldflags to pass to cgo
ClangHeaders string // Clang built-in header include path
DumpSSA bool // dump Go SSA, for compiler debugging
Debug bool // add debug symbols for gdb
GOROOT string // GOROOT
TINYGOROOT string // GOROOT for TinyGo
GOPATH string // GOPATH, like `go env GOPATH`
BuildTags []string // build tags for TinyGo (empty means {Config.GOOS/Config.GOARCH})
TestConfig TestConfig
}
type TestConfig struct {
CompileTestBinary bool
// TODO: Filter the test functions to run, include verbose flag, etc
}
type Compiler struct {
@@ -63,6 +74,7 @@ type Compiler struct {
interfaceInvokeWrappers []interfaceInvokeWrapper
ir *ir.Program
diagnostics []error
astComments map[string]*ast.CommentGroup
}
type Frame struct {
@@ -79,6 +91,7 @@ type Frame struct {
deferFuncs map[*ir.Function]int
deferInvokeFuncs map[string]int
deferClosureFuncs map[*ir.Function]int
selectRecvBuf map[*ssa.Select]llvm.Value
}
type Phi struct {
@@ -154,11 +167,10 @@ func (c *Compiler) TargetData() llvm.TargetData {
// selectGC picks an appropriate GC strategy if none was provided.
func (c *Compiler) selectGC() string {
gc := c.GC
if gc == "" {
gc = "dumb"
if c.GC != "" {
return c.GC
}
return gc
return "conservative"
}
// Compile the given package path or .go file path. Return an error when this
@@ -198,22 +210,29 @@ func (c *Compiler) Compile(mainPath string) []error {
Compiler: "gc", // must be one of the recognized compilers
BuildTags: append([]string{"tinygo", "gc." + c.selectGC()}, c.BuildTags...),
},
ShouldOverlay: func(path string) bool {
OverlayPath: func(path string) string {
// Return the (overlay) import path when it should be overlaid, and
// "" if it should not.
if strings.HasPrefix(path, tinygoPath+"/src/") {
// Avoid issues with packages that are imported twice, one from
// GOPATH and one from TINYGOPATH.
path = path[len(tinygoPath+"/src/"):]
}
switch path {
case "machine", "os", "reflect", "runtime", "runtime/volatile", "sync":
return true
case "machine", "os", "reflect", "runtime", "runtime/volatile", "sync", "testing":
return path
default:
if strings.HasPrefix(path, "device/") || strings.HasPrefix(path, "examples/") {
return true
return path
} else if path == "syscall" {
for _, tag := range c.BuildTags {
if tag == "avr" || tag == "cortexm" || tag == "darwin" {
return true
if tag == "avr" || tag == "cortexm" || tag == "darwin" || tag == "riscv" {
return path
}
}
}
}
return false
return ""
},
TypeChecker: types.Config{
Sizes: &StdSizes{
@@ -222,10 +241,12 @@ func (c *Compiler) Compile(mainPath string) []error {
MaxAlign: int64(c.targetData.PrefTypeAlignment(c.i8ptrType)),
},
},
Dir: wd,
TINYGOROOT: c.TINYGOROOT,
CFlags: c.CFlags,
Dir: wd,
TINYGOROOT: c.TINYGOROOT,
CFlags: c.CFlags,
ClangHeaders: c.ClangHeaders,
}
if strings.HasSuffix(mainPath, ".go") {
_, err = lprogram.ImportFile(mainPath)
if err != nil {
@@ -237,12 +258,13 @@ func (c *Compiler) Compile(mainPath string) []error {
return []error{err}
}
}
_, err = lprogram.Import("runtime", "")
if err != nil {
return []error{err}
}
err = lprogram.Parse()
err = lprogram.Parse(c.TestConfig.CompileTestBinary)
if err != nil {
return []error{err}
}
@@ -265,39 +287,7 @@ func (c *Compiler) Compile(mainPath string) []error {
var frames []*Frame
// Declare all named struct types.
for _, t := range c.ir.NamedTypes {
if named, ok := t.Type.Type().(*types.Named); ok {
if _, ok := named.Underlying().(*types.Struct); ok {
t.LLVMType = c.ctx.StructCreateNamed(named.Obj().Pkg().Path() + "." + named.Obj().Name())
}
}
}
// Define all named struct types.
for _, t := range c.ir.NamedTypes {
if named, ok := t.Type.Type().(*types.Named); ok {
if st, ok := named.Underlying().(*types.Struct); ok {
llvmType := c.getLLVMType(st)
t.LLVMType.StructSetBody(llvmType.StructElementTypes(), false)
}
}
}
// Declare all globals.
for _, g := range c.ir.Globals {
typ := g.Type().(*types.Pointer).Elem()
llvmType := c.getLLVMType(typ)
global := c.mod.NamedGlobal(g.LinkName())
if global.IsNil() {
global = llvm.AddGlobal(c.mod, llvmType, g.LinkName())
}
g.LLVMGlobal = global
if !g.IsExtern() {
global.SetLinkage(llvm.InternalLinkage)
global.SetInitializer(c.getZeroValue(llvmType))
}
}
c.loadASTComments(lprogram)
// Declare all functions.
for _, f := range c.ir.Functions {
@@ -371,6 +361,15 @@ func (c *Compiler) Compile(mainPath string) []error {
// See emitNilCheck in asserts.go.
c.mod.NamedFunction("runtime.isnil").AddAttributeAtIndex(1, nocapture)
// This function is necessary for tracking pointers on the stack in a
// portable way (see gc.go). Indicate to the optimizer that the only thing
// we'll do is read the pointer.
trackPointer := c.mod.NamedFunction("runtime.trackPointer")
if !trackPointer.IsNil() {
trackPointer.AddAttributeAtIndex(1, nocapture)
trackPointer.AddAttributeAtIndex(1, readonly)
}
// Memory copy operations do not capture pointers, even though some weird
// pointer arithmetic is happening in the Go implementation.
for _, fnName := range []string{"runtime.memcpy", "runtime.memmove"} {
@@ -403,6 +402,22 @@ func (c *Compiler) Compile(mainPath string) []error {
return c.diagnostics
}
// getRuntimeType obtains a named type from the runtime package and returns it
// as a Go type.
func (c *Compiler) getRuntimeType(name string) types.Type {
return c.ir.Program.ImportedPackage("runtime").Type(name).Type()
}
// getLLVMRuntimeType obtains a named type from the runtime package and returns
// it as a LLVM type, creating it if necessary. It is a shorthand for
// getLLVMType(getRuntimeType(name)).
func (c *Compiler) getLLVMRuntimeType(name string) llvm.Type {
return c.getLLVMType(c.getRuntimeType(name))
}
// 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.
func (c *Compiler) getLLVMType(goType types.Type) llvm.Type {
switch typ := goType.(type) {
case *types.Array:
@@ -431,7 +446,7 @@ func (c *Compiler) getLLVMType(goType types.Type) llvm.Type {
case types.Complex128:
return c.ctx.StructType([]llvm.Type{c.ctx.DoubleType(), c.ctx.DoubleType()}, false)
case types.String, types.UntypedString:
return c.mod.GetTypeByName("runtime._string")
return c.getLLVMRuntimeType("_string")
case types.Uintptr:
return c.uintptrType
case types.UnsafePointer:
@@ -440,16 +455,23 @@ func (c *Compiler) getLLVMType(goType types.Type) llvm.Type {
panic("unknown basic type: " + typ.String())
}
case *types.Chan:
return llvm.PointerType(c.mod.GetTypeByName("runtime.channel"), 0)
return llvm.PointerType(c.getLLVMRuntimeType("channel"), 0)
case *types.Interface:
return c.mod.GetTypeByName("runtime._interface")
return c.getLLVMRuntimeType("_interface")
case *types.Map:
return llvm.PointerType(c.mod.GetTypeByName("runtime.hashmap"), 0)
return llvm.PointerType(c.getLLVMRuntimeType("hashmap"), 0)
case *types.Named:
if _, ok := typ.Underlying().(*types.Struct); ok {
llvmType := c.mod.GetTypeByName(typ.Obj().Pkg().Path() + "." + typ.Obj().Name())
if st, ok := typ.Underlying().(*types.Struct); ok {
// Structs are a special case. While other named types are ignored
// in LLVM IR, named structs are implemented as named structs in
// 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() {
panic("underlying type not found: " + typ.Obj().Pkg().Path() + "." + typ.Obj().Name())
llvmType = c.ctx.StructCreateNamed(llvmName)
underlying := c.getLLVMType(st)
llvmType.StructSetBody(underlying.StructElementTypes(), false)
}
return llvmType
}
@@ -826,7 +848,8 @@ func (c *Compiler) parseFunc(frame *Frame) {
fmt.Printf("\nfunc %s:\n", frame.fn.Function)
}
if !frame.fn.LLVMFn.IsDeclaration() {
panic("function is already defined: " + frame.fn.LLVMFn.Name())
c.addError(frame.fn.Pos(), "function is already defined:"+frame.fn.LLVMFn.Name())
return
}
if !frame.fn.IsExported() {
frame.fn.LLVMFn.SetLinkage(llvm.InternalLinkage)
@@ -842,6 +865,10 @@ func (c *Compiler) parseFunc(frame *Frame) {
// Add LLVM inline hint to functions with //go:inline pragma.
inline := c.ctx.CreateEnumAttribute(llvm.AttributeKindID("inlinehint"), 0)
frame.fn.LLVMFn.AddFunctionAttr(inline)
case ir.InlineNone:
// Add LLVM attribute to always avoid inlining this function.
noinline := c.ctx.CreateEnumAttribute(llvm.AttributeKindID("noinline"), 0)
frame.fn.LLVMFn.AddFunctionAttr(noinline)
}
// Add debug info, if needed.
@@ -994,6 +1021,9 @@ func (c *Compiler) parseInstr(frame *Frame, instr ssa.Instruction) {
frame.locals[instr] = llvm.Undef(c.getLLVMType(instr.Type()))
} else {
frame.locals[instr] = value
if len(*instr.Referrers()) != 0 && c.needsStackObjects() {
c.trackExpr(frame, instr, value)
}
}
case *ssa.DebugRef:
// ignore
@@ -1075,12 +1105,7 @@ func (c *Compiler) parseInstr(frame *Frame, instr ssa.Instruction) {
// nothing to store
return
}
store := c.builder.CreateStore(llvmVal, llvmAddr)
valType := instr.Addr.Type().Underlying().(*types.Pointer).Elem()
if c.ir.IsVolatile(valType) {
// Volatile store, for memory-mapped registers.
store.SetVolatile(true)
}
c.builder.CreateStore(llvmVal, llvmAddr)
default:
c.addError(instr.Pos(), "unknown instruction: "+instr.String())
}
@@ -1283,11 +1308,11 @@ func (c *Compiler) parseCall(frame *Frame, instr *ssa.CallCommon) (llvm.Value, e
if fn := instr.StaticCallee(); fn != nil {
name := fn.RelString(nil)
switch {
case name == "device/arm.ReadRegister":
return c.emitReadRegister(instr.Args)
case name == "device/arm.Asm" || name == "device/avr.Asm":
case name == "device/arm.ReadRegister" || name == "device/riscv.ReadRegister":
return c.emitReadRegister(name, instr.Args)
case name == "device/arm.Asm" || name == "device/avr.Asm" || name == "device/riscv.Asm":
return c.emitAsm(instr.Args)
case name == "device/arm.AsmFull" || name == "device/avr.AsmFull":
case name == "device/arm.AsmFull" || name == "device/avr.AsmFull" || name == "device/riscv.AsmFull":
return c.emitAsmFull(frame, instr)
case strings.HasPrefix(name, "device/arm.SVCall"):
return c.emitSVCall(frame, instr.Args)
@@ -1327,10 +1352,7 @@ func (c *Compiler) parseCall(frame *Frame, instr *ssa.CallCommon) (llvm.Value, e
value := c.getValue(frame, instr.Value)
// This is a func value, which cannot be called directly. We have to
// extract the function pointer and context first from the func value.
funcPtr, context, err := c.decodeFuncValue(value, instr.Value.Type().Underlying().(*types.Signature))
if err != nil {
return llvm.Value{}, err
}
funcPtr, context := c.decodeFuncValue(value, instr.Value.Type().Underlying().(*types.Signature))
c.emitNilCheck(frame, funcPtr, "fpcall")
return c.parseFunctionCall(frame, instr.Args, funcPtr, context, false), nil
}
@@ -1350,9 +1372,9 @@ func (c *Compiler) getValue(frame *Frame, expr ssa.Value) llvm.Value {
}
return c.createFuncValue(fn.LLVMFn, llvm.Undef(c.i8ptrType), fn.Signature)
case *ssa.Global:
value := c.ir.GetGlobal(expr).LLVMGlobal
value := c.getGlobal(expr)
if value.IsNil() {
c.addError(expr.Pos(), "global not found: "+c.ir.GetGlobal(expr).LinkName())
c.addError(expr.Pos(), "global not found: "+expr.RelString(nil))
return llvm.Undef(c.getLLVMType(expr.Type()))
}
return value
@@ -1385,7 +1407,6 @@ func (c *Compiler) parseExpr(frame *Frame, expr ssa.Value) (llvm.Value, error) {
// Size would be truncated if truncated to uintptr.
return llvm.Value{}, c.makeError(expr.Pos(), fmt.Sprintf("value is too big (%v bytes)", size))
}
// TODO: escape analysis
sizeValue := llvm.ConstInt(c.uintptrType, size, false)
buf := c.createRuntimeCall("alloc", []llvm.Value{sizeValue}, expr.Comment)
buf = c.builder.CreateBitCast(buf, llvm.PointerType(typ, 0), "")
@@ -1449,9 +1470,11 @@ func (c *Compiler) parseExpr(frame *Frame, expr ssa.Value) (llvm.Value, error) {
x := c.getValue(frame, expr.X)
return c.parseConvert(expr.X.Type(), expr.Type(), x, expr.Pos())
case *ssa.Extract:
if _, ok := expr.Tuple.(*ssa.Select); ok {
return c.getChanSelectResult(frame, expr), nil
}
value := c.getValue(frame, expr.Tuple)
result := c.builder.CreateExtractValue(value, expr.Index, "")
return result, nil
return c.builder.CreateExtractValue(value, expr.Index, ""), nil
case *ssa.Field:
value := c.getValue(frame, expr.X)
if s := expr.X.Type().Underlying().(*types.Struct); s.NumFields() > 2 && s.Field(0).Name() == "C union" {
@@ -1630,7 +1653,6 @@ func (c *Compiler) parseExpr(frame *Frame, expr ssa.Value) (llvm.Value, error) {
c.emitSliceBoundsCheck(frame, maxSize, sliceLen, sliceCap, expr.Len.Type().(*types.Basic), expr.Cap.Type().(*types.Basic))
// Allocate the backing array.
// TODO: escape analysis
sliceCapCast, err := c.parseConvert(expr.Cap.Type(), types.Typ[types.Uintptr], sliceCap, expr.Pos())
if err != nil {
return llvm.Value{}, err
@@ -1690,9 +1712,9 @@ func (c *Compiler) parseExpr(frame *Frame, expr ssa.Value) (llvm.Value, error) {
var iteratorType llvm.Type
switch typ := expr.X.Type().Underlying().(type) {
case *types.Basic: // string
iteratorType = c.mod.GetTypeByName("runtime.stringIterator")
iteratorType = c.getLLVMRuntimeType("stringIterator")
case *types.Map:
iteratorType = c.mod.GetTypeByName("runtime.hashmapIterator")
iteratorType = c.getLLVMRuntimeType("hashmapIterator")
default:
panic("unknown type in range: " + typ.String())
}
@@ -1700,25 +1722,7 @@ func (c *Compiler) parseExpr(frame *Frame, expr ssa.Value) (llvm.Value, error) {
c.builder.CreateStore(c.getZeroValue(iteratorType), it)
return it, nil
case *ssa.Select:
if len(expr.States) == 0 {
// Shortcuts for some simple selects.
llvmType := c.getLLVMType(expr.Type())
if expr.Blocking {
// Blocks forever:
// select {}
c.createRuntimeCall("deadlockStub", nil, "")
return llvm.Undef(llvmType), nil
} else {
// No-op:
// select {
// default:
// }
retval := llvm.Undef(llvmType)
retval = c.builder.CreateInsertValue(retval, llvm.ConstInt(c.intType, 0xffffffffffffffff, true), 0, "")
return retval, nil // {-1, false}
}
}
return llvm.Value{}, c.makeError(expr.Pos(), "unimplemented: "+expr.String())
return c.emitSelect(frame, expr), nil
case *ssa.Slice:
if expr.Max != nil {
return llvm.Value{}, c.makeError(expr.Pos(), "todo: full slice expressions (with max): "+expr.Type().String())
@@ -1852,7 +1856,7 @@ func (c *Compiler) parseExpr(frame *Frame, expr ssa.Value) (llvm.Value, error) {
newPtr := c.builder.CreateInBoundsGEP(oldPtr, []llvm.Value{low}, "")
newLen := c.builder.CreateSub(high, low, "")
str := llvm.Undef(c.mod.GetTypeByName("runtime._string"))
str := llvm.Undef(c.getLLVMRuntimeType("_string"))
str = c.builder.CreateInsertValue(str, newPtr, 0, "")
str = c.builder.CreateInsertValue(str, newLen, 1, "")
return str, nil
@@ -2231,7 +2235,7 @@ func (c *Compiler) parseConst(prefix string, expr *ssa.Const) llvm.Value {
global.SetUnnamedAddr(true)
zero := llvm.ConstInt(c.ctx.Int32Type(), 0, false)
strPtr := c.builder.CreateInBoundsGEP(global, []llvm.Value{zero, zero}, "")
strObj := llvm.ConstNamedStruct(c.mod.GetTypeByName("runtime._string"), []llvm.Value{strPtr, strLen})
strObj := llvm.ConstNamedStruct(c.getLLVMRuntimeType("_string"), []llvm.Value{strPtr, strLen})
return strObj
} else if typ.Kind() == types.UnsafePointer {
if !expr.IsNil() {
@@ -2284,7 +2288,7 @@ func (c *Compiler) parseConst(prefix string, expr *ssa.Const) llvm.Value {
llvm.ConstInt(c.uintptrType, 0, false),
llvm.ConstPointerNull(c.i8ptrType),
}
return llvm.ConstNamedStruct(c.mod.GetTypeByName("runtime._interface"), fields)
return llvm.ConstNamedStruct(c.getLLVMRuntimeType("_interface"), fields)
case *types.Pointer:
if expr.Value != nil {
panic("expected nil pointer constant")
@@ -2467,8 +2471,10 @@ func (c *Compiler) parseConvert(typeFrom, typeTo types.Type, value llvm.Value, p
switch elemType.Kind() {
case types.Byte:
return c.createRuntimeCall("stringToBytes", []llvm.Value{value}, ""), nil
case types.Rune:
return c.createRuntimeCall("stringToRunes", []llvm.Value{value}, ""), nil
default:
return llvm.Value{}, c.makeError(pos, "todo: convert from string: "+elemType.String())
panic("unexpected type in string to slice conversion")
}
default:
@@ -2494,7 +2500,7 @@ func (c *Compiler) parseUnOp(frame *Frame, unop *ssa.UnOp) (llvm.Value, error) {
return llvm.Value{}, c.makeError(unop.Pos(), "todo: unknown type for negate: "+unop.X.Type().Underlying().String())
}
case token.MUL: // *x, dereference pointer
valType := unop.X.Type().Underlying().(*types.Pointer).Elem()
unop.X.Type().Underlying().(*types.Pointer).Elem()
if c.targetData.TypeAllocSize(x.Type().ElementType()) == 0 {
// zero-length data
return c.getZeroValue(x.Type().ElementType()), nil
@@ -2504,8 +2510,8 @@ func (c *Compiler) parseUnOp(frame *Frame, unop *ssa.UnOp) (llvm.Value, error) {
// var C.add unsafe.Pointer
// Instead of a load from the global, create a bitcast of the
// function pointer itself.
global := c.ir.GetGlobal(unop.X.(*ssa.Global))
name := global.LinkName()[:len(global.LinkName())-len("$funcaddr")]
globalName := c.getGlobalInfo(unop.X.(*ssa.Global)).linkName
name := globalName[:len(globalName)-len("$funcaddr")]
fn := c.mod.NamedFunction(name)
if fn.IsNil() {
return llvm.Value{}, c.makeError(unop.Pos(), "cgo function not found: "+name)
@@ -2514,10 +2520,6 @@ func (c *Compiler) parseUnOp(frame *Frame, unop *ssa.UnOp) (llvm.Value, error) {
} else {
c.emitNilCheck(frame, x, "deref")
load := c.builder.CreateLoad(x, "")
if c.ir.IsVolatile(valType) {
// Volatile load, for memory-mapped registers.
load.SetVolatile(true)
}
return load, nil
}
case token.XOR: // ^x, toggle all bits in integer
+7 -4
View File
@@ -29,7 +29,7 @@ func (c *Compiler) deferInitFunc(frame *Frame) {
frame.deferClosureFuncs = make(map[*ir.Function]int)
// Create defer list pointer.
deferType := llvm.PointerType(c.mod.GetTypeByName("runtime._defer"), 0)
deferType := llvm.PointerType(c.getLLVMRuntimeType("_defer"), 0)
frame.deferPtr = c.builder.CreateAlloca(deferType, "deferPtr")
c.builder.CreateStore(llvm.ConstPointerNull(deferType), frame.deferPtr)
}
@@ -130,6 +130,9 @@ func (c *Compiler) emitDefer(frame *Frame, instr *ssa.Defer) {
// Put this struct in an alloca.
alloca := c.builder.CreateAlloca(deferFrameType, "defer.alloca")
c.builder.CreateStore(deferFrame, alloca)
if c.needsStackObjects() {
c.trackPointer(alloca)
}
// Push it on top of the linked list by replacing deferPtr.
allocaCast := c.builder.CreateBitCast(alloca, next.Type(), "defer.alloca.cast")
@@ -200,7 +203,7 @@ func (c *Compiler) emitRunDefers(frame *Frame) {
}
// Get the real defer struct type and cast to it.
valueTypes := []llvm.Type{c.uintptrType, llvm.PointerType(c.mod.GetTypeByName("runtime._defer"), 0), c.i8ptrType}
valueTypes := []llvm.Type{c.uintptrType, llvm.PointerType(c.getLLVMRuntimeType("_defer"), 0), c.i8ptrType}
for _, arg := range callback.Args {
valueTypes = append(valueTypes, c.getLLVMType(arg.Type()))
}
@@ -231,7 +234,7 @@ func (c *Compiler) emitRunDefers(frame *Frame) {
// Direct call.
// Get the real defer struct type and cast to it.
valueTypes := []llvm.Type{c.uintptrType, llvm.PointerType(c.mod.GetTypeByName("runtime._defer"), 0)}
valueTypes := []llvm.Type{c.uintptrType, llvm.PointerType(c.getLLVMRuntimeType("_defer"), 0)}
for _, param := range callback.Params {
valueTypes = append(valueTypes, c.getLLVMType(param.Type()))
}
@@ -260,7 +263,7 @@ func (c *Compiler) emitRunDefers(frame *Frame) {
case *ssa.MakeClosure:
// Get the real defer struct type and cast to it.
fn := c.ir.GetFunction(callback.Fn.(*ssa.Function))
valueTypes := []llvm.Type{c.uintptrType, llvm.PointerType(c.mod.GetTypeByName("runtime._defer"), 0)}
valueTypes := []llvm.Type{c.uintptrType, llvm.PointerType(c.getLLVMRuntimeType("_defer"), 0)}
params := fn.Signature.Params()
for i := 0; i < params.Len(); i++ {
valueTypes = append(valueTypes, c.getLLVMType(params.At(i).Type()))
+1 -1
View File
@@ -51,7 +51,7 @@ func (c *Compiler) LowerFuncValues() {
}
// Find all func values used in the program with their signatures.
funcValueWithSignaturePtr := llvm.PointerType(c.mod.GetTypeByName("runtime.funcValueWithSignature"), 0)
funcValueWithSignaturePtr := llvm.PointerType(c.getLLVMRuntimeType("funcValueWithSignature"), 0)
signatures := map[string]*funcSignatureInfo{}
for global := c.mod.FirstGlobal(); !global.IsNil(); global = llvm.NextGlobal(global) {
if global.Type() != funcValueWithSignaturePtr {
+3 -3
View File
@@ -52,7 +52,7 @@ func (c *Compiler) createFuncValue(funcPtr, context llvm.Value, sig *types.Signa
funcValueWithSignatureGlobalName := funcPtr.Name() + "$withSignature"
funcValueWithSignatureGlobal := c.mod.NamedGlobal(funcValueWithSignatureGlobalName)
if funcValueWithSignatureGlobal.IsNil() {
funcValueWithSignatureType := c.mod.GetTypeByName("runtime.funcValueWithSignature")
funcValueWithSignatureType := c.getLLVMRuntimeType("funcValueWithSignature")
funcValueWithSignature := llvm.ConstNamedStruct(funcValueWithSignatureType, []llvm.Value{
llvm.ConstPtrToInt(funcPtr, c.uintptrType),
sigGlobal,
@@ -103,7 +103,7 @@ func (c *Compiler) extractFuncContext(funcValue llvm.Value) llvm.Value {
// decodeFuncValue extracts the context and the function pointer from this func
// value. This may be an expensive operation.
func (c *Compiler) decodeFuncValue(funcValue llvm.Value, sig *types.Signature) (funcPtr, context llvm.Value, err error) {
func (c *Compiler) decodeFuncValue(funcValue llvm.Value, sig *types.Signature) (funcPtr, context llvm.Value) {
context = c.builder.CreateExtractValue(funcValue, 0, "")
switch c.funcImplementation() {
case funcValueDoubleword:
@@ -126,7 +126,7 @@ func (c *Compiler) getFuncType(typ *types.Signature) llvm.Type {
rawPtr := c.getRawFuncType(typ)
return c.ctx.StructType([]llvm.Type{c.i8ptrType, rawPtr}, false)
case funcValueSwitch:
return c.mod.GetTypeByName("runtime.funcValue")
return c.getLLVMRuntimeType("funcValue")
default:
panic("unimplemented func value variant")
}
+471
View File
@@ -0,0 +1,471 @@
package compiler
// This file provides IR transformations necessary for precise and portable
// garbage collectors.
import (
"go/token"
"math/big"
"golang.org/x/tools/go/ssa"
"tinygo.org/x/go-llvm"
)
// needsStackObjects returns true if the compiler should insert stack objects
// that can be traced by the garbage collector.
func (c *Compiler) needsStackObjects() bool {
if c.selectGC() != "conservative" {
return false
}
for _, tag := range c.BuildTags {
if tag == "cortexm" || tag == "tinygo.riscv" {
return false
}
}
return true
}
// trackExpr inserts pointer tracking intrinsics for the GC if the expression is
// one of the expressions that need this.
func (c *Compiler) trackExpr(frame *Frame, expr ssa.Value, value llvm.Value) {
// There are uses of this expression, Make sure the pointers
// are tracked during GC.
switch expr := expr.(type) {
case *ssa.Alloc, *ssa.MakeChan, *ssa.MakeMap:
// These values are always of pointer type in IR.
c.trackPointer(value)
case *ssa.Call, *ssa.Convert, *ssa.MakeClosure, *ssa.MakeInterface, *ssa.MakeSlice, *ssa.Next:
if !value.IsNil() {
c.trackValue(value)
}
case *ssa.Select:
if alloca, ok := frame.selectRecvBuf[expr]; ok {
if alloca.IsAUndefValue().IsNil() {
c.trackPointer(alloca)
}
}
case *ssa.UnOp:
switch expr.Op {
case token.MUL:
// Pointer dereference.
c.trackValue(value)
case token.ARROW:
// Channel receive operator.
// It's not necessary to look at commaOk here, because in that
// case it's just an aggregate and trackValue will extract the
// pointer in there (if there is one).
c.trackValue(value)
}
}
}
// trackValue locates pointers in a value (possibly an aggregate) and tracks the
// individual pointers
func (c *Compiler) trackValue(value llvm.Value) {
typ := value.Type()
switch typ.TypeKind() {
case llvm.PointerTypeKind:
c.trackPointer(value)
case llvm.StructTypeKind:
if !typeHasPointers(typ) {
return
}
numElements := typ.StructElementTypesCount()
for i := 0; i < numElements; i++ {
subValue := c.builder.CreateExtractValue(value, i, "")
c.trackValue(subValue)
}
case llvm.ArrayTypeKind:
if !typeHasPointers(typ) {
return
}
numElements := typ.ArrayLength()
for i := 0; i < numElements; i++ {
subValue := c.builder.CreateExtractValue(value, i, "")
c.trackValue(subValue)
}
}
}
// trackPointer creates a call to runtime.trackPointer, bitcasting the poitner
// first if needed. The input value must be of LLVM pointer type.
func (c *Compiler) trackPointer(value llvm.Value) {
if value.Type() != c.i8ptrType {
value = c.builder.CreateBitCast(value, c.i8ptrType, "")
}
c.createRuntimeCall("trackPointer", []llvm.Value{value}, "")
}
// typeHasPointers returns whether this type is a pointer or contains pointers.
// If the type is an aggregate type, it will check whether there is a pointer
// inside.
func typeHasPointers(t llvm.Type) bool {
switch t.TypeKind() {
case llvm.PointerTypeKind:
return true
case llvm.StructTypeKind:
for _, subType := range t.StructElementTypes() {
if typeHasPointers(subType) {
return true
}
}
return false
case llvm.ArrayTypeKind:
if typeHasPointers(t.ElementType()) {
return true
}
return false
default:
return false
}
}
// makeGCStackSlots converts all calls to runtime.trackPointer to explicit
// stores to stack slots that are scannable by the GC.
func (c *Compiler) makeGCStackSlots() bool {
// Check whether there are allocations at all.
alloc := c.mod.NamedFunction("runtime.alloc")
if alloc.IsNil() {
// Nothing to. Make sure all remaining bits and pieces for stack
// chains are neutralized.
for _, call := range getUses(c.mod.NamedFunction("runtime.trackPointer")) {
call.EraseFromParentAsInstruction()
}
stackChainStart := c.mod.NamedGlobal("runtime.stackChainStart")
if !stackChainStart.IsNil() {
stackChainStart.SetInitializer(c.getZeroValue(stackChainStart.Type().ElementType()))
stackChainStart.SetGlobalConstant(true)
}
}
trackPointer := c.mod.NamedFunction("runtime.trackPointer")
if trackPointer.IsNil() || trackPointer.FirstUse().IsNil() {
return false // nothing to do
}
// Look at *all* functions to see whether they are free of function pointer
// calls.
// This takes less than 5ms for ~100kB of WebAssembly but would perhaps be
// faster when written in C++ (to avoid the CGo overhead).
funcsWithFPCall := map[llvm.Value]struct{}{}
n := 0
for fn := c.mod.FirstFunction(); !fn.IsNil(); fn = llvm.NextFunction(fn) {
n++
if _, ok := funcsWithFPCall[fn]; ok {
continue // already found
}
done := false
for bb := fn.FirstBasicBlock(); !bb.IsNil() && !done; bb = llvm.NextBasicBlock(bb) {
for call := bb.FirstInstruction(); !call.IsNil() && !done; call = llvm.NextInstruction(call) {
if call.IsACallInst().IsNil() {
continue // only looking at calls
}
called := call.CalledValue()
if !called.IsAFunction().IsNil() {
continue // only looking for function pointers
}
funcsWithFPCall[fn] = struct{}{}
markParentFunctions(funcsWithFPCall, fn)
done = true
}
}
}
// Determine which functions need stack objects. Many leaf functions don't
// need it: it only causes overhead for them.
// Actually, in one test it was only able to eliminate stack object from 12%
// of functions that had a call to runtime.trackPointer (8 out of 68
// functions), so this optimization is not as big as it may seem.
allocatingFunctions := map[llvm.Value]struct{}{} // set of allocating functions
// Work from runtime.alloc and trace all parents to check which functions do
// a heap allocation (and thus which functions do not).
markParentFunctions(allocatingFunctions, alloc)
// Also trace all functions that call a function pointer.
for fn := range funcsWithFPCall {
// Assume that functions that call a function pointer do a heap
// allocation as a conservative guess because the called function might
// do a heap allocation.
allocatingFunctions[fn] = struct{}{}
markParentFunctions(allocatingFunctions, fn)
}
// Collect some variables used below in the loop.
stackChainStart := c.mod.NamedGlobal("runtime.stackChainStart")
if stackChainStart.IsNil() {
panic("stack chain start not found!")
}
stackChainStartType := stackChainStart.Type().ElementType()
stackChainStart.SetInitializer(c.getZeroValue(stackChainStartType))
// Iterate until runtime.trackPointer has no uses left.
for use := trackPointer.FirstUse(); !use.IsNil(); use = trackPointer.FirstUse() {
// Pick the first use of runtime.trackPointer.
call := use.User()
if call.IsACallInst().IsNil() {
panic("expected runtime.trackPointer use to be a call")
}
// Pick the parent function.
fn := call.InstructionParent().Parent()
if _, ok := allocatingFunctions[fn]; !ok {
// This function nor any of the functions it calls (recursively)
// allocate anything from the heap, so it will not trigger a garbage
// collection cycle. Thus, it does not need to track local pointer
// values.
// This is a useful optimization but not as big as you might guess,
// as described above (it avoids stack objects for ~12% of
// functions).
call.EraseFromParentAsInstruction()
continue
}
// Find all calls to runtime.trackPointer in this function.
var calls []llvm.Value
var returns []llvm.Value
for bb := fn.FirstBasicBlock(); !bb.IsNil(); bb = llvm.NextBasicBlock(bb) {
for inst := bb.FirstInstruction(); !inst.IsNil(); inst = llvm.NextInstruction(inst) {
switch inst.InstructionOpcode() {
case llvm.Call:
if inst.CalledValue() == trackPointer {
calls = append(calls, inst)
}
case llvm.Ret:
returns = append(returns, inst)
}
}
}
// Determine what to do with each call.
var allocas, pointers []llvm.Value
for _, call := range calls {
ptr := call.Operand(0)
call.EraseFromParentAsInstruction()
if ptr.IsAInstruction().IsNil() {
continue
}
// Some trivial optimizations.
if ptr.IsAInstruction().IsNil() {
continue
}
switch ptr.InstructionOpcode() {
case llvm.PHI, llvm.GetElementPtr:
// These values do not create new values: the values already
// existed locally in this function so must have been tracked
// already.
continue
case llvm.ExtractValue, llvm.BitCast:
// These instructions do not create new values, but their
// original value may not be tracked. So keep tracking them for
// now.
// With more analysis, it should be possible to optimize a
// significant chunk of these away.
case llvm.Call, llvm.Load, llvm.IntToPtr:
// These create new values so must be stored locally. But
// perhaps some of these can be fused when they actually refer
// to the same value.
default:
// Ambiguous. These instructions are uncommon, but perhaps could
// be optimized if needed.
}
if !ptr.IsAAllocaInst().IsNil() {
if typeHasPointers(ptr.Type().ElementType()) {
allocas = append(allocas, ptr)
}
} else {
pointers = append(pointers, ptr)
}
}
if len(allocas) == 0 && len(pointers) == 0 {
// This function does not need to keep track of stack pointers.
continue
}
// Determine the type of the required stack slot.
fields := []llvm.Type{
stackChainStartType, // Pointer to parent frame.
c.uintptrType, // Number of elements in this frame.
}
for _, alloca := range allocas {
fields = append(fields, alloca.Type().ElementType())
}
for _, ptr := range pointers {
fields = append(fields, ptr.Type())
}
stackObjectType := c.ctx.StructType(fields, false)
// Create the stack object at the function entry.
c.builder.SetInsertPointBefore(fn.EntryBasicBlock().FirstInstruction())
stackObject := c.builder.CreateAlloca(stackObjectType, "gc.stackobject")
initialStackObject := c.getZeroValue(stackObjectType)
numSlots := (c.targetData.TypeAllocSize(stackObjectType) - c.targetData.TypeAllocSize(c.i8ptrType)*2) / uint64(c.targetData.ABITypeAlignment(c.uintptrType))
numSlotsValue := llvm.ConstInt(c.uintptrType, numSlots, false)
initialStackObject = llvm.ConstInsertValue(initialStackObject, numSlotsValue, []uint32{1})
c.builder.CreateStore(initialStackObject, stackObject)
// Update stack start.
parent := c.builder.CreateLoad(stackChainStart, "")
gep := c.builder.CreateGEP(stackObject, []llvm.Value{
llvm.ConstInt(c.ctx.Int32Type(), 0, false),
llvm.ConstInt(c.ctx.Int32Type(), 0, false),
}, "")
c.builder.CreateStore(parent, gep)
stackObjectCast := c.builder.CreateBitCast(stackObject, stackChainStartType, "")
c.builder.CreateStore(stackObjectCast, stackChainStart)
// Replace all independent allocas with GEPs in the stack object.
for i, alloca := range allocas {
gep := c.builder.CreateGEP(stackObject, []llvm.Value{
llvm.ConstInt(c.ctx.Int32Type(), 0, false),
llvm.ConstInt(c.ctx.Int32Type(), uint64(2+i), false),
}, "")
alloca.ReplaceAllUsesWith(gep)
alloca.EraseFromParentAsInstruction()
}
// Do a store to the stack object after each new pointer that is created.
for i, ptr := range pointers {
c.builder.SetInsertPointBefore(llvm.NextInstruction(ptr))
gep := c.builder.CreateGEP(stackObject, []llvm.Value{
llvm.ConstInt(c.ctx.Int32Type(), 0, false),
llvm.ConstInt(c.ctx.Int32Type(), uint64(2+len(allocas)+i), false),
}, "")
c.builder.CreateStore(ptr, gep)
}
// Make sure this stack object is popped from the linked list of stack
// objects at return.
for _, ret := range returns {
c.builder.SetInsertPointBefore(ret)
c.builder.CreateStore(parent, stackChainStart)
}
}
return true
}
func (c *Compiler) addGlobalsBitmap() bool {
if c.mod.NamedGlobal("runtime.trackedGlobalsStart").IsNil() {
return false // nothing to do: no GC in use
}
var trackedGlobals []llvm.Value
var trackedGlobalTypes []llvm.Type
for global := c.mod.FirstGlobal(); !global.IsNil(); global = llvm.NextGlobal(global) {
if global.IsDeclaration() {
continue
}
typ := global.Type().ElementType()
ptrs := c.getPointerBitmap(typ, global.Name())
if ptrs.BitLen() == 0 {
continue
}
trackedGlobals = append(trackedGlobals, global)
trackedGlobalTypes = append(trackedGlobalTypes, typ)
}
globalsBundleType := c.ctx.StructType(trackedGlobalTypes, false)
globalsBundle := llvm.AddGlobal(c.mod, globalsBundleType, "tinygo.trackedGlobals")
globalsBundle.SetLinkage(llvm.InternalLinkage)
globalsBundle.SetUnnamedAddr(true)
initializer := llvm.Undef(globalsBundleType)
for i, global := range trackedGlobals {
initializer = llvm.ConstInsertValue(initializer, global.Initializer(), []uint32{uint32(i)})
gep := llvm.ConstGEP(globalsBundle, []llvm.Value{
llvm.ConstInt(c.ctx.Int32Type(), 0, false),
llvm.ConstInt(c.ctx.Int32Type(), uint64(i), false),
})
global.ReplaceAllUsesWith(gep)
global.EraseFromParentAsGlobal()
}
globalsBundle.SetInitializer(initializer)
trackedGlobalsStart := llvm.ConstPtrToInt(globalsBundle, c.uintptrType)
c.mod.NamedGlobal("runtime.trackedGlobalsStart").SetInitializer(trackedGlobalsStart)
alignment := c.targetData.PrefTypeAlignment(c.i8ptrType)
trackedGlobalsLength := llvm.ConstInt(c.uintptrType, c.targetData.TypeAllocSize(globalsBundleType)/uint64(alignment), false)
c.mod.NamedGlobal("runtime.trackedGlobalsLength").SetInitializer(trackedGlobalsLength)
bitmapBytes := c.getPointerBitmap(globalsBundleType, "globals bundle").Bytes()
bitmapValues := make([]llvm.Value, len(bitmapBytes))
for i, b := range bitmapBytes {
bitmapValues[len(bitmapBytes)-i-1] = llvm.ConstInt(c.ctx.Int8Type(), uint64(b), false)
}
bitmapArray := llvm.ConstArray(llvm.ArrayType(c.ctx.Int8Type(), len(bitmapBytes)), bitmapValues)
bitmapNew := llvm.AddGlobal(c.mod, bitmapArray.Type(), "runtime.trackedGlobalsBitmap.tmp")
bitmapOld := c.mod.NamedGlobal("runtime.trackedGlobalsBitmap")
bitmapOld.ReplaceAllUsesWith(bitmapNew)
bitmapNew.SetInitializer(bitmapArray)
bitmapNew.SetName("runtime.trackedGlobalsBitmap")
return true // the IR was changed
}
func (c *Compiler) getPointerBitmap(typ llvm.Type, name string) *big.Int {
alignment := c.targetData.PrefTypeAlignment(c.i8ptrType)
switch typ.TypeKind() {
case llvm.IntegerTypeKind, llvm.FloatTypeKind, llvm.DoubleTypeKind:
return big.NewInt(0)
case llvm.PointerTypeKind:
return big.NewInt(1)
case llvm.StructTypeKind:
ptrs := big.NewInt(0)
for i, subtyp := range typ.StructElementTypes() {
subptrs := c.getPointerBitmap(subtyp, name)
if subptrs.BitLen() == 0 {
continue
}
offset := c.targetData.ElementOffset(typ, i)
if offset%uint64(alignment) != 0 {
panic("precise GC: global contains unaligned pointer: " + name)
}
subptrs.Lsh(subptrs, uint(offset)/uint(alignment))
ptrs.Or(ptrs, subptrs)
}
return ptrs
case llvm.ArrayTypeKind:
subtyp := typ.ElementType()
subptrs := c.getPointerBitmap(subtyp, name)
ptrs := big.NewInt(0)
if subptrs.BitLen() == 0 {
return ptrs
}
elementSize := c.targetData.TypeAllocSize(subtyp)
for i := 0; i < typ.ArrayLength(); i++ {
ptrs.Lsh(ptrs, uint(elementSize)/uint(alignment))
ptrs.Or(ptrs, subptrs)
}
return ptrs
default:
panic("unknown type kind of global: " + name)
}
}
// markParentFunctions traverses all parent function calls (recursively) and
// adds them to the set of marked functions. It only considers function calls:
// any other uses of such a function is ignored.
func markParentFunctions(marked map[llvm.Value]struct{}, fn llvm.Value) {
worklist := []llvm.Value{fn}
for len(worklist) != 0 {
fn := worklist[len(worklist)-1]
worklist = worklist[:len(worklist)-1]
for _, use := range getUses(fn) {
if use.IsACallInst().IsNil() || use.CalledValue() != fn {
// Not the parent function.
continue
}
parent := use.InstructionParent().Parent()
if _, ok := marked[parent]; !ok {
marked[parent] = struct{}{}
worklist = append(worklist, parent)
}
}
}
}
+4 -1
View File
@@ -321,7 +321,7 @@ func (c *Compiler) markAsyncFunctions() (needsScheduler bool, err error) {
// Coroutine setup.
c.builder.SetInsertPointBefore(f.EntryBasicBlock().FirstInstruction())
taskState := c.builder.CreateAlloca(c.mod.GetTypeByName("runtime.taskState"), "task.state")
taskState := c.builder.CreateAlloca(c.getLLVMRuntimeType("taskState"), "task.state")
stateI8 := c.builder.CreateBitCast(taskState, c.i8ptrType, "task.state.i8")
id := c.builder.CreateCall(coroIdFunc, []llvm.Value{
llvm.ConstInt(c.ctx.Int32Type(), 0, false),
@@ -336,6 +336,9 @@ func (c *Compiler) markAsyncFunctions() (needsScheduler bool, err error) {
size = c.builder.CreateZExt(size, c.uintptrType, "task.size.uintptr")
}
data := c.createRuntimeCall("alloc", []llvm.Value{size}, "task.data")
if c.needsStackObjects() {
c.trackPointer(data)
}
frame.taskHandle = c.builder.CreateCall(coroBeginFunc, []llvm.Value{id, data}, "task.handle")
// Modify async calls so this function suspends right after the child
+11 -2
View File
@@ -18,10 +18,19 @@ import (
// func ReadRegister(name string) uintptr
//
// The register name must be a constant, for example "sp".
func (c *Compiler) emitReadRegister(args []ssa.Value) (llvm.Value, error) {
func (c *Compiler) emitReadRegister(name string, args []ssa.Value) (llvm.Value, error) {
fnType := llvm.FunctionType(c.uintptrType, []llvm.Type{}, false)
regname := constant.StringVal(args[0].(*ssa.Const).Value)
target := llvm.InlineAsm(fnType, "mov $0, "+regname, "=r", false, false, 0)
var asm string
switch name {
case "device/arm.ReadRegister":
asm = "mov $0, " + regname
case "device/riscv.ReadRegister":
asm = "mv $0, " + regname
default:
panic("unknown architecture")
}
target := llvm.InlineAsm(fnType, asm, "=r", false, false, 0)
return c.builder.CreateCall(target, nil, ""), nil
}
+2 -2
View File
@@ -163,8 +163,8 @@ func (c *Compiler) LowerInterfaces() {
// run runs the pass itself.
func (p *lowerInterfacesPass) run() {
// Collect all type codes.
typecodeIDPtr := llvm.PointerType(p.mod.GetTypeByName("runtime.typecodeID"), 0)
typeInInterfacePtr := llvm.PointerType(p.mod.GetTypeByName("runtime.typeInInterface"), 0)
typecodeIDPtr := llvm.PointerType(p.getLLVMRuntimeType("typecodeID"), 0)
typeInInterfacePtr := llvm.PointerType(p.getLLVMRuntimeType("typeInInterface"), 0)
var typesInInterfaces []llvm.Value
for global := p.mod.FirstGlobal(); !global.IsNil(); global = llvm.NextGlobal(global) {
switch global.Type() {
+5 -5
View File
@@ -28,14 +28,14 @@ func (c *Compiler) parseMakeInterface(val llvm.Value, typ types.Type, pos token.
itfMethodSetGlobal := c.getTypeMethodSet(typ)
itfConcreteTypeGlobal := c.mod.NamedGlobal("typeInInterface:" + itfTypeCodeGlobal.Name())
if itfConcreteTypeGlobal.IsNil() {
typeInInterface := c.mod.GetTypeByName("runtime.typeInInterface")
typeInInterface := c.getLLVMRuntimeType("typeInInterface")
itfConcreteTypeGlobal = llvm.AddGlobal(c.mod, typeInInterface, "typeInInterface:"+itfTypeCodeGlobal.Name())
itfConcreteTypeGlobal.SetInitializer(llvm.ConstNamedStruct(typeInInterface, []llvm.Value{itfTypeCodeGlobal, itfMethodSetGlobal}))
itfConcreteTypeGlobal.SetGlobalConstant(true)
itfConcreteTypeGlobal.SetLinkage(llvm.PrivateLinkage)
}
itfTypeCode := c.builder.CreatePtrToInt(itfConcreteTypeGlobal, c.uintptrType, "")
itf := llvm.Undef(c.mod.GetTypeByName("runtime._interface"))
itf := llvm.Undef(c.getLLVMRuntimeType("_interface"))
itf = c.builder.CreateInsertValue(itf, itfTypeCode, 0, "")
itf = c.builder.CreateInsertValue(itf, itfValue, 1, "")
return itf
@@ -48,7 +48,7 @@ func (c *Compiler) getTypeCode(typ types.Type) llvm.Value {
globalName := "type:" + getTypeCodeName(typ)
global := c.mod.NamedGlobal(globalName)
if global.IsNil() {
global = llvm.AddGlobal(c.mod, c.mod.GetTypeByName("runtime.typecodeID"), globalName)
global = llvm.AddGlobal(c.mod, c.getLLVMRuntimeType("typecodeID"), globalName)
global.SetGlobalConstant(true)
}
return global
@@ -163,11 +163,11 @@ func (c *Compiler) getTypeMethodSet(typ types.Type) llvm.Value {
ms := c.ir.Program.MethodSets.MethodSet(typ)
if ms.Len() == 0 {
// no methods, so can leave that one out
return llvm.ConstPointerNull(llvm.PointerType(c.mod.GetTypeByName("runtime.interfaceMethodInfo"), 0))
return llvm.ConstPointerNull(llvm.PointerType(c.getLLVMRuntimeType("interfaceMethodInfo"), 0))
}
methods := make([]llvm.Value, ms.Len())
interfaceMethodInfoType := c.mod.GetTypeByName("runtime.interfaceMethodInfo")
interfaceMethodInfoType := c.getLLVMRuntimeType("interfaceMethodInfo")
for i := 0; i < ms.Len(); i++ {
method := ms.At(i)
signatureGlobal := c.getMethodSignature(method.Obj().(*types.Func))
+5 -3
View File
@@ -35,7 +35,7 @@ func (c *Compiler) emitMapLookup(keyType, valueType types.Type, m, key llvm.Valu
c.emitLifetimeEnd(mapKeyPtr, mapKeySize)
} else {
// Not trivially comparable using memcmp.
return llvm.Value{}, c.makeError(pos, "only strings, bools, ints or structs of bools/ints are supported as map keys, but got: "+keyType.String())
return llvm.Value{}, c.makeError(pos, "only strings, bools, ints, pointers or structs of bools/ints are supported as map keys, but got: "+keyType.String())
}
// Load the resulting value from the hashmap. The value is set to the zero
@@ -69,7 +69,7 @@ func (c *Compiler) emitMapUpdate(keyType types.Type, m, key, value llvm.Value, p
c.createRuntimeCall("hashmapBinarySet", params, "")
c.emitLifetimeEnd(keyPtr, keySize)
} else {
c.addError(pos, "only strings, bools, ints or structs of bools/ints are supported as map keys, but got: "+keyType.String())
c.addError(pos, "only strings, bools, ints, pointers or structs of bools/ints are supported as map keys, but got: "+keyType.String())
}
c.emitLifetimeEnd(valuePtr, valueSize)
}
@@ -89,7 +89,7 @@ func (c *Compiler) emitMapDelete(keyType types.Type, m, key llvm.Value, pos toke
c.emitLifetimeEnd(keyPtr, keySize)
return nil
} else {
return c.makeError(pos, "only strings, bools, ints or structs of bools/ints are supported as map keys, but got: "+keyType.String())
return c.makeError(pos, "only strings, bools, ints, pointers or structs of bools/ints are supported as map keys, but got: "+keyType.String())
}
}
@@ -121,6 +121,8 @@ func hashmapIsBinaryKey(keyType types.Type) bool {
switch keyType := keyType.(type) {
case *types.Basic:
return keyType.Info()&(types.IsBoolean|types.IsInteger) != 0
case *types.Pointer:
return true
case *types.Struct:
for i := 0; i < keyType.NumFields(); i++ {
fieldType := keyType.Field(i).Type().Underlying()
+8
View File
@@ -114,6 +114,14 @@ func (c *Compiler) Optimize(optLevel, sizeLevel int, inlinerThreshold uint) erro
builder.Populate(modPasses)
modPasses.Run(c.mod)
hasGCPass := c.addGlobalsBitmap()
hasGCPass = c.makeGCStackSlots() || hasGCPass
if hasGCPass {
if err := c.Verify(); err != nil {
return errors.New("GC pass caused a verification failure")
}
}
return nil
}
+107
View File
@@ -0,0 +1,107 @@
package compiler
// This file manages symbols, that is, functions and globals. It reads their
// pragmas, determines the link name, etc.
import (
"go/ast"
"go/token"
"go/types"
"strings"
"github.com/tinygo-org/tinygo/loader"
"golang.org/x/tools/go/ssa"
"tinygo.org/x/go-llvm"
)
// globalInfo contains some information about a specific global. By default,
// linkName is equal to .RelString(nil) on a global and extern is false, but for
// some symbols this is different (due to //go:extern for example).
type globalInfo struct {
linkName string // go:extern
extern bool // go:extern
}
// loadASTComments loads comments on globals from the AST, for use later in the
// program. In particular, they are required for //go:extern pragmas on globals.
func (c *Compiler) loadASTComments(lprogram *loader.Program) {
c.astComments = map[string]*ast.CommentGroup{}
for _, pkgInfo := range lprogram.Sorted() {
for _, file := range pkgInfo.Files {
for _, decl := range file.Decls {
switch decl := decl.(type) {
case *ast.GenDecl:
switch decl.Tok {
case token.VAR:
if len(decl.Specs) != 1 {
continue
}
for _, spec := range decl.Specs {
switch spec := spec.(type) {
case *ast.ValueSpec: // decl.Tok == token.VAR
for _, name := range spec.Names {
id := pkgInfo.Pkg.Path() + "." + name.Name
c.astComments[id] = decl.Doc
}
}
}
}
}
}
}
}
}
// getGlobal returns a LLVM IR global value for a Go SSA global. It is added to
// the LLVM IR if it has not been added already.
func (c *Compiler) getGlobal(g *ssa.Global) llvm.Value {
info := c.getGlobalInfo(g)
llvmGlobal := c.mod.NamedGlobal(info.linkName)
if llvmGlobal.IsNil() {
llvmType := c.getLLVMType(g.Type().(*types.Pointer).Elem())
llvmGlobal = llvm.AddGlobal(c.mod, llvmType, info.linkName)
if !info.extern {
llvmGlobal.SetInitializer(c.getZeroValue(llvmType))
llvmGlobal.SetLinkage(llvm.InternalLinkage)
}
}
return llvmGlobal
}
// getGlobalInfo returns some information about a specific global.
func (c *Compiler) getGlobalInfo(g *ssa.Global) globalInfo {
info := globalInfo{}
if strings.HasPrefix(g.Name(), "C.") {
// Created by CGo: such a name cannot be created by regular C code.
info.linkName = g.Name()[2:]
info.extern = true
} else {
// Pick the default linkName.
info.linkName = g.RelString(nil)
// Check for //go: pragmas, which may change the link name (among
// others).
doc := c.astComments[info.linkName]
if doc != nil {
info.parsePragmas(doc)
}
}
return info
}
// Parse //go: pragma comments from the source. In particular, it parses the
// //go:extern pragma on globals.
func (info *globalInfo) parsePragmas(doc *ast.CommentGroup) {
for _, comment := range doc.List {
if !strings.HasPrefix(comment.Text, "//go:") {
continue
}
parts := strings.Fields(comment.Text)
switch parts[0] {
case "//go:extern":
info.extern = true
if len(parts) == 2 {
info.linkName = parts[1]
}
}
}
}
+5 -6
View File
@@ -4,7 +4,6 @@ package compiler
// compiler builtins.
import (
"go/constant"
"strconv"
"golang.org/x/tools/go/ssa"
@@ -14,7 +13,7 @@ import (
// emitSyscall emits an inline system call instruction, depending on the target
// OS/arch.
func (c *Compiler) emitSyscall(frame *Frame, call *ssa.CallCommon) (llvm.Value, error) {
num, _ := constant.Uint64Val(call.Args[0].(*ssa.Const).Value)
num := c.getValue(frame, call.Args[0])
var syscallResult llvm.Value
switch {
case c.GOARCH == "amd64":
@@ -29,12 +28,12 @@ func (c *Compiler) emitSyscall(frame *Frame, call *ssa.CallCommon) (llvm.Value,
// > All system classes enter the kernel via the syscall instruction.
//
// Source: https://opensource.apple.com/source/xnu/xnu-792.13.8/osfmk/mach/i386/syscall_sw.h
num += 0x2000000
num = c.builder.CreateOr(num, llvm.ConstInt(c.uintptrType, 0x2000000, false), "")
}
// Sources:
// https://stackoverflow.com/a/2538212
// https://en.wikibooks.org/wiki/X86_Assembly/Interfacing_with_Linux#syscall
args := []llvm.Value{llvm.ConstInt(c.uintptrType, num, false)}
args := []llvm.Value{num}
argTypes := []llvm.Type{c.uintptrType}
// Constraints will look something like:
// "={rax},0,{rdi},{rsi},{rdx},{r10},{r8},{r9},~{rcx},~{r11}"
@@ -81,7 +80,7 @@ func (c *Compiler) emitSyscall(frame *Frame, call *ssa.CallCommon) (llvm.Value,
args = append(args, llvmValue)
argTypes = append(argTypes, llvmValue.Type())
}
args = append(args, llvm.ConstInt(c.uintptrType, num, false))
args = append(args, num)
argTypes = append(argTypes, c.uintptrType)
constraints += ",{r7}" // syscall number
for i := len(call.Args) - 1; i < 4; i++ {
@@ -111,7 +110,7 @@ func (c *Compiler) emitSyscall(frame *Frame, call *ssa.CallCommon) (llvm.Value,
args = append(args, llvmValue)
argTypes = append(argTypes, llvmValue.Type())
}
args = append(args, llvm.ConstInt(c.uintptrType, num, false))
args = append(args, num)
argTypes = append(argTypes, c.uintptrType)
constraints += ",{x8}" // syscall number
for i := len(call.Args) - 1; i < 8; i++ {
+3
View File
@@ -39,6 +39,9 @@ func (c *Compiler) emitPointerPack(values []llvm.Value) llvm.Value {
// Packed data is bigger than a pointer, so allocate it on the heap.
sizeValue := llvm.ConstInt(c.uintptrType, size, false)
packedHeapAlloc = c.createRuntimeCall("alloc", []llvm.Value{sizeValue}, "")
if c.needsStackObjects() {
c.trackPointer(packedHeapAlloc)
}
packedAlloc = c.builder.CreateBitCast(packedHeapAlloc, llvm.PointerType(packedType, 0), "")
}
// Store all values in the alloca or heap pointer.
+16
View File
@@ -176,6 +176,20 @@ func (fr *frame) evalBasicBlock(bb, incoming llvm.BasicBlock, indent string) (re
continue // special case: bitcast of alloc
}
}
if _, ok := fr.getLocal(operand).(*MapValue); ok {
// Special case for runtime.trackPointer calls.
// Note: this might not be entirely sound in some rare cases
// where the map is stored in a dirty global.
uses := getUses(inst)
if len(uses) == 1 {
use := uses[0]
if !use.IsACallInst().IsNil() && !use.CalledValue().IsAFunction().IsNil() && use.CalledValue().Name() == "runtime.trackPointer" {
continue
}
}
// It is not possible in Go to bitcast a map value to a pointer.
panic("unimplemented: bitcast of map")
}
value := fr.getLocal(operand).(*LocalValue)
fr.locals[inst] = &LocalValue{fr.Eval, fr.builder.CreateBitCast(value.Value(), inst.Type(), "")}
@@ -368,6 +382,8 @@ func (fr *frame) evalBasicBlock(bb, incoming llvm.BasicBlock, indent string) (re
fr.locals[inst] = &LocalValue{fr.Eval, llvm.ConstInt(fr.Mod.Context().Int64Type(), 0, false)}
case callee.Name() == "llvm.dbg.value":
// do nothing
case callee.Name() == "runtime.trackPointer":
// do nothing
case strings.HasPrefix(callee.Name(), "runtime.print") || callee.Name() == "runtime._panic":
// This are all print instructions, which necessarily have side
// effects but no results.
+2
View File
@@ -35,6 +35,8 @@ func (e *Eval) hasSideEffects(fn llvm.Value) *sideEffectResult {
return &sideEffectResult{severity: sideEffectLimited}
case "runtime.interfaceImplements":
return &sideEffectResult{severity: sideEffectNone}
case "runtime.trackPointer":
return &sideEffectResult{severity: sideEffectNone}
case "llvm.dbg.value":
return &sideEffectResult{severity: sideEffectNone}
}
+7 -137
View File
@@ -2,7 +2,6 @@ package ir
import (
"go/ast"
"go/token"
"go/types"
"sort"
"strings"
@@ -23,10 +22,6 @@ type Program struct {
mainPkg *ssa.Package
Functions []*Function
functionMap map[*ssa.Function]*Function
Globals []*Global
globalMap map[*ssa.Global]*Global
comments map[string]*ast.CommentGroup
NamedTypes []*NamedType
}
// Function or method.
@@ -41,28 +36,6 @@ type Function struct {
inline InlineType // go:inline
}
// Global variable, possibly constant.
type Global struct {
*ssa.Global
program *Program
LLVMGlobal llvm.Value
linkName string // go:extern
extern bool // go:extern
}
// Type with a name and possibly methods.
type NamedType struct {
*ssa.Type
LLVMType llvm.Type
}
// Type that is at some point put in an interface.
type TypeWithMethods struct {
t types.Type
Num int
Methods map[string]*types.Selection
}
// Interface type that is at some point used in a type assert (to check whether
// it implements another interface).
type Interface struct {
@@ -83,39 +56,14 @@ const (
// //go:inline). The compiler will be more likely to inline this function,
// but it is not a guarantee.
InlineHint
// Don't inline, just like the GCC noinline attribute. Signalled using
// //go:noinline.
InlineNone
)
// Create and initialize a new *Program from a *ssa.Program.
func NewProgram(lprogram *loader.Program, mainPath string) *Program {
comments := map[string]*ast.CommentGroup{}
for _, pkgInfo := range lprogram.Sorted() {
for _, file := range pkgInfo.Files {
for _, decl := range file.Decls {
switch decl := decl.(type) {
case *ast.GenDecl:
switch decl.Tok {
case token.TYPE, token.VAR:
if len(decl.Specs) != 1 {
continue
}
for _, spec := range decl.Specs {
switch spec := spec.(type) {
case *ast.TypeSpec: // decl.Tok == token.TYPE
id := pkgInfo.Pkg.Path() + "." + spec.Name.Name
comments[id] = decl.Doc
case *ast.ValueSpec: // decl.Tok == token.VAR
for _, name := range spec.Names {
id := pkgInfo.Pkg.Path() + "." + name.Name
comments[id] = decl.Doc
}
}
}
}
}
}
}
}
program := lprogram.LoadSSA()
program.Build()
@@ -187,8 +135,6 @@ func NewProgram(lprogram *loader.Program, mainPath string) *Program {
LoaderProgram: lprogram,
mainPkg: mainPkg,
functionMap: make(map[*ssa.Function]*Function),
globalMap: make(map[*ssa.Global]*Global),
comments: comments,
}
for _, pkg := range packageList {
@@ -213,8 +159,6 @@ func (p *Program) AddPackage(pkg *ssa.Package) {
case *ssa.Function:
p.addFunction(member)
case *ssa.Type:
t := &NamedType{Type: member}
p.NamedTypes = append(p.NamedTypes, t)
methods := getAllMethods(pkg.Prog, member.Type())
if !types.IsInterface(member.Type()) {
// named type
@@ -223,13 +167,7 @@ func (p *Program) AddPackage(pkg *ssa.Package) {
}
}
case *ssa.Global:
g := &Global{program: p, Global: member}
doc := p.comments[g.RelString(nil)]
if doc != nil {
g.parsePragmas(doc)
}
p.Globals = append(p.Globals, g)
p.globalMap[member] = g
// Ignore. Globals are not handled here.
case *ssa.NamedConst:
// Ignore: these are already resolved.
default:
@@ -263,10 +201,6 @@ func (p *Program) GetFunction(ssaFn *ssa.Function) *Function {
return p.functionMap[ssaFn]
}
func (p *Program) GetGlobal(ssaGlobal *ssa.Global) *Global {
return p.globalMap[ssaGlobal]
}
func (p *Program) MainPkg() *ssa.Package {
return p.mainPkg
}
@@ -297,6 +231,8 @@ func (f *Function) parsePragmas() {
f.exported = true
case "//go:inline":
f.inline = InlineHint
case "//go:noinline":
f.inline = InlineNone
case "//go:interrupt":
if len(parts) != 2 {
continue
@@ -389,72 +325,6 @@ func (f *Function) CName() string {
return ""
}
// Parse //go: pragma comments from the source.
func (g *Global) parsePragmas(doc *ast.CommentGroup) {
for _, comment := range doc.List {
if !strings.HasPrefix(comment.Text, "//go:") {
continue
}
parts := strings.Fields(comment.Text)
switch parts[0] {
case "//go:extern":
g.extern = true
if len(parts) == 2 {
g.linkName = parts[1]
}
}
}
}
// Return the link name for this global.
func (g *Global) LinkName() string {
if g.linkName != "" {
return g.linkName
}
if name := g.CName(); name != "" {
return name
}
return g.RelString(nil)
}
func (g *Global) IsExtern() bool {
return g.extern || g.CName() != ""
}
// Return the name of the C global if this is a CGo wrapper. Otherwise, return a
// zero-length string.
func (g *Global) CName() string {
name := g.Name()
if strings.HasPrefix(name, "C.") {
// created by ../loader/cgo.go
return name[2:]
}
return ""
}
// Return true if this named type is annotated with the //go:volatile pragma,
// for volatile loads and stores.
func (p *Program) IsVolatile(t types.Type) bool {
if t, ok := t.(*types.Named); !ok {
return false
} else {
if t.Obj().Pkg() == nil {
return false
}
id := t.Obj().Pkg().Path() + "." + t.Obj().Name()
doc := p.comments[id]
if doc == nil {
return false
}
for _, line := range doc.List {
if strings.TrimSpace(line.Text) == "//go:volatile" {
return true
}
}
return false
}
}
// Get all methods of a type.
func getAllMethods(prog *ssa.Program, typ types.Type) []*types.Selection {
ms := prog.MethodSets.MethodSet(typ)
+1
View File
@@ -63,6 +63,7 @@ func Link(linker string, flags ...string) error {
cmd := exec.Command(linker, flags...)
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
cmd.Dir = sourceDir()
return cmd.Run()
}
}
+1
View File
@@ -20,5 +20,6 @@ func Link(linker string, flags ...string) error {
cmd := exec.Command(linker, flags...)
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
cmd.Dir = sourceDir()
return cmd.Run()
}
+144 -29
View File
@@ -1,6 +1,7 @@
package loader
import (
"bytes"
"errors"
"go/ast"
"go/build"
@@ -10,22 +11,26 @@ import (
"os"
"path/filepath"
"sort"
"strings"
"text/template"
"github.com/tinygo-org/tinygo/cgo"
)
// Program holds all packages and some metadata about the program as a whole.
type Program struct {
Build *build.Context
OverlayBuild *build.Context
ShouldOverlay func(path string) bool
Packages map[string]*Package
sorted []*Package
fset *token.FileSet
TypeChecker types.Config
Dir string // current working directory (for error reporting)
TINYGOROOT string // root of the TinyGo installation or root of the source code
CFlags []string
mainPkg string
Build *build.Context
OverlayBuild *build.Context
OverlayPath func(path string) string
Packages map[string]*Package
sorted []*Package
fset *token.FileSet
TypeChecker types.Config
Dir string // current working directory (for error reporting)
TINYGOROOT string // root of the TinyGo installation or root of the source code
CFlags []string
ClangHeaders string
}
// Package holds a loaded package, its imports, and its parsed files.
@@ -48,8 +53,9 @@ func (p *Program) Import(path, srcDir string) (*Package, error) {
// Load this package.
ctx := p.Build
if p.ShouldOverlay(path) {
if newPath := p.OverlayPath(path); newPath != "" {
ctx = p.OverlayBuild
path = newPath
}
buildPkg, err := ctx.Import(path, srcDir, build.ImportComment)
if err != nil {
@@ -62,6 +68,11 @@ func (p *Program) Import(path, srcDir string) (*Package, error) {
p.sorted = nil // invalidate the sorted order of packages
pkg := p.newPackage(buildPkg)
p.Packages[buildPkg.ImportPath] = pkg
if p.mainPkg == "" {
p.mainPkg = buildPkg.ImportPath
}
return pkg, nil
}
@@ -91,6 +102,11 @@ func (p *Program) ImportFile(path string) (*Package, error) {
p.sorted = nil // invalidate the sorted order of packages
pkg := p.newPackage(buildPkg)
p.Packages[buildPkg.ImportPath] = pkg
if p.mainPkg == "" {
p.mainPkg = buildPkg.ImportPath
}
return pkg, nil
}
@@ -169,10 +185,12 @@ func (p *Program) sort() {
// The returned error may be an Errors error, which contains a list of errors.
//
// Idempotent.
func (p *Program) Parse() error {
func (p *Program) Parse(compileTestBinary bool) error {
includeTests := compileTestBinary
// Load all imports
for _, pkg := range p.Sorted() {
err := pkg.importRecursively()
err := pkg.importRecursively(includeTests)
if err != nil {
if err, ok := err.(*ImportCycleError); ok {
if pkg.ImportPath != err.Packages[0] {
@@ -185,7 +203,14 @@ func (p *Program) Parse() error {
// Parse all packages.
for _, pkg := range p.Sorted() {
err := pkg.Parse()
err := pkg.Parse(includeTests)
if err != nil {
return err
}
}
if compileTestBinary {
err := p.SwapTestMain()
if err != nil {
return err
}
@@ -202,6 +227,83 @@ func (p *Program) Parse() error {
return nil
}
func (p *Program) SwapTestMain() error {
var tests []string
isTestFunc := func(f *ast.FuncDecl) bool {
// TODO: improve signature check
if strings.HasPrefix(f.Name.Name, "Test") && f.Name.Name != "TestMain" {
return true
}
return false
}
mainPkg := p.Packages[p.mainPkg]
for _, f := range mainPkg.Files {
for i, d := range f.Decls {
switch v := d.(type) {
case *ast.FuncDecl:
if isTestFunc(v) {
tests = append(tests, v.Name.Name)
}
if v.Name.Name == "main" {
// Remove main
if len(f.Decls) == 1 {
f.Decls = make([]ast.Decl, 0)
} else {
f.Decls[i] = f.Decls[len(f.Decls)-1]
f.Decls = f.Decls[:len(f.Decls)-1]
}
}
}
}
}
// TODO: Check if they defined a TestMain and call it instead of testing.TestMain
const mainBody = `package main
import (
"testing"
)
func main () {
m := &testing.M{
Tests: []testing.TestToCall{
{{range .TestFunctions}}
{Name: "{{.}}", Func: {{.}}},
{{end}}
},
}
testing.TestMain(m)
}
`
tmpl := template.Must(template.New("testmain").Parse(mainBody))
b := bytes.Buffer{}
tmplData := struct {
TestFunctions []string
}{
TestFunctions: tests,
}
err := tmpl.Execute(&b, tmplData)
if err != nil {
return err
}
path := filepath.Join(p.mainPkg, "$testmain.go")
if p.fset == nil {
p.fset = token.NewFileSet()
}
newMain, err := parser.ParseFile(p.fset, path, b.Bytes(), parser.AllErrors)
if err != nil {
return err
}
mainPkg.Files = append(mainPkg.Files, newMain)
return nil
}
// parseFile is a wrapper around parser.ParseFile.
func (p *Program) parseFile(path string, mode parser.Mode) (*ast.File, error) {
if p.fset == nil {
@@ -226,7 +328,7 @@ func (p *Program) parseFile(path string, mode parser.Mode) (*ast.File, error) {
// Parse parses and typechecks this package.
//
// Idempotent.
func (p *Package) Parse() error {
func (p *Package) Parse(includeTests bool) error {
if len(p.Files) != 0 {
return nil
}
@@ -240,7 +342,7 @@ func (p *Package) Parse() error {
return nil
}
files, err := p.parseFiles()
files, err := p.parseFiles(includeTests)
if err != nil {
return err
}
@@ -279,11 +381,21 @@ func (p *Package) Check() error {
}
// parseFiles parses the loaded list of files and returns this list.
func (p *Package) parseFiles() ([]*ast.File, error) {
func (p *Package) parseFiles(includeTests bool) ([]*ast.File, error) {
// TODO: do this concurrently.
var files []*ast.File
var fileErrs []error
for _, file := range p.GoFiles {
var gofiles []string
if includeTests {
gofiles = make([]string, 0, len(p.GoFiles)+len(p.TestGoFiles))
gofiles = append(gofiles, p.GoFiles...)
gofiles = append(gofiles, p.TestGoFiles...)
} else {
gofiles = p.GoFiles
}
for _, file := range gofiles {
f, err := p.parseFile(filepath.Join(p.Package.Dir, file), parser.ParseComments)
if err != nil {
fileErrs = append(fileErrs, err)
@@ -305,15 +417,11 @@ func (p *Package) parseFiles() ([]*ast.File, error) {
files = append(files, f)
}
if len(p.CgoFiles) != 0 {
clangIncludes := ""
if _, err := os.Stat(filepath.Join(p.TINYGOROOT, "llvm", "tools", "clang", "lib", "Headers")); !os.IsNotExist(err) {
// Running from the source directory.
clangIncludes = filepath.Join(p.TINYGOROOT, "llvm", "tools", "clang", "lib", "Headers")
} else {
// Running from the installation directory.
clangIncludes = filepath.Join(p.TINYGOROOT, "lib", "clang", "include")
cflags := append(p.CFlags, "-I"+p.Package.Dir)
if p.ClangHeaders != "" {
cflags = append(cflags, "-I"+p.ClangHeaders)
}
generated, errs := cgo.Process(files, p.Program.Dir, p.fset, append(p.CFlags, "-I"+p.Package.Dir, "-I"+clangIncludes))
generated, errs := cgo.Process(files, p.Program.Dir, p.fset, cflags)
if errs != nil {
fileErrs = append(fileErrs, errs...)
}
@@ -322,6 +430,7 @@ func (p *Package) parseFiles() ([]*ast.File, error) {
if len(fileErrs) != 0 {
return nil, Errors{p, fileErrs}
}
return files, nil
}
@@ -342,9 +451,15 @@ func (p *Package) Import(to string) (*types.Package, error) {
// importRecursively() on the imported packages as well.
//
// Idempotent.
func (p *Package) importRecursively() error {
func (p *Package) importRecursively(includeTests bool) error {
p.Importing = true
for _, to := range p.Package.Imports {
imports := p.Package.Imports
if includeTests {
imports = append(imports, p.Package.TestImports...)
}
for _, to := range imports {
if to == "C" {
// Do CGo processing in a later stage.
continue
@@ -362,7 +477,7 @@ func (p *Package) importRecursively() error {
if importedPkg.Importing {
return &ImportCycleError{[]string{p.ImportPath, importedPkg.ImportPath}, p.ImportPos[to]}
}
err = importedPkg.importRecursively()
err = importedPkg.importRecursively(false)
if err != nil {
if err, ok := err.(*ImportCycleError); ok {
err.Packages = append([]string{p.ImportPath}, err.Packages...)
+111 -20
View File
@@ -53,7 +53,10 @@ type BuildConfig struct {
printSizes string
cFlags []string
ldFlags []string
tags string
wasmAbi string
heapSize int64
testConfig compiler.TestConfig
}
// Helper function for Compiler object.
@@ -81,16 +84,19 @@ func Compile(pkgName, outpath string, spec *TargetSpec, config *BuildConfig, act
return errors.New("cannot locate $GOROOT, please set it manually")
}
tags := spec.BuildTags
major, minor := getGorootVersion(goroot)
major, minor, err := getGorootVersion(goroot)
if err != nil {
return fmt.Errorf("could not read version from GOROOT (%v): %v", goroot, err)
}
if major != 1 {
if major == 0 {
return errors.New("could not read version from GOROOT: " + goroot)
}
return fmt.Errorf("expected major version 1, got go%d.%d", major, minor)
}
for i := 1; i <= minor; i++ {
tags = append(tags, fmt.Sprintf("go1.%d", i))
}
if extraTags := strings.Fields(config.tags); len(extraTags) != 0 {
tags = append(tags, extraTags...)
}
compilerConfig := compiler.Config{
Triple: spec.Triple,
CPU: spec.CPU,
@@ -101,12 +107,14 @@ func Compile(pkgName, outpath string, spec *TargetSpec, config *BuildConfig, act
PanicStrategy: config.panicStrategy,
CFlags: cflags,
LDFlags: ldflags,
ClangHeaders: getClangHeaderPath(root),
Debug: config.debug,
DumpSSA: config.dumpSSA,
TINYGOROOT: root,
GOROOT: goroot,
GOPATH: getGopath(),
BuildTags: tags,
TestConfig: config.testConfig,
}
c, err := compiler.NewCompiler(pkgName, compilerConfig)
if err != nil {
@@ -233,10 +241,16 @@ func Compile(pkgName, outpath string, spec *TargetSpec, config *BuildConfig, act
// Prepare link command.
executable := filepath.Join(dir, "main")
tmppath := executable // final file
ldflags := append(ldflags, "-o", executable, objfile, "-L", root)
ldflags = append(ldflags, "-o", executable, objfile, "-L", root)
if spec.RTLib == "compiler-rt" {
ldflags = append(ldflags, librt)
}
if spec.GOARCH == "wasm" {
// Round heap size to next multiple of 65536 (the WebAssembly page
// size).
heapSize := (config.heapSize + (65536 - 1)) &^ (65536 - 1)
ldflags = append(ldflags, "--initial-memory="+strconv.FormatInt(heapSize, 10))
}
// Compile extra files.
for i, path := range spec.ExtraFiles {
@@ -348,6 +362,33 @@ func Build(pkgName, outpath, target string, config *BuildConfig) error {
})
}
func Test(pkgName, target string, config *BuildConfig) error {
spec, err := LoadTarget(target)
if err != nil {
return err
}
spec.BuildTags = append(spec.BuildTags, "test")
config.testConfig.CompileTestBinary = true
return Compile(pkgName, ".elf", spec, config, func(tmppath string) error {
cmd := exec.Command(tmppath)
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
err := cmd.Run()
if err != nil {
// Propagate the exit code
if err, ok := err.(*exec.ExitError); ok {
if status, ok := err.Sys().(syscall.WaitStatus); ok {
os.Exit(status.ExitStatus())
}
os.Exit(1)
}
return &commandError{"failed to run compiled binary", tmppath, err}
}
return nil
})
}
func Flash(pkgName, target, port string, config *BuildConfig) error {
spec, err := LoadTarget(target)
if err != nil {
@@ -506,6 +547,30 @@ func Run(pkgName, target string, config *BuildConfig) error {
})
}
// parseSize converts a human-readable size (with k/m/g suffix) into a plain
// number.
func parseSize(s string) (int64, error) {
s = strings.ToLower(strings.TrimSpace(s))
if len(s) == 0 {
return 0, errors.New("no size provided")
}
multiply := int64(1)
switch s[len(s)-1] {
case 'k':
multiply = 1 << 10
case 'm':
multiply = 1 << 20
case 'g':
multiply = 1 << 30
}
if multiply != 1 {
s = s[:len(s)-1]
}
n, err := strconv.ParseInt(s, 0, 64)
n *= multiply
return n, err
}
func usage() {
fmt.Fprintln(os.Stderr, "TinyGo is a Go compiler for small places.")
fmt.Fprintln(os.Stderr, "version:", version)
@@ -513,6 +578,7 @@ func usage() {
fmt.Fprintln(os.Stderr, "\ncommands:")
fmt.Fprintln(os.Stderr, " build: compile packages and dependencies")
fmt.Fprintln(os.Stderr, " run: compile and run immediately")
fmt.Fprintln(os.Stderr, " test: test packages")
fmt.Fprintln(os.Stderr, " flash: compile and flash to the device")
fmt.Fprintln(os.Stderr, " gdb: run/flash and immediately enter GDB")
fmt.Fprintln(os.Stderr, " clean: empty cache directory ("+cacheDir()+")")
@@ -523,23 +589,24 @@ func usage() {
func handleCompilerError(err error) {
if err != nil {
if errUnsupported, ok := err.(*interp.Unsupported); ok {
switch err := err.(type) {
case *interp.Unsupported:
// hit an unknown/unsupported instruction
fmt.Fprintln(os.Stderr, "unsupported instruction during init evaluation:")
errUnsupported.Inst.Dump()
err.Inst.Dump()
fmt.Fprintln(os.Stderr)
} else if errCompiler, ok := err.(types.Error); ok {
fmt.Fprintln(os.Stderr, errCompiler)
} else if errLoader, ok := err.(loader.Errors); ok {
fmt.Fprintln(os.Stderr, "#", errLoader.Pkg.ImportPath)
for _, err := range errLoader.Errs {
case types.Error:
fmt.Fprintln(os.Stderr, err)
case loader.Errors:
fmt.Fprintln(os.Stderr, "#", err.Pkg.ImportPath)
for _, err := range err.Errs {
fmt.Fprintln(os.Stderr, err)
}
} else if errMulti, ok := err.(*multiError); ok {
for _, err := range errMulti.Errs {
case *multiError:
for _, err := range err.Errs {
fmt.Fprintln(os.Stderr, err)
}
} else {
default:
fmt.Fprintln(os.Stderr, "error:", err)
}
os.Exit(1)
@@ -549,11 +616,12 @@ func handleCompilerError(err error) {
func main() {
outpath := flag.String("o", "", "output filename")
opt := flag.String("opt", "z", "optimization level: 0, 1, 2, s, z")
gc := flag.String("gc", "", "garbage collector to use (none, dumb, marksweep)")
gc := flag.String("gc", "", "garbage collector to use (none, leaking, conservative)")
panicStrategy := flag.String("panic", "print", "panic strategy (abort, trap)")
printIR := flag.Bool("printir", false, "print LLVM IR")
dumpSSA := flag.Bool("dumpssa", false, "dump internal Go SSA")
target := flag.String("target", "", "LLVM target")
tags := flag.String("tags", "", "a space-separated list of extra build tags")
target := flag.String("target", "", "LLVM target | .json file with TargetSpec")
printSize := flag.String("size", "", "print sizes (none, short, full)")
nodebug := flag.Bool("no-debug", false, "disable DWARF debug symbol generation")
ocdOutput := flag.Bool("ocd-output", false, "print OCD daemon output during debug")
@@ -561,6 +629,7 @@ func main() {
cFlags := flag.String("cflags", "", "additional cflags for compiler")
ldFlags := flag.String("ldflags", "", "additional ldflags for linker")
wasmAbi := flag.String("wasm-abi", "js", "WebAssembly ABI conventions: js (no i64 params) or generic")
heapSize := flag.String("heap-size", "1M", "default heap size in bytes (only supported by WebAssembly)")
if len(os.Args) < 2 {
fmt.Fprintln(os.Stderr, "No command-line arguments supplied.")
@@ -578,6 +647,7 @@ func main() {
dumpSSA: *dumpSSA,
debug: !*nodebug,
printSizes: *printSize,
tags: *tags,
wasmAbi: *wasmAbi,
}
@@ -595,6 +665,13 @@ func main() {
os.Exit(1)
}
var err error
if config.heapSize, err = parseSize(*heapSize); err != nil {
fmt.Fprintln(os.Stderr, "Could not read heap size:", *heapSize)
usage()
os.Exit(1)
}
os.Setenv("CC", "clang -target="+*target)
switch command {
@@ -604,8 +681,11 @@ func main() {
usage()
os.Exit(1)
}
if flag.NArg() != 1 {
fmt.Fprintln(os.Stderr, "No package specified.")
pkgName := "."
if flag.NArg() == 1 {
pkgName = flag.Arg(0)
} else if flag.NArg() > 1 {
fmt.Fprintln(os.Stderr, "build only accepts a single positional argument: package name, but multiple were specified")
usage()
os.Exit(1)
}
@@ -613,7 +693,7 @@ func main() {
if target == "" && filepath.Ext(*outpath) == ".wasm" {
target = "wasm"
}
err := Build(flag.Arg(0), *outpath, target, config)
err := Build(pkgName, *outpath, target, config)
handleCompilerError(err)
case "build-builtins":
// Note: this command is only meant to be used while making a release!
@@ -655,6 +735,17 @@ func main() {
}
err := Run(flag.Arg(0), *target, config)
handleCompilerError(err)
case "test":
pkgName := "."
if flag.NArg() == 1 {
pkgName = flag.Arg(0)
} else if flag.NArg() > 1 {
fmt.Fprintln(os.Stderr, "test only accepts a single positional argument: package name, but multiple were specified")
usage()
os.Exit(1)
}
err := Test(pkgName, *target, config)
handleCompilerError(err)
case "clean":
// remove cache directory
dir := cacheDir()
+9 -11
View File
@@ -30,6 +30,7 @@
package arm
import (
"runtime/volatile"
"unsafe"
)
@@ -69,9 +70,6 @@ func SVCall3(num uintptr, a1, a2, a3 interface{}) uintptr
// Run the following system call (SVCall) with 4 arguments.
func SVCall4(num uintptr, a1, a2, a3, a4 interface{}) uintptr
//go:volatile
type RegValue uint32
const (
SCS_BASE = 0xE000E000
NVIC_BASE = SCS_BASE + 0x0100
@@ -82,24 +80,24 @@ const (
// Source:
// http://infocenter.arm.com/help/index.jsp?topic=/com.arm.doc.dui0553a/CIHIGCIF.html
type NVIC_Type struct {
ISER [8]RegValue // Interrupt Set-enable Registers
ISER [8]volatile.Register32 // Interrupt Set-enable Registers
_ [24]uint32
ICER [8]RegValue // Interrupt Clear-enable Registers
ICER [8]volatile.Register32 // Interrupt Clear-enable Registers
_ [24]uint32
ISPR [8]RegValue // Interrupt Set-pending Registers
ISPR [8]volatile.Register32 // Interrupt Set-pending Registers
_ [24]uint32
ICPR [8]RegValue // Interrupt Clear-pending Registers
ICPR [8]volatile.Register32 // Interrupt Clear-pending Registers
_ [24]uint32
IABR [8]RegValue // Interrupt Active Bit Registers
IABR [8]volatile.Register32 // Interrupt Active Bit Registers
_ [56]uint32
IPR [60]RegValue // Interrupt Priority Registers
IPR [60]volatile.Register32 // Interrupt Priority Registers
}
var NVIC = (*NVIC_Type)(unsafe.Pointer(uintptr(NVIC_BASE)))
// Enable the given interrupt number.
func EnableIRQ(irq uint32) {
NVIC.ISER[irq>>5] = 1 << (irq & 0x1F)
NVIC.ISER[irq>>5].Set(1 << (irq & 0x1F))
}
// Set the priority of the given interrupt number.
@@ -116,7 +114,7 @@ func SetPriority(irq uint32, priority uint32) {
regpos := irq % 4
mask := uint32(0xff) << (regpos * 8) // bits to clear
priority = priority << (regpos * 8) // bits to set
NVIC.IPR[regnum] = RegValue((uint32(NVIC.IPR[regnum]) &^ mask) | priority)
NVIC.IPR[regnum].Set((uint32(NVIC.IPR[regnum].Get()) &^ mask) | priority)
}
// DisableInterrupts disables all interrupts, and returns the old state.
+10
View File
@@ -0,0 +1,10 @@
package riscv
// Run the given assembly code. The code will be marked as having side effects,
// as it doesn't produce output and thus would normally be eliminated by the
// optimizer.
func Asm(asm string)
// ReadRegister returns the contents of the specified register. The register
// must be a processor register, reachable with the "mov" instruction.
func ReadRegister(name string) uintptr
+13
View File
@@ -0,0 +1,13 @@
.section .init
.global _start
.type _start,@function
_start:
// Workaround for missing support of the la pseudo-instruction in Clang 8:
// https://reviews.llvm.org/D55325
lui sp, %hi(_stack_top)
addi sp, sp, %lo(_stack_top)
// see https://gnu-mcu-eclipse.github.io/arch/riscv/programmer/#the-gp-global-pointer-register
lui gp, %hi(__global_pointer$)
addi gp, gp, %lo(__global_pointer$)
call main
+1 -1
View File
@@ -1,4 +1,4 @@
// +build avr,arduino
// +build arduino
package machine
+138
View File
@@ -0,0 +1,138 @@
// +build sam,atsamd21,arduino_nano33
// This contains the pin mappings for the Arduino Nano33 IoT board.
//
// For more information, see: https://store.arduino.cc/nano-33-iot
//
package machine
import "device/sam"
// GPIO Pins
const (
RX0 Pin = PB23 // UART2 RX
TX1 Pin = PB22 // UART2 TX
D2 Pin = PB10 // PWM available
D3 Pin = PB11 // PWM available
D4 Pin = PA07
D5 Pin = PA05 // PWM available
D6 Pin = PA04 // PWM available
D7 Pin = PA06
D8 Pin = PA18
D9 Pin = PA20 // PWM available
D10 Pin = PA21 // PWM available
D11 Pin = PA16 // PWM available
D12 Pin = PA19 // PWM available
D13 Pin = PA17
)
// Analog pins
const (
A0 Pin = PA02 // ADC/AIN[0]
A1 Pin = PB02 // ADC/AIN[10]
A2 Pin = PA11 // ADC/AIN[19]
A3 Pin = PA10 // ADC/AIN[18]
A4 Pin = PB08 // ADC/AIN[2], SCL: SERCOM2/PAD[1]
A5 Pin = PB09 // ADC/AIN[3], SDA: SERCOM2/PAD[1]
A6 Pin = PA09 // ADC/AIN[17]
A7 Pin = PB03 // ADC/AIN[11]
)
const (
LED = D13
)
// NINA-W102 Pins
const (
NINA_MOSI Pin = PA12
NINA_MISO Pin = PA13
NINA_CS Pin = PA14
NINA_SCK Pin = PA15
NINA_GPIO0 Pin = PA27
NINA_RESETN Pin = PA08
NINA_ACK Pin = PA28
)
// UART0 aka USBCDC pins
const (
USBCDC_DM_PIN Pin = PA24
USBCDC_DP_PIN Pin = PA25
)
// UART1 on the Arduino Nano 33 connects to the onboard NINA-W102 WiFi chip.
var (
UART1 = UART{Bus: sam.SERCOM5_USART,
Buffer: NewRingBuffer(),
Mode: PinSERCOMAlt,
IRQVal: sam.IRQ_SERCOM5,
}
)
// UART1 pins
const (
UART_TX_PIN Pin = PA22
UART_RX_PIN Pin = PA23
)
//go:export SERCOM5_IRQHandler
func handleUART1() {
defaultUART1Handler()
}
// UART2 on the Arduino Nano 33 connects to the normal TX/RX pins.
var (
UART2 = UART{Bus: sam.SERCOM3_USART,
Buffer: NewRingBuffer(),
Mode: PinSERCOMAlt,
IRQVal: sam.IRQ_SERCOM3,
}
)
//go:export SERCOM3_IRQHandler
func handleUART2() {
// should reset IRQ
UART2.Receive(byte((UART2.Bus.DATA.Get() & 0xFF)))
UART2.Bus.INTFLAG.SetBits(sam.SERCOM_USART_INTFLAG_RXC)
}
// I2C pins
const (
SDA_PIN Pin = A4 // SDA: SERCOM4/PAD[1]
SCL_PIN Pin = A5 // SCL: SERCOM4/PAD[1]
)
// I2C on the Arduino Nano 33.
var (
I2C0 = I2C{Bus: sam.SERCOM4_I2CM,
SDA: SDA_PIN,
SCL: SCL_PIN,
PinMode: PinSERCOMAlt}
)
// SPI pins
const (
SPI0_SCK_PIN Pin = PB11 // SCK: SERCOM4/PAD[3]
SPI0_MOSI_PIN Pin = PB10 // MOSI: SERCOM4/PAD[2]
SPI0_MISO_PIN Pin = PA12 // MISO: SERCOM4/PAD[0]
)
// SPI on the Arduino Nano 33.
var (
SPI0 = SPI{Bus: sam.SERCOM1_SPI}
)
// I2S pins
const (
I2S_SCK_PIN Pin = PA10
I2S_SD_PIN Pin = PA08
I2S_WS_PIN = NoPin // TODO: figure out what this is on Arduino Nano 33.
)
// I2S on the Arduino Nano 33.
var (
I2S0 = I2S{Bus: sam.I2S}
)
+1 -1
View File
@@ -1,4 +1,4 @@
// +build stm32,bluepill
// +build bluepill
package machine
+14
View File
@@ -65,6 +65,20 @@ const (
UART_RX_PIN = PB09 // PORTB
)
// UART1 on the Circuit Playground Express.
var (
UART1 = UART{Bus: sam.SERCOM1_USART,
Buffer: NewRingBuffer(),
Mode: PinSERCOM,
IRQVal: sam.IRQ_SERCOM1,
}
)
//go:export SERCOM1_IRQHandler
func handleUART1() {
defaultUART1Handler()
}
// I2C pins
const (
SDA_PIN = PB02 // I2C0 external
+1 -1
View File
@@ -1,4 +1,4 @@
// +build attiny85,digispark
// +build digispark
package machine
+38
View File
@@ -0,0 +1,38 @@
// +build hifive1b
package machine
const (
P00 Pin = 0
P01 Pin = 1
P02 Pin = 2
P03 Pin = 3
P04 Pin = 4
P05 Pin = 5
P06 Pin = 6
P07 Pin = 7
P08 Pin = 8
P09 Pin = 9
P10 Pin = 10
P11 Pin = 11
P12 Pin = 12
P13 Pin = 13
P14 Pin = 14
P15 Pin = 15
P16 Pin = 16
P17 Pin = 17
P18 Pin = 18
P19 Pin = 19
P20 Pin = 20
P21 Pin = 21
P22 Pin = 22
P23 Pin = 23
P24 Pin = 24
P25 Pin = 25
P26 Pin = 26
P27 Pin = 27
P28 Pin = 28
P29 Pin = 29
P30 Pin = 30
P31 Pin = 31
)
+14
View File
@@ -48,6 +48,20 @@ const (
UART_RX_PIN = D11
)
// UART1 on the Feather M0.
var (
UART1 = UART{Bus: sam.SERCOM1_USART,
Buffer: NewRingBuffer(),
Mode: PinSERCOM,
IRQVal: sam.IRQ_SERCOM1,
}
)
//go:export SERCOM1_IRQHandler
func handleUART1() {
defaultUART1Handler()
}
// I2C pins
const (
SDA_PIN = PA22 // SDA: SERCOM3/PAD[0]
+19
View File
@@ -0,0 +1,19 @@
// +build hifive1b
package machine
const (
LED = LED1
LED1 = LED_RED
LED2 = LED_GREEN
LED3 = LED_BLUE
LED_RED = P22
LED_GREEN = P19
LED_BLUE = P21
)
const (
// TODO: figure out the pin numbers for these.
UART_TX_PIN = NoPin
UART_RX_PIN = NoPin
)
+14
View File
@@ -48,6 +48,20 @@ const (
UART_RX_PIN = D11
)
// UART1 on the ItsyBitsy M0.
var (
UART1 = UART{Bus: sam.SERCOM1_USART,
Buffer: NewRingBuffer(),
Mode: PinSERCOM,
IRQVal: sam.IRQ_SERCOM1,
}
)
//go:export SERCOM1_IRQHandler
func handleUART1() {
defaultUART1Handler()
}
// I2C pins
const (
SDA_PIN = PA22 // SDA: SERCOM3/PAD[0]
+1 -1
View File
@@ -1,4 +1,4 @@
// +build nrf51,microbit
// +build microbit
package machine
+1 -1
View File
@@ -1,4 +1,4 @@
// +build nrf51,pca10031
// +build pca10031
// pca10031 is a nrf51 based dongle, intended for use in wireless applications.
//
+1 -1
View File
@@ -1,4 +1,4 @@
// +build nrf,pca10040
// +build pca10040
package machine
+29 -10
View File
@@ -4,17 +4,24 @@ package machine
const HasLowFrequencyCrystal = true
// LEDs on the reel board
// Pins on the reel board
const (
LED Pin = LED1
LED1 Pin = LED_YELLOW
LED2 Pin = LED_RED
LED3 Pin = LED_GREEN
LED4 Pin = LED_BLUE
LED_RED Pin = 11
LED_GREEN Pin = 12
LED_BLUE Pin = 41
LED_YELLOW Pin = 13
LED Pin = LED1
LED1 Pin = LED_YELLOW
LED2 Pin = LED_RED
LED3 Pin = LED_GREEN
LED4 Pin = LED_BLUE
LED_RED Pin = 11
LED_GREEN Pin = 12
LED_BLUE Pin = 41
LED_YELLOW Pin = 13
EPD_BUSY_PIN Pin = 14
EPD_RESET_PIN Pin = 15
EPD_DC_PIN Pin = 16
EPD_CS_PIN Pin = 17
EPD_SCK_PIN Pin = 19
EPD_MOSI_PIN Pin = 20
POWER_SUPPLY_PIN Pin = 32
)
// User "a" button on the reel board
@@ -40,3 +47,15 @@ const (
SPI0_MOSI_PIN Pin = 45
SPI0_MISO_PIN Pin = 46
)
// PowerSupplyActive enables the supply voltages for nRF52840 and peripherals (true) or only for nRF52840 (false)
// This controls the TPS610981 boost converter. You must turn the power supply active in order to use the EPD and
// other onboard peripherals.
func PowerSupplyActive(active bool) {
POWER_SUPPLY_PIN.Configure(PinConfig{Mode: PinOutput})
if active {
POWER_SUPPLY_PIN.High()
} else {
POWER_SUPPLY_PIN.Low()
}
}
+16
View File
@@ -0,0 +1,16 @@
// +build bluepill stm32f4disco
package machine
// Peripheral abstraction layer for the stm32.
const (
portA Pin = iota * 16
portB
portC
portD
portE
portF
portG
portH
)
+1 -1
View File
@@ -1,4 +1,4 @@
// +build stm32,stm32f4disco
// +build stm32f4disco
package machine
+14
View File
@@ -39,6 +39,20 @@ const (
UART_RX_PIN = D3
)
// UART1 on the Trinket M0.
var (
UART1 = UART{Bus: sam.SERCOM1_USART,
Buffer: NewRingBuffer(),
Mode: PinSERCOM,
IRQVal: sam.IRQ_SERCOM1,
}
)
//go:export SERCOM1_IRQHandler
func handleUART1() {
defaultUART1Handler()
}
// SPI pins
const (
SPI0_SCK_PIN = D3
+12 -11
View File
@@ -1,9 +1,10 @@
package machine
const bufferSize = 128
import (
"runtime/volatile"
)
//go:volatile
type volatileByte byte
const bufferSize = 128
// RingBuffer is ring buffer implementation inspired by post at
// https://www.embeddedrelated.com/showthread/comp.arch.embedded/77084-1.php
@@ -12,9 +13,9 @@ type volatileByte byte
// members of a struct are not compiled correctly by TinyGo.
// See https://github.com/tinygo-org/tinygo/issues/151 for details.
type RingBuffer struct {
rxbuffer [bufferSize]volatileByte
head volatileByte
tail volatileByte
rxbuffer [bufferSize]volatile.Register8
head volatile.Register8
tail volatile.Register8
}
// NewRingBuffer returns a new ring buffer.
@@ -24,15 +25,15 @@ func NewRingBuffer() *RingBuffer {
// Used returns how many bytes in buffer have been used.
func (rb *RingBuffer) Used() uint8 {
return uint8(rb.head - rb.tail)
return uint8(rb.head.Get() - rb.tail.Get())
}
// Put stores a byte in the buffer. If the buffer is already
// full, the method will return false.
func (rb *RingBuffer) Put(val byte) bool {
if rb.Used() != bufferSize {
rb.head++
rb.rxbuffer[rb.head%bufferSize] = volatileByte(val)
rb.head.Set(rb.head.Get() + 1)
rb.rxbuffer[rb.head.Get()%bufferSize].Set(val)
return true
}
return false
@@ -42,8 +43,8 @@ func (rb *RingBuffer) Put(val byte) bool {
// the method will return a false as the second value.
func (rb *RingBuffer) Get() (byte, bool) {
if rb.Used() != 0 {
rb.tail++
return byte(rb.rxbuffer[rb.tail%bufferSize]), true
rb.tail.Set(rb.tail.Get() + 1)
return rb.rxbuffer[rb.tail.Get()%bufferSize].Get(), true
}
return 0, false
}
+1 -1
View File
@@ -1,4 +1,4 @@
// +build avr nrf sam stm32f103xx
// +build !stm32f4disco,!hifive1b
package machine
+2 -1
View File
@@ -4,6 +4,7 @@ package machine
import (
"device/avr"
"runtime/volatile"
)
// Configure sets the pin to input or output.
@@ -34,7 +35,7 @@ func (p Pin) Get() bool {
}
}
func (p Pin) getPortMask() (*avr.Register8, uint8) {
func (p Pin) getPortMask() (*volatile.Register8, uint8) {
if p < 8 {
return avr.PORTD, 1 << uint8(p)
} else {
+69 -31
View File
@@ -250,14 +250,13 @@ func waitADCSync() {
type UART struct {
Buffer *RingBuffer
Bus *sam.SERCOM_USART_Type
Mode PinMode
IRQVal uint32
}
var (
// UART0 is actually a USB CDC interface.
UART0 = USBCDC{Buffer: NewRingBuffer()}
// The first hardware serial port on the SAMD21. Uses the SERCOM0 interface.
UART1 = UART{Bus: sam.SERCOM1_USART, Buffer: NewRingBuffer()}
)
const (
@@ -300,6 +299,8 @@ func (uart UART) Configure(config UARTConfig) {
txpad = sercomTXPad2
case PA16:
txpad = sercomTXPad0
case PA22:
txpad = sercomTXPad0
default:
panic("Invalid TX pin for UART")
}
@@ -315,13 +316,15 @@ func (uart UART) Configure(config UARTConfig) {
rxpad = sercomRXPad3
case PA17:
rxpad = sercomRXPad1
case PA23:
rxpad = sercomRXPad1
default:
panic("Invalid RX pin for UART")
}
// configure pins
config.TX.Configure(PinConfig{Mode: PinSERCOM})
config.RX.Configure(PinConfig{Mode: PinSERCOM})
config.TX.Configure(PinConfig{Mode: uart.Mode})
config.RX.Configure(PinConfig{Mode: uart.Mode})
// reset SERCOM0
uart.Bus.CTRLA.SetBits(sam.SERCOM_USART_CTRLA_SWRST)
@@ -372,13 +375,7 @@ func (uart UART) Configure(config UARTConfig) {
uart.Bus.INTENSET.Set(sam.SERCOM_USART_INTENSET_RXC)
// Enable RX IRQ.
if config.TX == PA10 {
// UART0
arm.EnableIRQ(sam.IRQ_SERCOM0)
} else {
// UART1 which is the normal default, since UART0 is used for USBCDC.
arm.EnableIRQ(sam.IRQ_SERCOM1)
}
arm.EnableIRQ(uart.IRQVal)
}
// SetBaudRate sets the communication speed for the UART.
@@ -404,8 +401,8 @@ func (uart UART) WriteByte(c byte) error {
return nil
}
//go:export SERCOM1_IRQHandler
func handleUART1() {
// defaultUART1Handler handles the UART1 IRQ.
func defaultUART1Handler() {
// should reset IRQ
UART1.Receive(byte((UART1.Bus.DATA.Get() & 0xFF)))
UART1.Bus.INTFLAG.SetBits(sam.SERCOM_USART_INTFLAG_RXC)
@@ -1182,23 +1179,35 @@ func (usbcdc USBCDC) WriteByte(c byte) error {
usbEndpointDescriptors[usb_CDC_ENDPOINT_IN].DeviceDescBank[1].PCKSIZE.ClearBits(usb_DEVICE_PCKSIZE_MULTI_PACKET_SIZE_Mask << usb_DEVICE_PCKSIZE_MULTI_PACKET_SIZE_Pos)
// set count of bytes to be sent
usbEndpointDescriptors[usb_CDC_ENDPOINT_IN].DeviceDescBank[1].PCKSIZE.SetBits((1&usb_DEVICE_PCKSIZE_BYTE_COUNT_Mask)<<usb_DEVICE_PCKSIZE_BYTE_COUNT_Pos |
(epPacketSize(64) << usb_DEVICE_PCKSIZE_SIZE_Pos))
usbEndpointDescriptors[usb_CDC_ENDPOINT_IN].DeviceDescBank[1].PCKSIZE.SetBits((1 & usb_DEVICE_PCKSIZE_BYTE_COUNT_Mask) << usb_DEVICE_PCKSIZE_BYTE_COUNT_Pos)
// ack transfer complete flag
// clear transfer complete flag
setEPINTFLAG(usb_CDC_ENDPOINT_IN, sam.USB_DEVICE_EPINTFLAG_TRCPT1)
// send data by setting bank ready
setEPSTATUSSET(usb_CDC_ENDPOINT_IN, sam.USB_DEVICE_EPSTATUSSET_BK1RDY)
// wait for transfer to complete
timeout := 3000
for (getEPINTFLAG(usb_CDC_ENDPOINT_IN) & sam.USB_DEVICE_EPINTFLAG_TRCPT1) == 0 {
timeout--
if timeout == 0 {
return errors.New("USBCDC write byte timeout")
}
}
}
return nil
}
func (usbcdc USBCDC) DTR() bool {
return (usbLineInfo.lineState & usb_CDC_LINESTATE_DTR) > 0
}
func (usbcdc USBCDC) RTS() bool {
return (usbLineInfo.lineState & usb_CDC_LINESTATE_RTS) > 0
}
const (
// these are SAMD21 specific.
usb_DEVICE_PCKSIZE_BYTE_COUNT_Pos = 0
@@ -1346,6 +1355,7 @@ func handleUSB() {
// Clear the Bank 0 ready flag on Control OUT
setEPSTATUSCLR(0, sam.USB_DEVICE_EPSTATUSCLR_BK0RDY)
usbEndpointDescriptors[0].DeviceDescBank[0].PCKSIZE.ClearBits(usb_DEVICE_PCKSIZE_BYTE_COUNT_Mask << usb_DEVICE_PCKSIZE_BYTE_COUNT_Pos)
ok := false
if (setup.bmRequestType & usb_REQUEST_TYPE) == usb_REQUEST_STANDARD {
@@ -1375,19 +1385,24 @@ func handleUSB() {
}
}
// Now the actual transfer handlers
eptInts := sam.USB_DEVICE.EPINTSMRY.Get() & 0xFE // Remove endpoint number 0 (setup)
// Now the actual transfer handlers, ignore endpoint number 0 (setup)
var i uint32
for i = 1; i < uint32(len(endPoints)); i++ {
// Check if endpoint has a pending interrupt
if eptInts&(1<<i) > 0 {
// yes, so handle flags
epFlags := getEPINTFLAG(i)
setEPINTFLAG(i, epFlags)
epFlags := getEPINTFLAG(i)
if epFlags > 0 {
switch i {
case usb_CDC_ENDPOINT_OUT:
if (epFlags & sam.USB_DEVICE_EPINTFLAG_TRCPT0) > 0 {
handleEndpoint(i)
}
setEPINTFLAG(i, epFlags)
case usb_CDC_ENDPOINT_IN, usb_CDC_ENDPOINT_ACM:
// set bank ready
setEPSTATUSCLR(i, sam.USB_DEVICE_EPSTATUSCLR_BK1RDY)
// Endpoint Transfer Complete Interrupt
if (epFlags & sam.USB_DEVICE_EPINTFLAG_TRCPT0) > 0 {
handleEndpoint(i)
// ack transfer complete
setEPINTFLAG(i, sam.USB_DEVICE_EPINTFLAG_TRCPT1)
}
}
}
@@ -1403,7 +1418,7 @@ func initEndpoint(ep, config uint32) {
usbEndpointDescriptors[ep].DeviceDescBank[1].ADDR.Set(uint32(uintptr(unsafe.Pointer(&udd_ep_in_cache_buffer[ep]))))
// set endpoint type
setEPCFG(ep, getEPCFG(ep)|((usb_ENDPOINT_TYPE_INTERRUPT+1)<<sam.USB_DEVICE_EPCFG_EPTYPE1_Pos))
setEPCFG(ep, ((usb_ENDPOINT_TYPE_INTERRUPT + 1) << sam.USB_DEVICE_EPCFG_EPTYPE1_Pos))
case usb_ENDPOINT_TYPE_BULK | usbEndpointOut:
// set packet size
@@ -1413,11 +1428,14 @@ func initEndpoint(ep, config uint32) {
usbEndpointDescriptors[ep].DeviceDescBank[0].ADDR.Set(uint32(uintptr(unsafe.Pointer(&udd_ep_out_cache_buffer[ep]))))
// set endpoint type
setEPCFG(ep, getEPCFG(ep)|((usb_ENDPOINT_TYPE_BULK+1)<<sam.USB_DEVICE_EPCFG_EPTYPE0_Pos))
setEPCFG(ep, ((usb_ENDPOINT_TYPE_BULK + 1) << sam.USB_DEVICE_EPCFG_EPTYPE0_Pos))
// ack the current transfer
// receive interrupts when current transfer complete
setEPINTENSET(ep, sam.USB_DEVICE_EPINTENSET_TRCPT0)
// set byte count to zero, we have not received anything yet
usbEndpointDescriptors[ep].DeviceDescBank[0].PCKSIZE.ClearBits(usb_DEVICE_PCKSIZE_BYTE_COUNT_Mask << usb_DEVICE_PCKSIZE_BYTE_COUNT_Pos)
// ready for next transfer
setEPSTATUSCLR(ep, sam.USB_DEVICE_EPSTATUSCLR_BK0RDY)
@@ -1432,7 +1450,7 @@ func initEndpoint(ep, config uint32) {
usbEndpointDescriptors[ep].DeviceDescBank[1].ADDR.Set(uint32(uintptr(unsafe.Pointer(&udd_ep_in_cache_buffer[ep]))))
// set endpoint type
setEPCFG(ep, getEPCFG(ep)|((usb_ENDPOINT_TYPE_BULK+1)<<sam.USB_DEVICE_EPCFG_EPTYPE1_Pos))
setEPCFG(ep, ((usb_ENDPOINT_TYPE_BULK + 1) << sam.USB_DEVICE_EPCFG_EPTYPE1_Pos))
// NAK on endpoint IN, the bank is not yet filled in.
setEPSTATUSCLR(ep, sam.USB_DEVICE_EPSTATUSCLR_BK1RDY)
@@ -1515,7 +1533,12 @@ func handleStandardSetup(setup usbSetup) bool {
setEPSTATUSSET(0, sam.USB_DEVICE_EPSTATUSSET_BK1RDY)
// wait for transfer to complete
timeout := 3000
for (getEPINTFLAG(0) & sam.USB_DEVICE_EPINTFLAG_TRCPT1) == 0 {
timeout--
if timeout == 0 {
return true
}
}
// last, set the device address to that requested by host
@@ -1659,11 +1682,21 @@ func armRecvCtrlOUT(ep uint32) uint32 {
setEPSTATUSCLR(ep, sam.USB_DEVICE_EPSTATUSCLR_BK0RDY)
// Wait until OUT transfer is ready.
timeout := 3000
for (getEPSTATUS(ep) & sam.USB_DEVICE_EPSTATUS_BK0RDY) == 0 {
timeout--
if timeout == 0 {
return 0
}
}
// Wait until OUT transfer is completed.
timeout = 3000
for (getEPINTFLAG(ep) & sam.USB_DEVICE_EPINTFLAG_TRCPT0) == 0 {
timeout--
if timeout == 0 {
return 0
}
}
// return number of bytes received
@@ -1800,9 +1833,14 @@ func handleEndpoint(ep uint32) {
UART0.Receive(byte((udd_ep_out_cache_buffer[ep][i] & 0xFF)))
}
// set byte count to zero
usbEndpointDescriptors[ep].DeviceDescBank[0].PCKSIZE.ClearBits(usb_DEVICE_PCKSIZE_BYTE_COUNT_Mask << usb_DEVICE_PCKSIZE_BYTE_COUNT_Pos)
// set multi packet size to 64
usbEndpointDescriptors[ep].DeviceDescBank[0].PCKSIZE.SetBits(64 << usb_DEVICE_PCKSIZE_MULTI_PACKET_SIZE_Pos)
// set ready for next data
setEPSTATUSCLR(ep, sam.USB_DEVICE_EPSTATUSCLR_BK0RDY)
}
func sendZlp(ep uint32) {
+2 -1
View File
@@ -4,6 +4,7 @@ package machine
import (
"device/avr"
"runtime/volatile"
)
// Configure sets the pin to input or output.
@@ -15,7 +16,7 @@ func (p Pin) Configure(config PinConfig) {
}
}
func (p Pin) getPortMask() (*avr.Register8, uint8) {
func (p Pin) getPortMask() (*volatile.Register8, uint8) {
return avr.PORTB, 1 << uint8(p)
}
+3 -2
View File
@@ -4,6 +4,7 @@ package machine
import (
"device/avr"
"runtime/volatile"
)
type PinMode uint8
@@ -30,7 +31,7 @@ func (p Pin) Set(value bool) {
// Warning: there are no separate pin set/clear registers on the AVR. The
// returned mask is only valid as long as no other pin in the same port has been
// changed.
func (p Pin) PortMaskSet() (*avr.Register8, uint8) {
func (p Pin) PortMaskSet() (*volatile.Register8, uint8) {
port, mask := p.getPortMask()
return port, port.Get() | mask
}
@@ -41,7 +42,7 @@ func (p Pin) PortMaskSet() (*avr.Register8, uint8) {
// Warning: there are no separate pin set/clear registers on the AVR. The
// returned mask is only valid as long as no other pin in the same port has been
// changed.
func (p Pin) PortMaskClear() (*avr.Register8, uint8) {
func (p Pin) PortMaskClear() (*volatile.Register8, uint8) {
port, mask := p.getPortMask()
return port, port.Get() &^ mask
}
-40
View File
@@ -1,40 +0,0 @@
// +build !avr,!nrf,!sam,!stm32
package machine
// Dummy machine package, filled with no-ops.
type PinMode uint8
const (
PinInput PinMode = iota
PinOutput
)
// Fake LED numbers, for testing.
const (
LED Pin = LED1
LED1 Pin = 0
LED2 Pin = 0
LED3 Pin = 0
LED4 Pin = 0
)
// Fake button numbers, for testing.
const (
BUTTON Pin = BUTTON1
BUTTON1 Pin = 0
BUTTON2 Pin = 0
BUTTON3 Pin = 0
BUTTON4 Pin = 0
)
func (p Pin) Configure(config PinConfig) {
}
func (p Pin) Set(value bool) {
}
func (p Pin) Get() bool {
return false
}
+54
View File
@@ -0,0 +1,54 @@
// +build fe310
package machine
import (
"device/sifive"
)
type PinMode uint8
const (
PinInput PinMode = iota
PinOutput
)
// Configure this pin with the given configuration.
func (p Pin) Configure(config PinConfig) {
sifive.GPIO0.INPUT_EN.SetBits(1 << uint8(p))
if config.Mode == PinOutput {
sifive.GPIO0.OUTPUT_EN.SetBits(1 << uint8(p))
}
}
// Set the pin to high or low.
func (p Pin) Set(high bool) {
if high {
sifive.GPIO0.PORT.SetBits(1 << uint8(p))
} else {
sifive.GPIO0.PORT.ClearBits(1 << uint8(p))
}
}
type UART struct {
Bus *sifive.UART_Type
Buffer *RingBuffer
}
var (
UART0 = UART{Bus: sifive.UART0, Buffer: NewRingBuffer()}
)
func (uart UART) Configure(config UARTConfig) {
// Assuming a 16Mhz Crystal (which is Y1 on the HiFive1), the divisor for a
// 115200 baud rate is 138.
sifive.UART0.DIV.Set(138)
sifive.UART0.TXCTRL.Set(sifive.UART_TXCTRL_ENABLE)
}
func (uart UART) WriteByte(c byte) {
for sifive.UART0.TXDATA.Get()&sifive.UART_TXDATA_FULL != 0 {
}
sifive.UART0.TXDATA.Set(uint32(c))
}
+184
View File
@@ -0,0 +1,184 @@
// +build !avr,!nrf,!sam,!sifive,!stm32
package machine
// Dummy machine package that calls out to external functions.
var (
SPI0 = SPI{0}
I2C0 = I2C{0}
UART0 = UART{0}
)
type PinMode uint8
const (
PinInput PinMode = iota
PinOutput
PinInputPullup
PinInputPulldown
)
func (p Pin) Configure(config PinConfig) {
gpioConfigure(p, config)
}
func (p Pin) Set(value bool) {
gpioSet(p, value)
}
func (p Pin) Get() bool {
return gpioGet(p)
}
//go:export __tinygo_gpio_configure
func gpioConfigure(pin Pin, config PinConfig)
//go:export __tinygo_gpio_set
func gpioSet(pin Pin, value bool)
//go:export __tinygo_gpio_get
func gpioGet(pin Pin) bool
type SPI struct {
Bus uint8
}
type SPIConfig struct {
Frequency uint32
SCK Pin
MOSI Pin
MISO Pin
Mode uint8
}
func (spi SPI) Configure(config SPIConfig) {
spiConfigure(spi.Bus, config.SCK, config.MOSI, config.MISO)
}
// Transfer writes/reads a single byte using the SPI interface.
func (spi SPI) Transfer(w byte) (byte, error) {
return spiTransfer(spi.Bus, w), nil
}
//go:export __tinygo_spi_configure
func spiConfigure(bus uint8, sck Pin, mosi Pin, miso Pin)
//go:export __tinygo_spi_transfer
func spiTransfer(bus uint8, w uint8) uint8
// InitADC enables support for ADC peripherals.
func InitADC() {
// Nothing to do here.
}
// Configure configures an ADC pin to be able to be used to read data.
func (adc ADC) Configure() {
}
// Get reads the current analog value from this ADC peripheral.
func (adc ADC) Get() uint16 {
return adcRead(adc.Pin)
}
//go:export __tinygo_adc_read
func adcRead(pin Pin) uint16
// InitPWM enables support for PWM peripherals.
func InitPWM() {
// Nothing to do here.
}
// Configure configures a PWM pin for output.
func (pwm PWM) Configure() {
}
// Set turns on the duty cycle for a PWM pin using the provided value.
func (pwm PWM) Set(value uint16) {
pwmSet(pwm.Pin, value)
}
//go:export __tinygo_pwm_set
func pwmSet(pin Pin, value uint16)
// I2C is a generic implementation of the Inter-IC communication protocol.
type I2C struct {
Bus uint8
}
// I2CConfig is used to store config info for I2C.
type I2CConfig struct {
Frequency uint32
SCL Pin
SDA Pin
}
// Configure is intended to setup the I2C interface.
func (i2c I2C) Configure(config I2CConfig) {
i2cConfigure(i2c.Bus, config.SCL, config.SDA)
}
// Tx does a single I2C transaction at the specified address.
func (i2c I2C) Tx(addr uint16, w, r []byte) error {
i2cTransfer(i2c.Bus, &w[0], len(w), &r[0], len(r))
// TODO: do something with the returned error code.
return nil
}
//go:export __tinygo_i2c_configure
func i2cConfigure(bus uint8, scl Pin, sda Pin)
//go:export __tinygo_i2c_transfer
func i2cTransfer(bus uint8, w *byte, wlen int, r *byte, rlen int) int
type UART struct {
Bus uint8
}
type UARTConfig struct {
BaudRate uint32
TX Pin
RX Pin
}
// Configure the UART.
func (uart UART) Configure(config UARTConfig) {
uartConfigure(uart.Bus, config.TX, config.RX)
}
// Read from the UART.
func (uart UART) Read(data []byte) (n int, err error) {
return uartRead(uart.Bus, &data[0], len(data)), nil
}
// Write to the UART.
func (uart UART) Write(data []byte) (n int, err error) {
return uartWrite(uart.Bus, &data[0], len(data)), nil
}
// Buffered returns the number of bytes currently stored in the RX buffer.
func (uart UART) Buffered() int {
return 0
}
// ReadByte reads a single byte from the UART.
func (uart UART) ReadByte() (byte, error) {
var b byte
uartRead(uart.Bus, &b, 1)
return b, nil
}
// WriteByte writes a single byte to the UART.
func (uart UART) WriteByte(b byte) error {
uartWrite(uart.Bus, &b, 1)
return nil
}
//go:export __tinygo_uart_configure
func uartConfigure(bus uint8, tx Pin, rx Pin)
//go:export __tinygo_uart_read
func uartRead(bus uint8, buf *byte, bufLen int) int
//go:export __tinygo_uart_write
func uartWrite(bus uint8, buf *byte, bufLen int) int
-11
View File
@@ -5,14 +5,3 @@ package machine
// Peripheral abstraction layer for the stm32.
type PinMode uint8
const (
portA Pin = iota * 16
portB
portC
portD
portE
portF
portG
portH
)
+1 -1
View File
@@ -1,4 +1,4 @@
// +build nrf stm32f103xx atsamd21g18a
// +build !stm32f407,!avr,!hifive1b
package machine
+1 -1
View File
@@ -1,4 +1,4 @@
// +build avr nrf sam stm32
// +build avr nrf sam sifive stm32
package machine
+9 -6
View File
@@ -4,9 +4,9 @@ package machine
import (
"bytes"
"device/sam"
"encoding/binary"
"errors"
"runtime/volatile"
)
const deviceDescriptorSize = 18
@@ -473,6 +473,9 @@ const (
usb_CDC_CS_INTERFACE = 0x24
usb_CDC_CS_ENDPOINT = 0x25
usb_CDC_DATA_INTERFACE_CLASS = 0x0A
usb_CDC_LINESTATE_DTR = 0x01
usb_CDC_LINESTATE_RTS = 0x02
)
// usbDeviceDescBank is the USB device endpoint descriptor.
@@ -484,11 +487,11 @@ const (
// RoReg8 Reserved1[0x5];
// } UsbDeviceDescBank;
type usbDeviceDescBank struct {
ADDR sam.Register32
PCKSIZE sam.Register32
EXTREG sam.Register16
STATUS_BK sam.Register8
_reserved [5]sam.Register8
ADDR volatile.Register32
PCKSIZE volatile.Register32
EXTREG volatile.Register16
STATUS_BK volatile.Register8
_reserved [5]volatile.Register8
}
type usbDeviceDescriptor struct {
+160 -1
View File
@@ -11,7 +11,8 @@ import (
// Portable analogs of some common system call errors.
var (
ErrUnsupported = errors.New("operation not supported")
errUnsupported = errors.New("operation not supported")
notImplemented = errors.New("os: not implemented")
)
// Stdin, Stdout, and Stderr are open Files pointing to the standard input,
@@ -28,6 +29,21 @@ type File struct {
name string
}
// Readdir is a stub, not yet implemented
func (f *File) Readdir(n int) ([]FileInfo, error) {
return nil, notImplemented
}
// Readdirnames is a stub, not yet implemented
func (f *File) Readdirnames(n int) (names []string, err error) {
return nil, notImplemented
}
// Stat is a stub, not yet implemented
func (f *File) Stat() (FileInfo, error) {
return nil, notImplemented
}
// NewFile returns a new File with the given file descriptor and name.
func NewFile(fd uintptr, name string) *File {
return &File{fd, name}
@@ -38,3 +54,146 @@ func NewFile(fd uintptr, name string) *File {
func (f *File) Fd() uintptr {
return f.fd
}
const (
PathSeparator = '/' // OS-specific path separator
PathListSeparator = ':' // OS-specific path list separator
)
// IsPathSeparator reports whether c is a directory separator character.
func IsPathSeparator(c uint8) bool {
return PathSeparator == c
}
// PathError records an error and the operation and file path that caused it.
type PathError struct {
Op string
Path string
Err error
}
func (e *PathError) Error() string { return e.Op + " " + e.Path + ": " + e.Err.Error() }
// Open is a super simple stub function (for now), only capable of opening stdin, stdout, and stderr
func Open(name string) (*File, error) {
fd := uintptr(999)
switch name {
case "/dev/stdin":
fd = 0
case "/dev/stdout":
fd = 1
case "/dev/stderr":
fd = 2
default:
return nil, &PathError{"open", name, notImplemented}
}
return &File{fd, name}, nil
}
// OpenFile is a stub, passing through to the stub Open() call
func OpenFile(name string, flag int, perm FileMode) (*File, error) {
return Open(name)
}
// Create is a stub, passing through to the stub Open() call
func Create(name string) (*File, error) {
return Open(name)
}
type FileMode uint32
// Mode constants, copied from the mainline Go source
// https://github.com/golang/go/blob/4ce6a8e89668b87dce67e2f55802903d6eb9110a/src/os/types.go#L35-L63
const (
// The single letters are the abbreviations used by the String method's formatting.
ModeDir FileMode = 1 << (32 - 1 - iota) // d: is a directory
ModeAppend // a: append-only
ModeExclusive // l: exclusive use
ModeTemporary // T: temporary file; Plan 9 only
ModeSymlink // L: symbolic link
ModeDevice // D: device file
ModeNamedPipe // p: named pipe (FIFO)
ModeSocket // S: Unix domain socket
ModeSetuid // u: setuid
ModeSetgid // g: setgid
ModeCharDevice // c: Unix character device, when ModeDevice is set
ModeSticky // t: sticky
ModeIrregular // ?: non-regular file; nothing else is known about this file
// Mask for the type bits. For regular files, none will be set.
ModeType = ModeDir | ModeSymlink | ModeNamedPipe | ModeSocket | ModeDevice | ModeCharDevice | ModeIrregular
ModePerm FileMode = 0777 // Unix permission bits
)
// IsDir is a stub, always returning false
func (m FileMode) IsDir() bool {
return false
}
// Stub constants
const (
O_RDONLY int = 1
O_WRONLY int = 2
O_RDWR int = 4
O_APPEND int = 8
O_CREATE int = 16
O_EXCL int = 32
O_SYNC int = 64
O_TRUNC int = 128
)
// A FileInfo describes a file and is returned by Stat and Lstat.
type FileInfo interface {
Name() string // base name of the file
Size() int64 // length in bytes for regular files; system-dependent for others
Mode() FileMode // file mode bits
// ModTime() time.Time // modification time
IsDir() bool // abbreviation for Mode().IsDir()
Sys() interface{} // underlying data source (can return nil)
}
// Stat is a stub, not yet implemented
func Stat(name string) (FileInfo, error) {
return nil, notImplemented
}
// Lstat is a stub, not yet implemented
func Lstat(name string) (FileInfo, error) {
return nil, notImplemented
}
// Getwd is a stub (for now), always returning an empty string
func Getwd() (string, error) {
return "", nil
}
// Readlink is a stub (for now), always returning the string it was given
func Readlink(name string) (string, error) {
return name, nil
}
// TempDir is a stub (for now), always returning the string "/tmp"
func TempDir() string {
return "/tmp"
}
// Mkdir is a stub, not yet implemented
func Mkdir(name string, perm FileMode) error {
return notImplemented
}
// IsExist is a stub (for now), always returning false
func IsExist(err error) bool {
return false
}
// IsNotExist is a stub (for now), always returning false
func IsNotExist(err error) bool {
return false
}
// Getpid is a stub (for now), always returning 1
func Getpid() int {
return 1
}
+4 -4
View File
@@ -1,4 +1,4 @@
// +build avr cortexm wasm
// +build avr cortexm tinygo.riscv wasm
package os
@@ -8,7 +8,7 @@ import (
// Read is unsupported on this system.
func (f *File) Read(b []byte) (n int, err error) {
return 0, ErrUnsupported
return 0, errUnsupported
}
// Write writes len(b) bytes to the output. It returns the number of bytes
@@ -21,13 +21,13 @@ func (f *File) Write(b []byte) (n int, err error) {
}
return len(b), nil
default:
return 0, ErrUnsupported
return 0, errUnsupported
}
}
// Close is unsupported on this system.
func (f *File) Close() error {
return ErrUnsupported
return errUnsupported
}
//go:linkname putchar runtime.putchar
+1 -1
View File
@@ -1,4 +1,4 @@
// +build darwin linux,!avr,!cortexm
// +build darwin linux,!avr,!cortexm,!tinygo.riscv
package os
+17
View File
@@ -0,0 +1,17 @@
// Package os implements a subset of the Go "os" package. See
// https://godoc.org/os for details.
//
// Note that the current implementation is blocking. This limitation should be
// removed in a future version.
package os
import (
"syscall"
)
// Exit causes the current program to exit with the given status code.
// Conventionally, code zero indicates success, non-zero an error.
// The program terminates immediately; deferred functions are not run.
func Exit(code int) {
syscall.Exit(code)
}
+9 -1
View File
@@ -92,7 +92,7 @@ func (v Value) Pointer() uintptr {
}
func (v Value) IsValid() bool {
panic("unimplemented: (reflect.Value).IsValid()")
return v.typecode != 0
}
func (v Value) CanInterface() bool {
@@ -502,6 +502,14 @@ func MakeSlice(typ Type, len, cap int) Value {
panic("unimplemented: reflect.MakeSlice()")
}
func Zero(typ Type) Value {
panic("unimplemented: reflect.Zero()")
}
func New(typ Type) Value {
panic("unimplemented: reflect.New()")
}
type funcHeader struct {
Context unsafe.Pointer
Code unsafe.Pointer
+1 -1
View File
@@ -1,4 +1,4 @@
// +build arm,!avr,!cortexm
// +build arm,!avr,!cortexm,!tinygo.riscv
package runtime
+1 -16
View File
@@ -2,26 +2,11 @@
package runtime
import (
"unsafe"
)
const GOARCH = "avr"
const GOARCH = "arm" // avr pretends to be arm
// The bitness of the CPU (e.g. 8, 32, 64).
const TargetBits = 8
//go:extern _heap_start
var heapStartSymbol unsafe.Pointer
//go:extern _heap_end
var heapEndSymbol unsafe.Pointer
var (
heapStart = uintptr(unsafe.Pointer(&heapStartSymbol))
heapEnd = uintptr(unsafe.Pointer(&heapEndSymbol))
)
// Align on a word boundary.
func align(ptr uintptr) uintptr {
// No alignment necessary on the AVR.
-25
View File
@@ -3,8 +3,6 @@
package runtime
import (
"unsafe"
"device/arm"
)
@@ -13,29 +11,6 @@ const GOARCH = "arm"
// The bitness of the CPU (e.g. 8, 32, 64).
const TargetBits = 32
//go:extern _heap_start
var heapStartSymbol unsafe.Pointer
//go:extern _heap_end
var heapEndSymbol unsafe.Pointer
//go:extern _globals_start
var globalsStartSymbol unsafe.Pointer
//go:extern _globals_end
var globalsEndSymbol unsafe.Pointer
//go:extern _stack_top
var stackTopSymbol unsafe.Pointer
var (
heapStart = uintptr(unsafe.Pointer(&heapStartSymbol))
heapEnd = uintptr(unsafe.Pointer(&heapEndSymbol))
globalsStart = uintptr(unsafe.Pointer(&globalsStartSymbol))
globalsEnd = uintptr(unsafe.Pointer(&globalsEndSymbol))
stackTop = uintptr(unsafe.Pointer(&stackTopSymbol))
)
// Align on word boundary.
func align(ptr uintptr) uintptr {
return (ptr + 3) &^ 3
+19
View File
@@ -0,0 +1,19 @@
// +build tinygo.riscv
package runtime
import "device/riscv"
const GOARCH = "arm" // riscv pretends to be arm
// The bitness of the CPU (e.g. 8, 32, 64).
const TargetBits = 32
// Align on word boundary.
func align(ptr uintptr) uintptr {
return (ptr + 3) &^ 3
}
func getCurrentStackPointer() uintptr {
return riscv.ReadRegister("sp")
}
+4 -1
View File
@@ -14,9 +14,12 @@ const TargetBits = 32
//go:extern __heap_base
var heapStartSymbol unsafe.Pointer
//go:export llvm.wasm.memory.size.i32
func wasm_memory_size(index int32) int32
var (
heapStart = uintptr(unsafe.Pointer(&heapStartSymbol))
heapEnd = (heapStart + wasmPageSize - 1) &^ (wasmPageSize - 1) // conservative guess: one page of heap memory
heapEnd = uintptr(wasm_memory_size(0) * wasmPageSize)
)
const wasmPageSize = 64 * 1024
+30
View File
@@ -0,0 +1,30 @@
// +build avr cortexm tinygo.riscv
package runtime
import (
"unsafe"
)
//go:extern _heap_start
var heapStartSymbol unsafe.Pointer
//go:extern _heap_end
var heapEndSymbol unsafe.Pointer
//go:extern _globals_start
var globalsStartSymbol unsafe.Pointer
//go:extern _globals_end
var globalsEndSymbol unsafe.Pointer
//go:extern _stack_top
var stackTopSymbol unsafe.Pointer
var (
heapStart = uintptr(unsafe.Pointer(&heapStartSymbol))
heapEnd = uintptr(unsafe.Pointer(&heapEndSymbol))
globalsStart = uintptr(unsafe.Pointer(&globalsStartSymbol))
globalsEnd = uintptr(unsafe.Pointer(&globalsEndSymbol))
stackTop = uintptr(unsafe.Pointer(&stackTopSymbol))
)
+81 -10
View File
@@ -28,24 +28,35 @@ import (
)
type channel struct {
state uint8
blocked *coroutine
elementSize uint16 // the size of one value in this channel
state chanState
blocked *coroutine
}
type chanState uint8
const (
chanStateEmpty = iota
chanStateEmpty chanState = iota
chanStateRecv
chanStateSend
chanStateClosed
)
// chanSelectState is a single channel operation (send/recv) in a select
// statement. The value pointer is either nil (for receives) or points to the
// value to send (for sends).
type chanSelectState struct {
ch *channel
value unsafe.Pointer
}
func deadlockStub()
// chanSend sends a single value over the channel. If this operation can
// complete immediately (there is a goroutine waiting for a value), it sends the
// value and re-activates both goroutines. If not, it sets itself as waiting on
// a value.
func chanSend(sender *coroutine, ch *channel, value unsafe.Pointer, size uintptr) {
func chanSend(sender *coroutine, ch *channel, value unsafe.Pointer) {
if ch == nil {
// A nil channel blocks forever. Do not scheduler this goroutine again.
return
@@ -58,7 +69,7 @@ func chanSend(sender *coroutine, ch *channel, value unsafe.Pointer, size uintptr
case chanStateRecv:
receiver := ch.blocked
receiverPromise := receiver.promise()
memcpy(receiverPromise.ptr, value, size)
memcpy(receiverPromise.ptr, value, uintptr(ch.elementSize))
receiverPromise.data = 1 // commaOk = true
ch.blocked = receiverPromise.next
receiverPromise.next = nil
@@ -80,7 +91,7 @@ func chanSend(sender *coroutine, ch *channel, value unsafe.Pointer, size uintptr
// sender, it receives the value immediately and re-activates both coroutines.
// If not, it sets itself as available for receiving. If the channel is closed,
// it immediately activates itself with a zero value as the result.
func chanRecv(receiver *coroutine, ch *channel, value unsafe.Pointer, size uintptr) {
func chanRecv(receiver *coroutine, ch *channel, value unsafe.Pointer) {
if ch == nil {
// A nil channel blocks forever. Do not scheduler this goroutine again.
return
@@ -89,7 +100,7 @@ func chanRecv(receiver *coroutine, ch *channel, value unsafe.Pointer, size uintp
case chanStateSend:
sender := ch.blocked
senderPromise := sender.promise()
memcpy(value, senderPromise.ptr, size)
memcpy(value, senderPromise.ptr, uintptr(ch.elementSize))
receiver.promise().data = 1 // commaOk = true
ch.blocked = senderPromise.next
senderPromise.next = nil
@@ -103,7 +114,7 @@ func chanRecv(receiver *coroutine, ch *channel, value unsafe.Pointer, size uintp
ch.state = chanStateRecv
ch.blocked = receiver
case chanStateClosed:
memzero(value, size)
memzero(value, uintptr(ch.elementSize))
receiver.promise().data = 0 // commaOk = false
activateTask(receiver)
case chanStateRecv:
@@ -115,7 +126,7 @@ func chanRecv(receiver *coroutine, ch *channel, value unsafe.Pointer, size uintp
// chanClose closes the given channel. If this channel has a receiver or is
// empty, it closes the channel. Else, it panics.
func chanClose(ch *channel, size uintptr) {
func chanClose(ch *channel) {
if ch == nil {
// Not allowed by the language spec.
runtimePanic("close of nil channel")
@@ -133,7 +144,7 @@ func chanClose(ch *channel, size uintptr) {
case chanStateRecv:
// The receiver must be re-activated with a zero value.
receiverPromise := ch.blocked.promise()
memzero(receiverPromise.ptr, size)
memzero(receiverPromise.ptr, uintptr(ch.elementSize))
receiverPromise.data = 0 // commaOk = false
activateTask(ch.blocked)
ch.state = chanStateClosed
@@ -143,3 +154,63 @@ func chanClose(ch *channel, size uintptr) {
ch.state = chanStateClosed
}
}
// chanSelect is the runtime implementation of the select statement. This is
// perhaps the most complicated statement in the Go spec. It returns the
// selected index and the 'comma-ok' value.
//
// TODO: do this in a round-robin fashion (as specified in the Go spec) instead
// of picking the first one that can proceed.
func chanSelect(recvbuf unsafe.Pointer, states []chanSelectState, blocking bool) (uintptr, bool) {
// See whether we can receive from one of the channels.
for i, state := range states {
if state.ch == nil {
// A nil channel blocks forever, so don't consider it here.
continue
}
if state.value == nil {
// A receive operation.
switch state.ch.state {
case chanStateSend:
// We can receive immediately.
sender := state.ch.blocked
senderPromise := sender.promise()
memcpy(recvbuf, senderPromise.ptr, uintptr(state.ch.elementSize))
state.ch.blocked = senderPromise.next
senderPromise.next = nil
activateTask(sender)
if state.ch.blocked == nil {
state.ch.state = chanStateEmpty
}
return uintptr(i), true // commaOk = true
case chanStateClosed:
// Receive the zero value.
memzero(recvbuf, uintptr(state.ch.elementSize))
return uintptr(i), false // commaOk = false
}
} else {
// A send operation: state.value is not nil.
switch state.ch.state {
case chanStateRecv:
receiver := state.ch.blocked
receiverPromise := receiver.promise()
memcpy(receiverPromise.ptr, state.value, uintptr(state.ch.elementSize))
receiverPromise.data = 1 // commaOk = true
state.ch.blocked = receiverPromise.next
receiverPromise.next = nil
activateTask(receiver)
if state.ch.blocked == nil {
state.ch.state = chanStateEmpty
}
return uintptr(i), false
case chanStateClosed:
runtimePanic("send on closed channel")
}
}
}
if !blocking {
return ^uintptr(0), false
}
panic("unimplemented: blocking select")
}
@@ -1,4 +1,4 @@
// +build gc.marksweep
// +build gc.conservative
package runtime
@@ -119,7 +119,7 @@ func (b gcBlock) findHead() gcBlock {
// findNext returns the first block just past the end of the tail. This may or
// may not be the head of an object.
func (b gcBlock) findNext() gcBlock {
if b.state() == blockStateHead {
if b.state() == blockStateHead || b.state() == blockStateMark {
b++
}
for b.state() == blockStateTail {
@@ -203,6 +203,7 @@ func init() {
// alloc tries to find some free space on the heap, possibly doing a garbage
// collection cycle if needed. If no space is free, it panics.
//go:noinline
func alloc(size uintptr) unsafe.Pointer {
if size == 0 {
return unsafe.Pointer(&zeroSizedAlloc)
@@ -282,8 +283,8 @@ func GC() {
}
// Mark phase: mark all reachable objects, recursively.
markRoots(globalsStart, globalsEnd)
markRoots(getCurrentStackPointer(), stackTop) // assume a descending stack
markGlobals()
markStack()
// Sweep phase: free all non-marked objects and unmark marked objects for
// the next collection cycle.
@@ -303,21 +304,30 @@ func markRoots(start, end uintptr) {
if gcDebug {
println("mark from", start, "to", end, int(end-start))
}
if gcAsserts {
if start >= end {
runtimePanic("gc: unexpected range to mark")
}
}
for addr := start; addr != end; addr += unsafe.Sizeof(addr) {
root := *(*uintptr)(unsafe.Pointer(addr))
if looksLikePointer(root) {
block := blockFromAddr(root)
head := block.findHead()
if head.state() != blockStateMark {
if gcDebug {
println("found unmarked pointer", root, "at address", addr)
}
head.setState(blockStateMark)
next := block.findNext()
// TODO: avoid recursion as much as possible
markRoots(head.address(), next.address())
markRoot(addr, root)
}
}
func markRoot(addr, root uintptr) {
if looksLikePointer(root) {
block := blockFromAddr(root)
head := block.findHead()
if head.state() != blockStateMark {
if gcDebug {
println("found unmarked pointer", root, "at address", addr)
}
head.setState(blockStateMark)
next := block.findNext()
// TODO: avoid recursion as much as possible
markRoots(head.address(), next.address())
}
}
}
+12
View File
@@ -0,0 +1,12 @@
// +build gc.conservative
// +build cortexm tinygo.riscv
package runtime
// markGlobals marks all globals, which are reachable by definition.
//
// This implementation marks all globals conservatively and assumes it can use
// linker-defined symbols for the start and end of the .data section.
func markGlobals() {
markRoots(globalsStart, globalsEnd)
}
+35
View File
@@ -0,0 +1,35 @@
// +build gc.conservative
// +build !cortexm,!tinygo.riscv
package runtime
import (
"unsafe"
)
//go:extern runtime.trackedGlobalsStart
var trackedGlobalsStart uintptr
//go:extern runtime.trackedGlobalsLength
var trackedGlobalsLength uintptr
//go:extern runtime.trackedGlobalsBitmap
var trackedGlobalsBitmap [0]uint8
// markGlobals marks all globals, which are reachable by definition.
//
// This implementation relies on a compiler pass that stores all globals in a
// single global (adjusting all uses of them accordingly) and creates a bit
// vector with the locations of each pointer. This implementation then walks the
// bit vector and for each pointer it indicates, it marks the root.
//
//go:nobounds
func markGlobals() {
for i := uintptr(0); i < trackedGlobalsLength; i++ {
if trackedGlobalsBitmap[i/8]&(1<<(i%8)) != 0 {
addr := trackedGlobalsStart + i*unsafe.Alignof(uintptr(0))
root := *(*uintptr)(unsafe.Pointer(addr))
markRoot(addr, root)
}
}
}
@@ -1,4 +1,4 @@
// +build gc.dumb
// +build gc.leaking
package runtime
+39
View File
@@ -0,0 +1,39 @@
// +build gc.conservative
// +build !cortexm,!tinygo.riscv
package runtime
import (
"unsafe"
)
//go:extern runtime.stackChainStart
var stackChainStart *stackChainObject
type stackChainObject struct {
parent *stackChainObject
numSlots uintptr
}
// markStack marks all root pointers found on the stack.
//
// This implementation is conservative and relies on the compiler inserting code
// to manually push/pop stack objects that are stored in a linked list starting
// with stackChainStart. Manually keeping track of stack values is _much_ more
// expensive than letting the compiler do it and it inhibits a few important
// optimizations, but it has the big advantage of being portable to basically
// any ISA, including WebAssembly.
func markStack() {
stackObject := stackChainStart
for stackObject != nil {
start := uintptr(unsafe.Pointer(stackObject)) + unsafe.Sizeof(uintptr(0))*2
end := start + stackObject.numSlots*unsafe.Alignof(uintptr(0))
markRoots(start, end)
stackObject = stackObject.parent
}
}
// trackPointer is a stub function call inserted by the compiler during IR
// construction. Calls to it are later replaced with regular stack bookkeeping
// code.
func trackPointer(ptr unsafe.Pointer)
+13
View File
@@ -0,0 +1,13 @@
// +build gc.conservative
// +build cortexm tinygo.riscv
package runtime
// markStack marks all root pointers found on the stack.
//
// This implementation is conservative and relies on the stack top (provided by
// the linker) and getting the current stack pointer from a register. Also, it
// assumes a descending stack. Thus, it is not very portable.
func markStack() {
markRoots(getCurrentStackPointer(), stackTop)
}
+54 -6
View File
@@ -56,7 +56,15 @@ func math_Cbrt(x float64) float64 { return math_cbrt(x) }
func math_cbrt(x float64) float64
//go:linkname math_Ceil math.Ceil
func math_Ceil(x float64) float64 { return math_ceil(x) }
func math_Ceil(x float64) float64 {
if GOARCH == "arm64" || GOARCH == "wasm" {
return llvm_ceil(x)
}
return math_ceil(x)
}
//go:export llvm.ceil.f64
func llvm_ceil(x float64) float64
//go:linkname math_ceil math.ceil
func math_ceil(x float64) float64
@@ -104,7 +112,15 @@ func math_Exp2(x float64) float64 { return math_exp2(x) }
func math_exp2(x float64) float64
//go:linkname math_Floor math.Floor
func math_Floor(x float64) float64 { return math_floor(x) }
func math_Floor(x float64) float64 {
if GOARCH == "arm64" || GOARCH == "wasm" {
return llvm_floor(x)
}
return math_floor(x)
}
//go:export llvm.floor.f64
func llvm_floor(x float64) float64
//go:linkname math_floor math.floor
func math_floor(x float64) float64
@@ -152,13 +168,29 @@ func math_Log2(x float64) float64 { return math_log2(x) }
func math_log2(x float64) float64
//go:linkname math_Max math.Max
func math_Max(x, y float64) float64 { return math_max(x, y) }
func math_Max(x, y float64) float64 {
if GOARCH == "arm64" || GOARCH == "wasm" {
return llvm_maximum(x, y)
}
return math_max(x, y)
}
//go: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 { return math_min(x, y) }
func math_Min(x, y float64) float64 {
if GOARCH == "arm64" || GOARCH == "wasm" {
return llvm_minimum(x, y)
}
return math_min(x, y)
}
//go:export llvm.minimum.f64
func llvm_minimum(x, y float64) float64
//go:linkname math_min math.min
func math_min(x, y float64) float64
@@ -200,7 +232,15 @@ func math_Sinh(x float64) float64 { return math_sinh(x) }
func math_sinh(x float64) float64
//go:linkname math_Sqrt math.Sqrt
func math_Sqrt(x float64) float64 { return math_sqrt(x) }
func math_Sqrt(x float64) float64 {
if GOARCH == "x86" || GOARCH == "amd64" || GOARCH == "wasm" {
return llvm_sqrt(x)
}
return math_sqrt(x)
}
//go:export llvm.sqrt.f64
func llvm_sqrt(x float64) float64
//go:linkname math_sqrt math.sqrt
func math_sqrt(x float64) float64
@@ -218,7 +258,15 @@ func math_Tanh(x float64) float64 { return math_tanh(x) }
func math_tanh(x float64) float64
//go:linkname math_Trunc math.Trunc
func math_Trunc(x float64) float64 { return math_trunc(x) }
func math_Trunc(x float64) float64 {
if GOARCH == "arm64" || GOARCH == "wasm" {
return llvm_trunc(x)
}
return math_trunc(x)
}
//go:export llvm.trunc.f64
func llvm_trunc(x float64) float64
//go:linkname math_trunc math.trunc
func math_trunc(x float64) float64
+5 -7
View File
@@ -6,6 +6,7 @@ import (
"device/arm"
"device/sam"
"machine"
"runtime/volatile"
"unsafe"
)
@@ -230,10 +231,7 @@ var (
timerLastCounter uint64
)
//go:volatile
type isrFlag bool
var timerWakeup isrFlag
var timerWakeup volatile.Register8
const asyncScheduler = false
@@ -262,7 +260,7 @@ func ticks() timeUnit {
// ticks are in microseconds
func timerSleep(ticks uint32) {
timerWakeup = false
timerWakeup.Set(0)
if ticks < 30 {
// have to have at least one clock count
ticks = 30
@@ -280,7 +278,7 @@ func timerSleep(ticks uint32) {
// enable IRQ for CMP0 compare
sam.RTC_MODE0.INTENSET.SetBits(sam.RTC_MODE0_INTENSET_CMP0)
for !timerWakeup {
for timerWakeup.Get() == 0 {
arm.Asm("wfi")
}
}
@@ -290,7 +288,7 @@ func handleRTC() {
// disable IRQ for CMP0 compare
sam.RTC_MODE0.INTFLAG.Set(sam.RTC_MODE0_INTENSET_CMP0)
timerWakeup = true
timerWakeup.Set(1)
}
func initUSBClock() {
+115
View File
@@ -0,0 +1,115 @@
// +build fe310
// This file implements target-specific things for the FE310 chip as used in the
// HiFive1.
package runtime
import (
"machine"
"unsafe"
"device/riscv"
"device/sifive"
)
type timeUnit int64
const tickMicros = 32768 // RTC runs at 32.768kHz
//go:extern _sbss
var _sbss unsafe.Pointer
//go:extern _ebss
var _ebss unsafe.Pointer
//go:extern _sdata
var _sdata unsafe.Pointer
//go:extern _sidata
var _sidata unsafe.Pointer
//go:extern _edata
var _edata unsafe.Pointer
//go:export main
func main() {
preinit()
initAll()
callMain()
abort()
}
func init() {
pric_init()
machine.UART0.Configure(machine.UARTConfig{})
}
func pric_init() {
// Make sure the HFROSC is on
sifive.PRIC.HFROSCCFG.SetBits(sifive.PRIC_HFROSCCFG_ENABLE)
// Run off 16 MHz Crystal for accuracy.
sifive.PRIC.PLLCFG.SetBits(sifive.PRIC_PLLCFG_REFSEL | sifive.PRIC_PLLCFG_BYPASS)
sifive.PRIC.PLLCFG.SetBits(sifive.PRIC_PLLCFG_SEL)
// Turn off HFROSC to save power
sifive.PRIC.HFROSCCFG.ClearBits(sifive.PRIC_HFROSCCFG_ENABLE)
// Enable the RTC.
sifive.RTC.CONFIG.Set(sifive.RTC_CONFIG_ENALWAYS)
}
func preinit() {
// Initialize .bss: zero-initialized global variables.
ptr := uintptr(unsafe.Pointer(&_sbss))
for ptr != uintptr(unsafe.Pointer(&_ebss)) {
*(*uint32)(unsafe.Pointer(ptr)) = 0
ptr += 4
}
// Initialize .data: global variables initialized from flash.
src := uintptr(unsafe.Pointer(&_sidata))
dst := uintptr(unsafe.Pointer(&_sdata))
for dst != uintptr(unsafe.Pointer(&_edata)) {
*(*uint32)(unsafe.Pointer(dst)) = *(*uint32)(unsafe.Pointer(src))
dst += 4
src += 4
}
}
func putchar(c byte) {
machine.UART0.WriteByte(c)
}
func ticks() timeUnit {
// Combining the low bits and the high bits yields a time span of over 270
// years without counter rollover.
highBits := sifive.RTC.HI.Get()
for {
lowBits := sifive.RTC.LO.Get()
newHighBits := sifive.RTC.HI.Get()
if newHighBits == highBits {
// High bits stayed the same.
return timeUnit(lowBits) | (timeUnit(highBits) << 32)
}
// Retry, because there was a rollover in the low bits (happening every
// 1.5 days).
highBits = newHighBits
}
}
const asyncScheduler = false
func sleepTicks(d timeUnit) {
target := ticks() + d
for ticks() < target {
}
}
func abort() {
// lock up forever
for {
riscv.Asm("wfi")
}
}
+5 -7
View File
@@ -6,6 +6,7 @@ import (
"device/arm"
"device/nrf"
"machine"
"runtime/volatile"
)
type timeUnit int64
@@ -79,14 +80,11 @@ func ticks() timeUnit {
return timestamp
}
//go:volatile
type isrFlag bool
var rtc_wakeup isrFlag
var rtc_wakeup volatile.Register8
func rtc_sleep(ticks uint32) {
nrf.RTC1.INTENSET.Set(nrf.RTC_INTENSET_COMPARE0)
rtc_wakeup = false
rtc_wakeup.Set(0)
if ticks == 1 {
// Race condition (even in hardware) at ticks == 1.
// TODO: fix this in a better way by detecting it, like the manual
@@ -94,7 +92,7 @@ func rtc_sleep(ticks uint32) {
ticks = 2
}
nrf.RTC1.CC[0].Set((nrf.RTC1.COUNTER.Get() + ticks) & 0x00ffffff)
for !rtc_wakeup {
for rtc_wakeup.Get() == 0 {
arm.Asm("wfi")
}
}
@@ -103,5 +101,5 @@ func rtc_sleep(ticks uint32) {
func handleRTC1() {
nrf.RTC1.INTENCLR.Set(nrf.RTC_INTENSET_COMPARE0)
nrf.RTC1.EVENTS_COMPARE[0].Set(0)
rtc_wakeup = true
rtc_wakeup.Set(1)
}
+3 -5
View File
@@ -7,6 +7,7 @@ package runtime
import (
"device/arm"
"runtime/volatile"
"unsafe"
)
@@ -36,12 +37,9 @@ func ticks() timeUnit {
return timestamp
}
//go:volatile
type regValue uint32
// UART0 output register.
var stdoutWrite *regValue = (*regValue)(unsafe.Pointer(uintptr(0x4000c000)))
var stdoutWrite = (*volatile.Register8)(unsafe.Pointer(uintptr(0x4000c000)))
func putchar(c byte) {
*stdoutWrite = regValue(c)
stdoutWrite.Set(uint8(c))
}
+5 -7
View File
@@ -6,6 +6,7 @@ import (
"device/arm"
"device/stm32"
"machine"
"runtime/volatile"
)
func init() {
@@ -58,10 +59,7 @@ var (
timerLastCounter uint64
)
//go:volatile
type isrFlag bool
var timerWakeup isrFlag
var timerWakeup volatile.Register8
func initRTC() {
// Enable the PWR and BKP.
@@ -136,7 +134,7 @@ func ticks() timeUnit {
// ticks are in microseconds
func timerSleep(ticks uint32) {
timerWakeup = false
timerWakeup.Set(0)
// STM32 timer update event period is calculated as follows:
//
@@ -177,7 +175,7 @@ func timerSleep(ticks uint32) {
stm32.TIM3.CR1.SetBits(stm32.TIM_CR1_CEN)
// wait till timer wakes up
for !timerWakeup {
for timerWakeup.Get() == 0 {
arm.Asm("wfi")
}
}
@@ -192,6 +190,6 @@ func handleTIM3() {
stm32.TIM3.SR.ClearBits(stm32.TIM_SR_UIF)
// timer was triggered
timerWakeup = true
timerWakeup.Set(1)
}
}
+5 -7
View File
@@ -6,6 +6,7 @@ import (
"device/arm"
"device/stm32"
"machine"
"runtime/volatile"
)
func init() {
@@ -114,10 +115,7 @@ var (
tickCount timeUnit
)
//go:volatile
type isrFlag bool
var timerWakeup isrFlag
var timerWakeup volatile.Register8
// Enable the TIM3 clock.(sleep count)
func initTIM3() {
@@ -160,7 +158,7 @@ func ticks() timeUnit {
// ticks are in microseconds
func timerSleep(ticks uint32) {
timerWakeup = false
timerWakeup.Set(0)
// CK_INT = APB1 x2 = 84mhz
// prescale counter down from 84mhz to 10khz aka 0.1 ms frequency.
@@ -180,7 +178,7 @@ func timerSleep(ticks uint32) {
stm32.TIM3.CR1.SetBits(stm32.TIM_CR1_CEN)
// wait till timer wakes up
for !timerWakeup {
for timerWakeup.Get() == 0 {
arm.Asm("wfi")
}
}
@@ -195,7 +193,7 @@ func handleTIM3() {
stm32.TIM3.SR.ClearBits(stm32.TIM_SR_UIF)
// timer was triggered
timerWakeup = true
timerWakeup.Set(1)
}
}
+19
View File
@@ -0,0 +1,19 @@
// +build tinygo.riscv
package runtime
import "unsafe"
// Implement memset for LLVM.
//go:export memset
func libc_memset(ptr unsafe.Pointer, c byte, size uintptr) {
for i := uintptr(0); i < size; i++ {
*(*byte)(unsafe.Pointer(uintptr(ptr) + i)) = c
}
}
// Implement memmove for LLVM.
//go:export memmove
func libc_memmove(dst, src unsafe.Pointer, size uintptr) {
memmove(dst, src, size)
}
+1 -1
View File
@@ -1,4 +1,4 @@
// +build darwin linux,!avr,!cortexm
// +build darwin linux,!avr,!cortexm,!tinygo.riscv
package runtime
+15
View File
@@ -89,6 +89,21 @@ func stringToBytes(x _string) (slice struct {
return
}
// Convert a string to []rune slice.
func stringToRunes(s string) []rune {
var n = 0
for range s {
n++
}
var r = make([]rune, n)
n = 0
for _, e := range s {
r[n] = e
n++
}
return r
}
// Create a string from a Unicode code point.
func stringFromUnicode(x rune) _string {
array, length := encodeUTF8(x)
+162
View File
@@ -0,0 +1,162 @@
package volatile
// This file defines Register{8,16,32} types, which are convenience types for
// volatile register accesses.
// Special types that causes loads/stores to be volatile (necessary for
// memory-mapped registers).
type Register8 struct {
Reg uint8
}
// Get returns the value in the register. It is the volatile equivalent of:
//
// *r.Reg
//
//go:inline
func (r *Register8) Get() uint8 {
return LoadUint8(&r.Reg)
}
// Set updates the register value. It is the volatile equivalent of:
//
// *r.Reg = value
//
//go:inline
func (r *Register8) Set(value uint8) {
StoreUint8(&r.Reg, value)
}
// SetBits reads the register, sets the given bits, and writes it back. It is
// the volatile equivalent of:
//
// r.Reg |= value
//
//go:inline
func (r *Register8) SetBits(value uint8) {
StoreUint8(&r.Reg, LoadUint8(&r.Reg)|value)
}
// ClearBits reads the register, clears the given bits, and writes it back. It
// is the volatile equivalent of:
//
// r.Reg &^= value
//
//go:inline
func (r *Register8) ClearBits(value uint8) {
StoreUint8(&r.Reg, LoadUint8(&r.Reg)&^value)
}
// HasBits reads the register and then checks to see if the passed bits are set. It
// is the volatile equivalent of:
//
// (*r.Reg & value) > 0
//
//go:inline
func (r *Register8) HasBits(value uint8) bool {
return (r.Get() & value) > 0
}
type Register16 struct {
Reg uint16
}
// Get returns the value in the register. It is the volatile equivalent of:
//
// *r.Reg
//
//go:inline
func (r *Register16) Get() uint16 {
return LoadUint16(&r.Reg)
}
// Set updates the register value. It is the volatile equivalent of:
//
// *r.Reg = value
//
//go:inline
func (r *Register16) Set(value uint16) {
StoreUint16(&r.Reg, value)
}
// SetBits reads the register, sets the given bits, and writes it back. It is
// the volatile equivalent of:
//
// r.Reg |= value
//
//go:inline
func (r *Register16) SetBits(value uint16) {
StoreUint16(&r.Reg, LoadUint16(&r.Reg)|value)
}
// ClearBits reads the register, clears the given bits, and writes it back. It
// is the volatile equivalent of:
//
// r.Reg &^= value
//
//go:inline
func (r *Register16) ClearBits(value uint16) {
StoreUint16(&r.Reg, LoadUint16(&r.Reg)&^value)
}
// HasBits reads the register and then checks to see if the passed bits are set. It
// is the volatile equivalent of:
//
// (*r.Reg & value) > 0
//
//go:inline
func (r *Register16) HasBits(value uint16) bool {
return (r.Get() & value) > 0
}
type Register32 struct {
Reg uint32
}
// Get returns the value in the register. It is the volatile equivalent of:
//
// *r.Reg
//
//go:inline
func (r *Register32) Get() uint32 {
return LoadUint32(&r.Reg)
}
// Set updates the register value. It is the volatile equivalent of:
//
// *r.Reg = value
//
//go:inline
func (r *Register32) Set(value uint32) {
StoreUint32(&r.Reg, value)
}
// SetBits reads the register, sets the given bits, and writes it back. It is
// the volatile equivalent of:
//
// r.Reg |= value
//
//go:inline
func (r *Register32) SetBits(value uint32) {
StoreUint32(&r.Reg, LoadUint32(&r.Reg)|value)
}
// ClearBits reads the register, clears the given bits, and writes it back. It
// is the volatile equivalent of:
//
// r.Reg &^= value
//
//go:inline
func (r *Register32) ClearBits(value uint32) {
StoreUint32(&r.Reg, LoadUint32(&r.Reg)&^value)
}
// HasBits reads the register and then checks to see if the passed bits are set. It
// is the volatile equivalent of:
//
// (*r.Reg & value) > 0
//
//go:inline
func (r *Register32) HasBits(value uint32) bool {
return (r.Get() & value) > 0
}
+3
View File
@@ -0,0 +1,3 @@
package syscall
func Exit(code int)
+6
View File
@@ -0,0 +1,6 @@
package testing
/*
This is a sad stub of the upstream testing package because it doesn't compile
with tinygo right now.
*/
+77
View File
@@ -0,0 +1,77 @@
package testing
import (
"bytes"
"fmt"
"io"
"os"
)
// T is a test helper.
type T struct {
name string
output io.Writer
// flags the test as having failed when non-zero
failed int
}
// TestToCall is a reference to a test that should be called during a test suite run.
type TestToCall struct {
// Name of the test to call.
Name string
// Function reference to the test.
Func func(*T)
}
// M is a test suite.
type M struct {
// tests is a list of the test names to execute
Tests []TestToCall
}
// Run the test suite.
func (m *M) Run() int {
failures := 0
for _, test := range m.Tests {
t := &T{
name: test.Name,
output: &bytes.Buffer{},
}
fmt.Printf("=== RUN %s\n", test.Name)
test.Func(t)
if t.failed == 0 {
fmt.Printf("--- PASS: %s\n", test.Name)
} else {
fmt.Printf("--- FAIL: %s\n", test.Name)
}
fmt.Println(t.output)
failures += t.failed
}
if failures > 0 {
fmt.Printf("exit status %d\n", failures)
fmt.Println("FAIL")
}
return failures
}
func TestMain(m *M) {
os.Exit(m.Run())
}
// Error is equivalent to Log followed by Fail
func (t *T) Error(args ...interface{}) {
// This doesn't print the same as in upstream go, but works good enough
// TODO: buffer test output like go does
fmt.Fprintf(t.output, "\t")
fmt.Fprintln(t.output, args...)
t.Fail()
}
func (t *T) Fail() {
t.failed = 1
}

Some files were not shown because too many files have changed in this diff Show More