mirror of
https://github.com/tinygo-org/tinygo.git
synced 2026-07-28 15:48:40 +00:00
Compare commits
71 Commits
shadowstack
...
v0.2.0
| Author | SHA1 | Date | |
|---|---|---|---|
| fbc2099ee3 | |||
| 95d895646a | |||
| 35fb594f8f | |||
| 01f6aff422 | |||
| 7dd5839f47 | |||
| 403fee7e06 | |||
| 7657238c24 | |||
| 3cba36f2ba | |||
| 93d5269fef | |||
| 4b477fad55 | |||
| 6360e318a7 | |||
| 0757eb5919 | |||
| 013a71aa3d | |||
| f0091b31b5 | |||
| 709a296150 | |||
| f7b2a2c977 | |||
| 6ae4b43eb2 | |||
| bece6b9648 | |||
| 553f00bdb8 | |||
| a789108926 | |||
| d63ce0646c | |||
| 003211b4ff | |||
| e6720d7ddc | |||
| dfef168139 | |||
| 101f2e519b | |||
| eb34afde4b | |||
| 63f2a3dfe9 | |||
| fb23e9c212 | |||
| 222c4c75b1 | |||
| 5a509f5bfe | |||
| 2e46275c45 | |||
| 1d34f868da | |||
| f0904779a5 | |||
| 70f1064f36 | |||
| 930de54dc5 | |||
| 25cd982263 | |||
| 9bbb233cf0 | |||
| 5b507593d2 | |||
| 05d2c14600 | |||
| e7ad366f20 | |||
| 8aed8d8c56 | |||
| c3a15885f5 | |||
| 914d383a96 | |||
| 504c82a0e7 | |||
| f8a1e5f449 | |||
| 0308c92e67 | |||
| 1db9a162da | |||
| 19b4476cbb | |||
| 7461c298dd | |||
| 9092dbcc53 | |||
| e6d90d89fa | |||
| 4f4d7976c6 | |||
| d820c36c4f | |||
| 85108514df | |||
| 589569fc35 | |||
| 2e4dd09bbc | |||
| 602c264749 | |||
| 072ef603fe | |||
| 54baf48266 | |||
| 072eb590a6 | |||
| c0ab91a263 | |||
| 3ebf464da2 | |||
| 38c5e384af | |||
| 65ea74bd84 | |||
| 683e2a66e1 | |||
| 8cbbbb0e76 | |||
| f89c695c8c | |||
| e2be7ccf76 | |||
| 1f511786d3 | |||
| c9f4e41073 | |||
| 67fbfe6305 |
+39
-22
@@ -1,33 +1,50 @@
|
||||
language: go
|
||||
|
||||
go:
|
||||
- "1.11"
|
||||
matrix:
|
||||
include:
|
||||
- dist: xenial
|
||||
go: "1.11"
|
||||
|
||||
before_install:
|
||||
- echo "deb http://apt.llvm.org/trusty/ llvm-toolchain-trusty-7 main" | sudo tee -a /etc/apt/sources.list
|
||||
- echo "deb http://ppa.launchpad.net/ubuntu-toolchain-r/test/ubuntu trusty main" | sudo tee -a /etc/apt/sources.list
|
||||
- sudo apt-get update -qq
|
||||
- sudo apt-get install llvm-7-dev clang-7 libclang-7-dev binutils-arm-none-eabi qemu-system-arm --allow-unauthenticated -y
|
||||
- sudo ln -s /usr/bin/clang-7 /usr/local/bin/cc # work around missing -no-pie in old GCC version
|
||||
addons:
|
||||
apt:
|
||||
sources:
|
||||
- sourceline: 'ppa:ubuntu-toolchain-r'
|
||||
- sourceline: 'deb http://apt.llvm.org/xenial/ llvm-toolchain-xenial-7 main'
|
||||
key_url: 'https://apt.llvm.org/llvm-snapshot.gpg.key'
|
||||
packages:
|
||||
- llvm-7-dev
|
||||
- clang-7
|
||||
- libclang-7-dev
|
||||
- gcc-arm-linux-gnueabi
|
||||
- binutils-arm-none-eabi
|
||||
- libc6-dev-armel-cross
|
||||
- gcc-aarch64-linux-gnu
|
||||
- libc6-dev-arm64-cross
|
||||
- qemu-system-arm
|
||||
- qemu-user
|
||||
- gcc-avr
|
||||
- avr-libc
|
||||
|
||||
install:
|
||||
- curl https://raw.githubusercontent.com/golang/dep/master/install.sh | sh
|
||||
- dep ensure --vendor-only
|
||||
|
||||
script:
|
||||
- go install github.com/aykevl/tinygo
|
||||
- go install github.com/tinygo-org/tinygo
|
||||
- go test -v .
|
||||
- make gen-device
|
||||
- tinygo build -o blinky1.nrf.elf -target=pca10040 examples/blinky1
|
||||
- tinygo build -o blinky2.nrf.elf -target=pca10040 examples/blinky2
|
||||
- tinygo build -o blinky2 examples/blinky2
|
||||
- tinygo build -o test.nrf.elf -target=pca10040 examples/test
|
||||
- tinygo build -o blinky1.nrf51.elf -target=microbit examples/echo
|
||||
- tinygo build -o test.nrf.elf -target=nrf52840-mdk examples/blinky1
|
||||
- tinygo build -o blinky1.nrf51d.elf -target=pca10031 examples/blinky1
|
||||
- tinygo build -o blinky1.stm32.elf -target=bluepill examples/blinky1
|
||||
- tinygo build -o blinky1.avr.o -target=arduino examples/blinky1 # TODO: avr-as/avr-gcc doesn't work
|
||||
- tinygo build -o blinky1.reel.elf -target=reelboard examples/blinky1
|
||||
- tinygo build -o blinky2.reel.elf -target=reelboard examples/blinky2
|
||||
- tinygo build -o blinky1.pca10056.elf -target=pca10056 examples/blinky1
|
||||
- tinygo build -o blinky2.pca10056.elf -target=pca10056 examples/blinky2
|
||||
- tinygo build -size short -o blinky1.nrf.elf -target=pca10040 examples/blinky1
|
||||
- tinygo build -size short -o blinky2.nrf.elf -target=pca10040 examples/blinky2
|
||||
- tinygo build -size short -o blinky2 examples/blinky2
|
||||
- tinygo build -size short -o test.nrf.elf -target=pca10040 examples/test
|
||||
- tinygo build -size short -o blinky1.nrf51.elf -target=microbit examples/echo
|
||||
- tinygo build -size short -o test.nrf.elf -target=nrf52840-mdk examples/blinky1
|
||||
- tinygo build -size short -o blinky1.nrf51d.elf -target=pca10031 examples/blinky1
|
||||
- tinygo build -size short -o blinky1.stm32.elf -target=bluepill examples/blinky1
|
||||
- tinygo build -size short -o blinky1.avr.elf -target=arduino examples/blinky1
|
||||
- tinygo build -size short -o blinky1.avr.elf -target=digispark examples/blinky1
|
||||
- tinygo build -size short -o blinky1.reel.elf -target=reelboard examples/blinky1
|
||||
- tinygo build -size short -o blinky2.reel.elf -target=reelboard examples/blinky2
|
||||
- tinygo build -size short -o blinky1.pca10056.elf -target=pca10056 examples/blinky1
|
||||
- tinygo build -size short -o blinky2.pca10056.elf -target=pca10056 examples/blinky2
|
||||
- tinygo build -size short -o blinky1.samd21.elf -target=itsybitsy-m0 examples/blinky1
|
||||
|
||||
+125
@@ -0,0 +1,125 @@
|
||||
# Building TinyGo
|
||||
|
||||
TinyGo depends on LLVM and libclang, which are both big C++ libraries. It can
|
||||
also optionally use a built-in lld to ease cross compiling. There are two ways
|
||||
these can be linked: dynamically and statically. The default is dynamic linking
|
||||
because it is fast and works almost out of the box on Debian-based systems with
|
||||
the right libraries installed.
|
||||
|
||||
This guide describes how to statically link TinyGo against LLVM, libclang and
|
||||
lld so that the binary can be easily moved between systems. It also shows how to
|
||||
build a release tarball that includes this binary and all necessary extra files.
|
||||
|
||||
## Dependencies
|
||||
|
||||
LLVM, Clang and LLD are quite light on dependencies, requiring only standard
|
||||
build tools to be built. Go is of course necessary to build TinyGo itself.
|
||||
|
||||
* Go (1.11+)
|
||||
* [dep](https://golang.github.io/dep/)
|
||||
* Standard build tools (gcc/clang)
|
||||
* git or subversion
|
||||
* CMake
|
||||
* [Ninja](https://ninja-build.org/) or make (preferably Ninja)
|
||||
|
||||
The rest of this guide assumes you're running Linux, but it should be equivalent
|
||||
on a different system like Mac.
|
||||
|
||||
## Download the source
|
||||
|
||||
The first step is to get the source code. Place it in some directory, assuming
|
||||
`$HOME/src` here, but you can pick a different one of course:
|
||||
|
||||
git clone -b release_70 https://github.com/llvm-mirror/llvm.git $HOME/src/llvm
|
||||
git clone -b release_70 https://github.com/llvm-mirror/clang.git $HOME/src/llvm/tools/clang
|
||||
git clone -b release_70 https://github.com/llvm-mirror/lld.git $HOME/src/llvm/tools/lld
|
||||
go get -d github.com/tinygo-org/tinygo
|
||||
cd $HOME/go/src/github.com/tinygo-org/tinygo
|
||||
dep ensure -vendor-only # download dependencies
|
||||
|
||||
Note that Clang and LLD must be placed inside the tools subdirectory of LLVM to
|
||||
be automatically built with the rest of the system.
|
||||
|
||||
## Build LLVM, Clang, LLD
|
||||
|
||||
Building LLVM is quite easy compared to some other software packages. However,
|
||||
the default configuration is _not_ optimized for distribution. It is optimized
|
||||
for development, meaning that binaries produce accurate error messages at the
|
||||
cost of huge binaries and slow compiles.
|
||||
|
||||
Before configuring, you may want to set the following environment variables to
|
||||
speed up the build. Most Linux distributions ship with GCC as the default
|
||||
compiler, but Clang is significantly faster and uses much less memory while
|
||||
producing binaries that are about as fast.
|
||||
|
||||
export CC=clang
|
||||
export CXX=clang++
|
||||
|
||||
Make a build directory. LLVM requires out-of-tree builds:
|
||||
|
||||
mkdir $HOME/src/llvm-build
|
||||
cd $HOME/src/llvm-build
|
||||
|
||||
Configure LLVM with CMake:
|
||||
|
||||
cmake -G Ninja ../llvm "-DLLVM_TARGETS_TO_BUILD=X86;ARM;AArch64" "-DLLVM_EXPERIMENTAL_TARGETS_TO_BUILD=AVR;WebAssembly" -DCMAKE_BUILD_TYPE=Release -DLLVM_ENABLE_ASSERTIONS=OFF -DLIBCLANG_BUILD_STATIC=ON
|
||||
|
||||
You can also choose a different build system than Ninja, but Ninja is fast.
|
||||
|
||||
There are various options you can tune here, but the options given above are
|
||||
preferable for releases. Here is what they do:
|
||||
|
||||
* `LLVM_TARGETS_TO_BUILD` and `LLVM_EXPERIMENTAL_TARGETS_TO_BUILD`: the
|
||||
targets that are natively supported by the LLVM code generators. The targets
|
||||
listed here are the ones supported by TinyGo. Note that LLVM is a cross
|
||||
compiler by default, unlike some other compilers.
|
||||
* `CMAKE_BUILD_TYPE`: the default is Debug, which produces large inefficient
|
||||
binaries that are easy to debug. We want small and fast binaries.
|
||||
* `LLVM_ENABLE_ASSERTIONS`: the default is ON, which greatly slows down LLVM
|
||||
and is only really useful during development. Disable them here.
|
||||
* `LIBCLANG_BUILD_STATIC`: unlike LLVM, libclang is built as a shared library
|
||||
by default. We want a static library for easy distribution.
|
||||
|
||||
Now build it:
|
||||
|
||||
ninja # or make, if you choose make in the previous step
|
||||
|
||||
This can take over an hour depending on the speed of your system.
|
||||
|
||||
## Build TinyGo
|
||||
|
||||
Now that you have a working version of LLVM, build TinyGo using it. You need to
|
||||
specify the directories to the LLVM build directory and to the Clang and LLD source.
|
||||
|
||||
cd $HOME/go/src/github.com/tinygo-org/tinygo
|
||||
make static LLVM_BUILDDIR=$HOME/src/llvm-build CLANG_SRC=$HOME/src/llvm/tools/clang LLD_SRC=$HOME/src/llvm/tools/lld
|
||||
|
||||
## Verify TinyGo
|
||||
|
||||
Try running TinyGo:
|
||||
|
||||
./build/tinygo help
|
||||
|
||||
Also, make sure the `tinygo` binary really is statically linked. Check this
|
||||
using `ldd` (not to be confused with `lld`):
|
||||
|
||||
ldd ./build/tinygo
|
||||
|
||||
The result should not contain libclang or libLLVM.
|
||||
|
||||
## Make a release tarball
|
||||
|
||||
Now that we have a working static build, it's time to make a release tarball.
|
||||
This is just a slight change from the command to build TinyGo:
|
||||
|
||||
cd $HOME/go/src/github.com/tinygo-org/tinygo
|
||||
make release LLVM_BUILDDIR=$HOME/src/llvm-build CLANG_SRC=$HOME/src/llvm/tools/clang LLD_SRC=$HOME/src/llvm/tools/lld
|
||||
|
||||
The release tarball is stored in build/release.tar.gz, and can be extracted with
|
||||
the following command:
|
||||
|
||||
tar -xvf path/to/release.tar.gz
|
||||
|
||||
TinyGo will get extracted to a `tinygo` directory. You can then call it with:
|
||||
|
||||
./tinygo/bin/tinygo
|
||||
@@ -0,0 +1,17 @@
|
||||
0.2.0
|
||||
---
|
||||
- **command line**
|
||||
- add version subcommand
|
||||
- **compiler**
|
||||
- fix a bug in floating point comparisons with NaN values
|
||||
- fix a bug when calling `panic` in package initialization code
|
||||
- add support for comparing `complex64` and `complex128`
|
||||
- **cgo**
|
||||
- add support for external globals
|
||||
- add support for pointers and function pointers
|
||||
- **standard library**
|
||||
- `fmt`: initial support, `fmt.Println` works
|
||||
- `math`: support for most/all functions
|
||||
- `os`: initial support (only stdin/stdout/stderr)
|
||||
- `reflect`: initial support
|
||||
- `syscall`: add support for amd64, arm, and arm64
|
||||
@@ -0,0 +1,48 @@
|
||||
# How to contribute
|
||||
|
||||
Thank you for your interest in improving TinyGo.
|
||||
|
||||
We would like your help to make this project better, so we appreciate any contributions. See if one of the following descriptions matches your situation:
|
||||
|
||||
### New to TinyGo
|
||||
|
||||
We'd love to get your feedback on getting started with TinyGo. Run into any difficulty, confusion, or anything else? You are not alone. We want to know about your experience, so we can help the next people. Please open a Github issue with your questions, or you can also get in touch directly with us on our Slack channel at [https://gophers.slack.com/messages/CDJD3SUP6](https://gophers.slack.com/messages/CDJD3SUP6).
|
||||
|
||||
### Something in TinyGo is not working as you expect
|
||||
|
||||
Please open a Github issue with your problem, and we will be happy to assist.
|
||||
|
||||
### Something in Go that you want/need does not appear to be in TinyGo
|
||||
|
||||
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.
|
||||
|
||||
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.
|
||||
|
||||
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.
|
||||
|
||||
## How to use our Github repository
|
||||
|
||||
The `master` branch of this repo will always have the latest released version of TinyGo. All of the active development work for the next release will take place in the `dev` branch. TinyGo will use semantic versioning and will create a tag/release for each release.
|
||||
|
||||
Here is how to contribute back some code or documentation:
|
||||
|
||||
- Fork repo
|
||||
- Create a feature branch off of the `dev` branch
|
||||
- Make some useful change
|
||||
- Make sure the tests still pass
|
||||
- Submit a pull request against the `dev` branch.
|
||||
- Be kind
|
||||
|
||||
## How to run tests
|
||||
|
||||
To run the tests:
|
||||
|
||||
```
|
||||
make test
|
||||
```
|
||||
+25
-22
@@ -4,22 +4,25 @@ FROM golang:latest AS tinygo-base
|
||||
RUN wget -O- https://apt.llvm.org/llvm-snapshot.gpg.key| apt-key add - && \
|
||||
echo "deb http://apt.llvm.org/stretch/ llvm-toolchain-stretch-7 main" >> /etc/apt/sources.list && \
|
||||
apt-get update && \
|
||||
apt-get install -y llvm-7-dev libclang-7-dev
|
||||
apt-get install -y llvm-7-dev libclang-7-dev git
|
||||
|
||||
RUN wget -O- https://raw.githubusercontent.com/golang/dep/master/install.sh | sh
|
||||
|
||||
COPY . /go/src/github.com/aykevl/tinygo
|
||||
COPY . /go/src/github.com/tinygo-org/tinygo
|
||||
|
||||
RUN cd /go/src/github.com/aykevl/tinygo/ && \
|
||||
RUN cd /go/src/github.com/tinygo-org/tinygo/ && \
|
||||
git submodule update --init
|
||||
|
||||
RUN cd /go/src/github.com/tinygo-org/tinygo/ && \
|
||||
dep ensure --vendor-only && \
|
||||
go install /go/src/github.com/aykevl/tinygo/
|
||||
go install /go/src/github.com/tinygo-org/tinygo/
|
||||
|
||||
# tinygo-wasm stage installs the needed dependencies to compile TinyGo programs for WASM.
|
||||
FROM tinygo-base AS tinygo-wasm
|
||||
|
||||
COPY --from=tinygo-base /go/bin/tinygo /go/bin/tinygo
|
||||
COPY --from=tinygo-base /go/src/github.com/aykevl/tinygo/src /go/src/github.com/aykevl/tinygo/src
|
||||
COPY --from=tinygo-base /go/src/github.com/aykevl/tinygo/targets /go/src/github.com/aykevl/tinygo/targets
|
||||
COPY --from=tinygo-base /go/src/github.com/tinygo-org/tinygo/src /go/src/github.com/tinygo-org/tinygo/src
|
||||
COPY --from=tinygo-base /go/src/github.com/tinygo-org/tinygo/targets /go/src/github.com/tinygo-org/tinygo/targets
|
||||
|
||||
RUN wget -O- https://apt.llvm.org/llvm-snapshot.gpg.key| apt-key add - && \
|
||||
echo "deb http://apt.llvm.org/stretch/ llvm-toolchain-stretch-7 main" >> /etc/apt/sources.list && \
|
||||
@@ -30,13 +33,13 @@ RUN wget -O- https://apt.llvm.org/llvm-snapshot.gpg.key| apt-key add - && \
|
||||
FROM tinygo-base AS tinygo-avr
|
||||
|
||||
COPY --from=tinygo-base /go/bin/tinygo /go/bin/tinygo
|
||||
COPY --from=tinygo-base /go/src/github.com/aykevl/tinygo/src /go/src/github.com/aykevl/tinygo/src
|
||||
COPY --from=tinygo-base /go/src/github.com/aykevl/tinygo/targets /go/src/github.com/aykevl/tinygo/targets
|
||||
COPY --from=tinygo-base /go/src/github.com/aykevl/tinygo/Makefile /go/src/github.com/aykevl/tinygo/
|
||||
COPY --from=tinygo-base /go/src/github.com/aykevl/tinygo/tools /go/src/github.com/aykevl/tinygo/tools
|
||||
COPY --from=tinygo-base /go/src/github.com/aykevl/tinygo/lib /go/src/github.com/aykevl/tinygo/lib
|
||||
COPY --from=tinygo-base /go/src/github.com/tinygo-org/tinygo/src /go/src/github.com/tinygo-org/tinygo/src
|
||||
COPY --from=tinygo-base /go/src/github.com/tinygo-org/tinygo/targets /go/src/github.com/tinygo-org/tinygo/targets
|
||||
COPY --from=tinygo-base /go/src/github.com/tinygo-org/tinygo/Makefile /go/src/github.com/tinygo-org/tinygo/
|
||||
COPY --from=tinygo-base /go/src/github.com/tinygo-org/tinygo/tools /go/src/github.com/tinygo-org/tinygo/tools
|
||||
COPY --from=tinygo-base /go/src/github.com/tinygo-org/tinygo/lib /go/src/github.com/tinygo-org/tinygo/lib
|
||||
|
||||
RUN cd /go/src/github.com/aykevl/tinygo/ && \
|
||||
RUN cd /go/src/github.com/tinygo-org/tinygo/ && \
|
||||
apt-get update && \
|
||||
apt-get install -y apt-utils python3 make binutils-avr gcc-avr avr-libc && \
|
||||
make gen-device-avr && \
|
||||
@@ -48,13 +51,13 @@ RUN cd /go/src/github.com/aykevl/tinygo/ && \
|
||||
FROM tinygo-base AS tinygo-arm
|
||||
|
||||
COPY --from=tinygo-base /go/bin/tinygo /go/bin/tinygo
|
||||
COPY --from=tinygo-base /go/src/github.com/aykevl/tinygo/src /go/src/github.com/aykevl/tinygo/src
|
||||
COPY --from=tinygo-base /go/src/github.com/aykevl/tinygo/targets /go/src/github.com/aykevl/tinygo/targets
|
||||
COPY --from=tinygo-base /go/src/github.com/aykevl/tinygo/Makefile /go/src/github.com/aykevl/tinygo/
|
||||
COPY --from=tinygo-base /go/src/github.com/aykevl/tinygo/tools /go/src/github.com/aykevl/tinygo/tools
|
||||
COPY --from=tinygo-base /go/src/github.com/aykevl/tinygo/lib /go/src/github.com/aykevl/tinygo/lib
|
||||
COPY --from=tinygo-base /go/src/github.com/tinygo-org/tinygo/src /go/src/github.com/tinygo-org/tinygo/src
|
||||
COPY --from=tinygo-base /go/src/github.com/tinygo-org/tinygo/targets /go/src/github.com/tinygo-org/tinygo/targets
|
||||
COPY --from=tinygo-base /go/src/github.com/tinygo-org/tinygo/Makefile /go/src/github.com/tinygo-org/tinygo/
|
||||
COPY --from=tinygo-base /go/src/github.com/tinygo-org/tinygo/tools /go/src/github.com/tinygo-org/tinygo/tools
|
||||
COPY --from=tinygo-base /go/src/github.com/tinygo-org/tinygo/lib /go/src/github.com/tinygo-org/tinygo/lib
|
||||
|
||||
RUN cd /go/src/github.com/aykevl/tinygo/ && \
|
||||
RUN cd /go/src/github.com/tinygo-org/tinygo/ && \
|
||||
apt-get update && \
|
||||
apt-get install -y apt-utils python3 make binutils-arm-none-eabi clang-7 && \
|
||||
make gen-device-nrf && make gen-device-stm32 && \
|
||||
@@ -65,11 +68,11 @@ RUN cd /go/src/github.com/aykevl/tinygo/ && \
|
||||
# tinygo-all stage installs the needed dependencies to compile TinyGo programs for all platforms.
|
||||
FROM tinygo-wasm AS tinygo-all
|
||||
|
||||
COPY --from=tinygo-base /go/src/github.com/aykevl/tinygo/Makefile /go/src/github.com/aykevl/tinygo/
|
||||
COPY --from=tinygo-base /go/src/github.com/aykevl/tinygo/tools /go/src/github.com/aykevl/tinygo/tools
|
||||
COPY --from=tinygo-base /go/src/github.com/aykevl/tinygo/lib /go/src/github.com/aykevl/tinygo/lib
|
||||
COPY --from=tinygo-base /go/src/github.com/tinygo-org/tinygo/Makefile /go/src/github.com/tinygo-org/tinygo/
|
||||
COPY --from=tinygo-base /go/src/github.com/tinygo-org/tinygo/tools /go/src/github.com/tinygo-org/tinygo/tools
|
||||
COPY --from=tinygo-base /go/src/github.com/tinygo-org/tinygo/lib /go/src/github.com/tinygo-org/tinygo/lib
|
||||
|
||||
RUN cd /go/src/github.com/aykevl/tinygo/ && \
|
||||
RUN cd /go/src/github.com/tinygo-org/tinygo/ && \
|
||||
apt-get update && \
|
||||
apt-get install -y apt-utils python3 make binutils-arm-none-eabi clang-7 binutils-avr gcc-avr avr-libc && \
|
||||
make gen-device && \
|
||||
|
||||
Generated
+12
-23
@@ -3,42 +3,31 @@
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
digest = "1:f250e2a6d7e4f9ebc5ba37e5e2ec91b46eb1399ee43f2fdaeb20cd4fd1aeee59"
|
||||
name = "github.com/aykevl/go-llvm"
|
||||
packages = ["."]
|
||||
pruneopts = "UT"
|
||||
revision = "d8539684f173a591ea9474d6262ac47ef2277d64"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
digest = "1:d1102ae84d8c9318db4ce2ad2673eb2bf54569ab2a4a5d57e70d8aef726b681d"
|
||||
digest = "1:ba70784a3deee74c0ca3c87bcac3c2f93d3b2d27d8f237b768c358b45ba47da8"
|
||||
name = "golang.org/x/tools"
|
||||
packages = [
|
||||
"go/ast/astutil",
|
||||
"go/buildutil",
|
||||
"go/gcexportdata",
|
||||
"go/internal/cgo",
|
||||
"go/internal/gcimporter",
|
||||
"go/loader",
|
||||
"go/packages",
|
||||
"go/ssa",
|
||||
"go/ssa/ssautil",
|
||||
"go/types/typeutil",
|
||||
"internal/fastwalk",
|
||||
"internal/gopathwalk",
|
||||
"internal/semver",
|
||||
]
|
||||
pruneopts = "UT"
|
||||
revision = "3e7aa9e59977626dc60433e9aeadf1bb63d28295"
|
||||
revision = "40960b6deb8ecdb8bcde6a8f44722731939b8ddc"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
digest = "1:3611159788efdd4e0cfae18b6ebcccbad25a2815968b0e4323b42647d201031a"
|
||||
name = "tinygo.org/x/go-llvm"
|
||||
packages = ["."]
|
||||
pruneopts = "UT"
|
||||
revision = "f420620d1a0f54417a5712260153fe861780d030"
|
||||
|
||||
[solve-meta]
|
||||
analyzer-name = "dep"
|
||||
analyzer-version = 1
|
||||
input-imports = [
|
||||
"github.com/aykevl/go-llvm",
|
||||
"golang.org/x/tools/go/loader",
|
||||
"golang.org/x/tools/go/ast/astutil",
|
||||
"golang.org/x/tools/go/ssa",
|
||||
"golang.org/x/tools/go/ssa/ssautil",
|
||||
"tinygo.org/x/go-llvm",
|
||||
]
|
||||
solver-name = "gps-cdcl"
|
||||
solver-version = 1
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
[[constraint]]
|
||||
branch = "master"
|
||||
name = "github.com/aykevl/go-llvm"
|
||||
name = "tinygo.org/x/go-llvm"
|
||||
|
||||
[[constraint]]
|
||||
branch = "master"
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
|
||||
# aliases
|
||||
all: tgo
|
||||
tgo: build/tgo
|
||||
all: tinygo
|
||||
tinygo: build/tinygo
|
||||
|
||||
.PHONY: all tgo run-test run-blinky run-blinky2 clean fmt gen-device gen-device-nrf gen-device-avr
|
||||
.PHONY: all tinygo static run-test run-blinky run-blinky2 clean fmt gen-device gen-device-nrf gen-device-avr
|
||||
|
||||
TARGET ?= unix
|
||||
|
||||
@@ -40,6 +40,18 @@ $(error Unknown target)
|
||||
|
||||
endif
|
||||
|
||||
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
|
||||
|
||||
CLANG_LIBS = -Wl,--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 -Wl,--end-group -lstdc++
|
||||
|
||||
LLD_LIBS = -Wl,--start-group -llldCOFF -llldCommon -llldCore -llldDriver -llldELF -llldMachO -llldMinGW -llldReaderWriter -llldWasm -llldYAML -Wl,--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))
|
||||
|
||||
|
||||
|
||||
run-test: build/test
|
||||
@@ -70,7 +82,7 @@ clean:
|
||||
@rm -rf build
|
||||
|
||||
fmt:
|
||||
@go fmt . ./compiler ./interp ./loader ./ir ./src/device/arm ./src/examples/* ./src/machine ./src/runtime ./src/sync
|
||||
@go fmt . ./compiler ./interp ./loader ./ir ./src/device/arm ./src/examples/* ./src/machine ./src/os ./src/runtime ./src/sync
|
||||
@go fmt ./testdata/*.go
|
||||
|
||||
test:
|
||||
@@ -96,17 +108,42 @@ gen-device-stm32:
|
||||
go fmt ./src/device/stm32
|
||||
|
||||
# Build the Go compiler.
|
||||
build/tgo: *.go compiler/*.go interp/*.go loader/*.go ir/*.go
|
||||
tinygo:
|
||||
@mkdir -p build
|
||||
go build -o build/tgo -i .
|
||||
go build -o build/tinygo .
|
||||
|
||||
static:
|
||||
CGO_CPPFLAGS="$(CGO_CPPFLAGS)" CGO_CXXFLAGS="$(CGO_CXXFLAGS)" CGO_LDFLAGS="$(CGO_LDFLAGS)" go build -o build/tinygo -tags byollvm .
|
||||
|
||||
release: static gen-device
|
||||
@mkdir -p build/release/tinygo/bin
|
||||
@mkdir -p build/release/tinygo/lib/CMSIS/CMSIS
|
||||
@mkdir -p build/release/tinygo/lib/compiler-rt/lib
|
||||
@mkdir -p build/release/tinygo/lib/nrfx
|
||||
@mkdir -p build/release/tinygo/pkg/armv6m-none-eabi
|
||||
@mkdir -p build/release/tinygo/pkg/armv7m-none-eabi
|
||||
@mkdir -p build/release/tinygo/pkg/armv7em-none-eabi
|
||||
@cp -p build/tinygo build/release/tinygo/bin
|
||||
@cp -rp lib/CMSIS/CMSIS/Include build/release/tinygo/lib/CMSIS/CMSIS
|
||||
@cp -rp lib/CMSIS/README.md build/release/tinygo/lib/CMSIS
|
||||
@cp -rp lib/compiler-rt/lib/builtins build/release/tinygo/lib/compiler-rt/lib
|
||||
@cp -rp lib/compiler-rt/LICENSE.TXT build/release/tinygo/lib/compiler-rt
|
||||
@cp -rp lib/compiler-rt/README.txt build/release/tinygo/lib/compiler-rt
|
||||
@cp -rp lib/nrfx/* build/release/tinygo/lib/nrfx
|
||||
@cp -rp src build/release/tinygo/src
|
||||
@cp -rp targets build/release/tinygo/targets
|
||||
./build/tinygo build-builtins -target=armv6m-none-eabi -o build/release/tinygo/pkg/armv6m-none-eabi/compiler-rt.a
|
||||
./build/tinygo build-builtins -target=armv7m-none-eabi -o build/release/tinygo/pkg/armv7m-none-eabi/compiler-rt.a
|
||||
./build/tinygo build-builtins -target=armv7em-none-eabi -o build/release/tinygo/pkg/armv7em-none-eabi/compiler-rt.a
|
||||
tar -czf build/release.tar.gz -C build/release tinygo
|
||||
|
||||
# Binary that can run on the host.
|
||||
build/%: src/examples/% src/examples/%/*.go build/tgo src/runtime/*.go
|
||||
./build/tgo build $(TGOFLAGS) -size=short -o $@ $(subst src/,,$<)
|
||||
build/%: src/examples/% src/examples/%/*.go build/tinygo src/runtime/*.go
|
||||
./build/tinygo build $(TGOFLAGS) -size=short -o $@ $(subst src/,,$<)
|
||||
|
||||
# ELF file that can run on a microcontroller.
|
||||
build/%.elf: src/examples/% src/examples/%/*.go build/tgo src/runtime/*.go
|
||||
./build/tgo build $(TGOFLAGS) -size=short -o $@ $(subst src/,,$<)
|
||||
build/%.elf: src/examples/% src/examples/%/*.go build/tinygo src/runtime/*.go
|
||||
./build/tinygo build $(TGOFLAGS) -size=short -o $@ $(subst src/,,$<)
|
||||
|
||||
# Convert executable to Intel hex file (for flashing).
|
||||
build/%.hex: build/%.elf
|
||||
|
||||
@@ -1,144 +1,70 @@
|
||||
# TinyGo - Go compiler for microcontrollers
|
||||
# TinyGo - Go compiler for small places
|
||||
|
||||
[](https://travis-ci.com/aykevl/tinygo)
|
||||
[](https://travis-ci.com/tinygo-org/tinygo)
|
||||
|
||||
> We never expected Go to be an embedded language and so it's got serious
|
||||
> problems [...].
|
||||
TinyGo is a Go compiler intended for use in small places such as microcontrollers, WebAssembly (WASM), and command-line tools.
|
||||
|
||||
-- Rob Pike, [GopherCon 2014 Opening Keynote](https://www.youtube.com/watch?v=VoS7DsT1rdM&feature=youtu.be&t=2799)
|
||||
It reuses libraries used by the [Go language tools](https://golang.org/pkg/go/) alongside [LLVM](http://llvm.org) to provide an alternative way to compile programs written in the Go programming language.
|
||||
|
||||
TinyGo is a project to bring Go to microcontrollers and small systems with a
|
||||
single processor core. It is similar to [emgo](https://github.com/ziutek/emgo)
|
||||
but a major difference is that I want to keep the Go memory model (which implies
|
||||
garbage collection of some sort). Another difference is that TinyGo uses LLVM
|
||||
internally instead of emitting C, which hopefully leads to smaller and more
|
||||
efficient code and certainly leads to more flexibility.
|
||||
|
||||
My original reasoning was: if [Python](https://micropython.org/) can run on
|
||||
microcontrollers, then certainly [Go](https://golang.org/) should be able to and
|
||||
run on even lower level micros.
|
||||
|
||||
Example program (blinky):
|
||||
Here is an example program that blinks the built-in LED when run directly on any supported board with onboard LED:
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"machine"
|
||||
"time"
|
||||
"machine"
|
||||
"time"
|
||||
)
|
||||
|
||||
func main() {
|
||||
led := machine.GPIO{machine.LED}
|
||||
led.Configure(machine.GPIOConfig{Mode: machine.GPIO_OUTPUT})
|
||||
for {
|
||||
led.Low()
|
||||
time.Sleep(time.Millisecond * 1000)
|
||||
led := machine.GPIO{machine.LED}
|
||||
led.Configure(machine.GPIOConfig{Mode: machine.GPIO_OUTPUT})
|
||||
for {
|
||||
led.Low()
|
||||
time.Sleep(time.Millisecond * 1000)
|
||||
|
||||
led.High()
|
||||
time.Sleep(time.Millisecond * 1000)
|
||||
}
|
||||
led.High()
|
||||
time.Sleep(time.Millisecond * 1000)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Currently supported features:
|
||||
The above program can be compiled and run without modification on an Arduino Uno, an Adafruit ItsyBitsy M0, or any of the supported boards that have a built-in LED, just by setting the correct TinyGo compiler target. For example, this compiles and flashes an Arduino Uno:
|
||||
|
||||
* control flow
|
||||
* many (but not all) basic types: most ints, floats, strings, structs
|
||||
* function calling
|
||||
* interfaces for basic types (with type switches and asserts)
|
||||
* goroutines (very initial support)
|
||||
* function pointers (non-blocking)
|
||||
* interface methods
|
||||
* standard library (but most packages won't work due to missing language
|
||||
features)
|
||||
* slices (partially)
|
||||
* maps (very rough, unfinished)
|
||||
* defer
|
||||
* closures
|
||||
* bound methods
|
||||
* complex numbers (except for arithmetic)
|
||||
|
||||
Not yet supported:
|
||||
|
||||
* complex arithmetic
|
||||
* garbage collection
|
||||
* recover
|
||||
* channels
|
||||
* introspection (if it ever gets implemented)
|
||||
* ...
|
||||
```shell
|
||||
tinygo flash -target arduino examples/blinky1
|
||||
```
|
||||
|
||||
## Installation
|
||||
|
||||
See the [getting started instructions](https://tinygo.org/getting-started/).
|
||||
See the [getting started instructions](https://tinygo.org/getting-started/) for information on how to install TinyGo, as well as how to run the TinyGo compiler using our Docker container.
|
||||
|
||||
### Running with Docker
|
||||
## Supported boards/targets
|
||||
|
||||
A docker container exists for easy access to the `tinygo` CLI:
|
||||
You can compile TinyGo programs for microcontrollers, WebAssembly and Linux.
|
||||
|
||||
```sh
|
||||
$ docker run --rm -v $(pwd):/src tinygo/tinygo tinygo build -o /src/wasm.wasm -target wasm examples/wasm
|
||||
```
|
||||
The following microcontroller boards are currently supported:
|
||||
|
||||
Note that you cannot run `tinygo flash` from inside the docker container,
|
||||
so it is less useful for microcontroller development.
|
||||
* [Adafruit ItsyBitsy M0](https://www.adafruit.com/product/3727)
|
||||
* [Arduino Uno](https://store.arduino.cc/arduino-uno-rev3)
|
||||
* [BBC:Microbit](https://microbit.org/)
|
||||
* [ST Micro STM32F103XX "Bluepill"](http://wiki.stm32duino.com/index.php?title=Blue_Pill)
|
||||
* [Digispark](http://digistump.com/products/1)
|
||||
* [Nordic Semiconductor PCA10031](https://www.nordicsemi.com/eng/Products/nRF51-Dongle)
|
||||
* [Nordic Semiconductor PCA10040](https://www.nordicsemi.com/eng/Products/Bluetooth-low-energy/nRF52-DK)
|
||||
* [Nordic Semiconductor PCA10056](https://www.nordicsemi.com/Software-and-Tools/Development-Kits/nRF52840-DK)
|
||||
* [Makerdiary nRF52840-MDK](https://wiki.makerdiary.com/nrf52840-mdk/)
|
||||
* [Phytec reel board](https://www.phytec.eu/product-eu/internet-of-things/reelboard/)
|
||||
|
||||
## Supported targets
|
||||
For more information, see [this list of boards](https://tinygo.org/microcontrollers/). Pull requests for additional support are welcome!
|
||||
|
||||
The following architectures/systems are currently supported:
|
||||
## Currently supported features:
|
||||
|
||||
* ARM (Cortex-M)
|
||||
* AVR (Arduino Uno)
|
||||
* Linux
|
||||
* WebAssembly
|
||||
|
||||
For more information, see [this list of targets and
|
||||
boards](https://tinygo.org/targets/). Pull requests for
|
||||
broader support are welcome!
|
||||
|
||||
## Analysis and optimizations
|
||||
|
||||
The goal is to reduce code size (and increase performance) by performing all
|
||||
kinds of whole-program analysis passes. The official Go compiler doesn't do a
|
||||
whole lot of analysis (except for escape analysis) because it needs to be fast,
|
||||
but embedded programs are necessarily smaller so it becomes practical. And I
|
||||
think especially program size can be reduced by a large margin when actually
|
||||
trying to optimize for it.
|
||||
|
||||
Implemented compiler passes:
|
||||
|
||||
* Analyse which functions are blocking. Blocking functions are functions that
|
||||
call sleep, chan send, etc. Its parents are also blocking.
|
||||
* Analyse whether the scheduler is needed. It is only needed when there are
|
||||
`go` statements for blocking functions.
|
||||
* Analyse whether a given type switch or type assert is possible with
|
||||
[type-based alias analysis](https://en.wikipedia.org/wiki/Alias_analysis#Type-based_alias_analysis).
|
||||
I would like to use flow-based alias analysis in the future, if feasible.
|
||||
* Do basic dead code elimination of functions. This pass makes later passes
|
||||
better and probably improves compile time as well.
|
||||
|
||||
## Scope
|
||||
|
||||
Goals:
|
||||
|
||||
* Have very small binary sizes. Don't pay for what you don't use.
|
||||
* Support for most common microcontroller boards.
|
||||
* Be usable on the web using WebAssembly.
|
||||
* Good CGo support, with no more overhead than a regular function call.
|
||||
* Support most standard library packages and compile most Go code without
|
||||
modification.
|
||||
|
||||
Non-goals:
|
||||
|
||||
* Using more than one core.
|
||||
* Be efficient while using zillions of goroutines. However, good goroutine
|
||||
support is certainly a goal.
|
||||
* Be as fast as `gc`. However, LLVM will probably be better at optimizing
|
||||
certain things so TinyGo might actually turn out to be faster for number
|
||||
crunching.
|
||||
* Be able to compile every Go program out there.
|
||||
For a description of currently supported Go language features, please see [https://tinygo.org/lang-support/](https://tinygo.org/lang-support/).
|
||||
|
||||
## Documentation
|
||||
|
||||
Documentation is currently maintained on a dedicated web site located at [https://tinygo.org/](https://tinygo.org/).
|
||||
Documentation is located on our web site at [https://tinygo.org/](https://tinygo.org/).
|
||||
|
||||
You can find the web site code at [https://github.com/tinygo-org/tinygo-site](https://github.com/tinygo-org/tinygo-site).
|
||||
|
||||
@@ -153,26 +79,37 @@ should arrive fairly quickly (under 1 min): https://invite.slack.golangbridge.or
|
||||
|
||||
## Contributing
|
||||
|
||||
Patches are welcome!
|
||||
Your contributions are welcome!
|
||||
|
||||
If you want to contribute, here are some suggestions:
|
||||
Please take a look at our [CONTRIBUTING.md](./CONTRIBUTING.md) document for details.
|
||||
|
||||
* 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.
|
||||
* 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/aykevl/tinygo/wiki/Adding-a-new-board).
|
||||
* Microcontrollers have lots of peripherals and many don't have an
|
||||
implementation yet in the `machine` package. Adding support for new
|
||||
peripherals is very useful.
|
||||
* Just raising bugs for things you'd like to see implemented is also a form of
|
||||
contributing! It helps prioritization.
|
||||
## Project Scope
|
||||
|
||||
Goals:
|
||||
|
||||
* Have very small binary sizes. Don't pay for what you don't use.
|
||||
* Support for most common microcontroller boards.
|
||||
* Be usable on the web using WebAssembly.
|
||||
* Good CGo support, with no more overhead than a regular function call.
|
||||
* Support most standard library packages and compile most Go code without modification.
|
||||
|
||||
Non-goals:
|
||||
|
||||
* Using more than one core.
|
||||
* Be efficient while using zillions of goroutines. However, good goroutine support is certainly a goal.
|
||||
* Be as fast as `gc`. However, LLVM will probably be better at optimizing certain things so TinyGo might actually turn out to be faster for number crunching.
|
||||
* Be able to compile every Go program out there.
|
||||
|
||||
## Why this project exists
|
||||
|
||||
> We never expected Go to be an embedded language and so its got serious problems...
|
||||
|
||||
-- Rob Pike, [GopherCon 2014 Opening Keynote](https://www.youtube.com/watch?v=VoS7DsT1rdM&feature=youtu.be&t=2799)
|
||||
|
||||
TinyGo is a project to bring Go to microcontrollers and small systems with a single processor core. It is similar to [emgo](https://github.com/ziutek/emgo) but a major difference is that we want to keep the Go memory model (which implies garbage collection of some sort). Another difference is that TinyGo uses LLVM internally instead of emitting C, which hopefully leads to smaller and more efficient code and certainly leads to more flexibility.
|
||||
|
||||
The original reasoning was: if [Python](https://micropython.org/) can run on microcontrollers, then certainly [Go](https://golang.org/) should be able to run on even lower level micros.
|
||||
|
||||
## License
|
||||
|
||||
This project is licensed under the BSD 3-clause license, just like the
|
||||
[Go project](https://golang.org/LICENSE) itself.
|
||||
This project is licensed under the BSD 3-clause license, just like the [Go project](https://golang.org/LICENSE) itself.
|
||||
|
||||
+38
-22
@@ -80,29 +80,45 @@ func cacheStore(tmppath, name, configKey string, sourceFiles []string) (string,
|
||||
return "", err
|
||||
}
|
||||
cachepath := filepath.Join(dir, name)
|
||||
err = os.Rename(tmppath, cachepath)
|
||||
err = moveFile(tmppath, cachepath)
|
||||
if err != nil {
|
||||
inf, err := os.Open(tmppath)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
defer inf.Close()
|
||||
outf, err := os.Create(cachepath + ".tmp")
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
_, err = io.Copy(outf, inf)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
err = os.Rename(cachepath+".tmp", cachepath)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return cachepath, outf.Close()
|
||||
return "", err
|
||||
}
|
||||
return cachepath, nil
|
||||
}
|
||||
|
||||
// moveFile renames the file from src to dst. If renaming doesn't work (for
|
||||
// example, the rename crosses a filesystem boundary), the file is copied and
|
||||
// the old file is removed.
|
||||
func moveFile(src, dst string) error {
|
||||
err := os.Rename(src, dst)
|
||||
if err == nil {
|
||||
// Success!
|
||||
return nil
|
||||
}
|
||||
// Failed to move, probably a different filesystem.
|
||||
// Do a copy + remove.
|
||||
inf, err := os.Open(src)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer inf.Close()
|
||||
outpath := dst + ".tmp"
|
||||
outf, err := os.Create(outpath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = io.Copy(outf, inf)
|
||||
if err != nil {
|
||||
os.Remove(outpath)
|
||||
return err
|
||||
}
|
||||
|
||||
err = os.Rename(dst+".tmp", dst)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return outf.Close()
|
||||
}
|
||||
|
||||
+50
-8
@@ -157,16 +157,29 @@ var aeabiBuiltins = []string{
|
||||
|
||||
func builtinFiles(target string) []string {
|
||||
builtins := append([]string{}, genericBuiltins...) // copy genericBuiltins
|
||||
if target[:3] == "arm" {
|
||||
if strings.HasPrefix(target, "arm") {
|
||||
builtins = append(builtins, aeabiBuiltins...)
|
||||
}
|
||||
return builtins
|
||||
}
|
||||
|
||||
// builtinsDir returns the directory where the sources for compiler-rt are kept.
|
||||
func builtinsDir() string {
|
||||
return filepath.Join(sourceDir(), "lib", "compiler-rt", "lib", "builtins")
|
||||
}
|
||||
|
||||
// Get the builtins archive, possibly generating it as needed.
|
||||
func loadBuiltins(target string) (path string, err error) {
|
||||
// Try to load a precompiled compiler-rt library.
|
||||
precompiledPath := filepath.Join(sourceDir(), "pkg", target, "compiler-rt.a")
|
||||
if _, err := os.Stat(precompiledPath); err == nil {
|
||||
// Found a precompiled compiler-rt for this OS/architecture. Return the
|
||||
// path directly.
|
||||
return precompiledPath, nil
|
||||
}
|
||||
|
||||
outfile := "librt-" + target + ".a"
|
||||
builtinsDir := filepath.Join(sourceDir(), "lib", "compiler-rt", "lib", "builtins")
|
||||
builtinsDir := builtinsDir()
|
||||
|
||||
builtins := builtinFiles(target)
|
||||
srcs := make([]string, len(builtins))
|
||||
@@ -178,9 +191,33 @@ func loadBuiltins(target string) (path string, err error) {
|
||||
return path, err
|
||||
}
|
||||
|
||||
dir, err := ioutil.TempDir("", "tinygo-builtins")
|
||||
var cachepath string
|
||||
err = compileBuiltins(target, func(path string) error {
|
||||
path, err := cacheStore(path, outfile, commands["clang"], srcs)
|
||||
cachepath = path
|
||||
return err
|
||||
})
|
||||
return cachepath, err
|
||||
}
|
||||
|
||||
// compileBuiltins compiles builtins from compiler-rt into a static library.
|
||||
// When it succeeds, it will call the callback with the resulting path. The path
|
||||
// will be removed after callback returns. If callback returns an error, this is
|
||||
// passed through to the return value of this function.
|
||||
func compileBuiltins(target string, callback func(path string) error) error {
|
||||
builtinsDir := builtinsDir()
|
||||
|
||||
builtins := builtinFiles(target)
|
||||
srcs := make([]string, len(builtins))
|
||||
for i, name := range builtins {
|
||||
srcs[i] = filepath.Join(builtinsDir, name)
|
||||
}
|
||||
|
||||
dirPrefix := "tinygo-builtins"
|
||||
remapDir := filepath.Join(os.TempDir(), dirPrefix)
|
||||
dir, err := ioutil.TempDir(os.TempDir(), dirPrefix)
|
||||
if err != nil {
|
||||
return "", err
|
||||
return err
|
||||
}
|
||||
defer os.RemoveAll(dir)
|
||||
|
||||
@@ -195,13 +232,16 @@ func loadBuiltins(target string) (path string, err error) {
|
||||
objpath := filepath.Join(dir, objname+".o")
|
||||
objs = append(objs, objpath)
|
||||
srcpath := filepath.Join(builtinsDir, name)
|
||||
cmd := exec.Command(commands["clang"], "-c", "-Oz", "-g", "-Werror", "-Wall", "-std=c11", "-fshort-enums", "-nostdlibinc", "-ffunction-sections", "-fdata-sections", "--target="+target, "-o", objpath, srcpath)
|
||||
// Note: -fdebug-prefix-map is necessary to make the output archive
|
||||
// reproducible. Otherwise the temporary directory is stored in the
|
||||
// archive itself, which varies each run.
|
||||
cmd := exec.Command(commands["clang"], "-c", "-Oz", "-g", "-Werror", "-Wall", "-std=c11", "-fshort-enums", "-nostdlibinc", "-ffunction-sections", "-fdata-sections", "--target="+target, "-fdebug-prefix-map="+dir+"="+remapDir, "-o", objpath, srcpath)
|
||||
cmd.Stdout = os.Stdout
|
||||
cmd.Stderr = os.Stderr
|
||||
cmd.Dir = dir
|
||||
err = cmd.Run()
|
||||
if err != nil {
|
||||
return "", err
|
||||
return &commandError{"failed to build", srcpath, err}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -213,8 +253,10 @@ func loadBuiltins(target string) (path string, err error) {
|
||||
cmd.Dir = dir
|
||||
err = cmd.Run()
|
||||
if err != nil {
|
||||
return "", err
|
||||
return &commandError{"failed to make static library", arpath, err}
|
||||
}
|
||||
|
||||
return cacheStore(arpath, outfile, commands["clang"], srcs)
|
||||
// Give the caller the resulting file. The callback must copy the file,
|
||||
// because after it returns the temporary directory will be removed.
|
||||
return callback(arpath)
|
||||
}
|
||||
|
||||
+3
-2
@@ -1,8 +1,8 @@
|
||||
package compiler
|
||||
|
||||
import (
|
||||
"github.com/aykevl/go-llvm"
|
||||
"golang.org/x/tools/go/ssa"
|
||||
"tinygo.org/x/go-llvm"
|
||||
)
|
||||
|
||||
// For a description of the calling convention in prose, see:
|
||||
@@ -21,7 +21,8 @@ func (c *Compiler) createRuntimeCall(fnName string, args []llvm.Value, name stri
|
||||
}
|
||||
fn := c.ir.GetFunction(member.(*ssa.Function))
|
||||
if !fn.IsExported() {
|
||||
args = append(args, llvm.Undef(c.i8ptrType)) // unused context parameter
|
||||
args = append(args, llvm.Undef(c.i8ptrType)) // unused context parameter
|
||||
args = append(args, llvm.ConstPointerNull(c.i8ptrType)) // coroutine handle
|
||||
}
|
||||
return c.createCall(fn.LLVMFn, args, name)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,97 @@
|
||||
package compiler
|
||||
|
||||
// This file lowers channel operations (make/send/recv/close) to runtime calls
|
||||
// or pseudo-operations that are lowered during goroutine lowering.
|
||||
|
||||
import (
|
||||
"go/types"
|
||||
|
||||
"golang.org/x/tools/go/ssa"
|
||||
"tinygo.org/x/go-llvm"
|
||||
)
|
||||
|
||||
// emitMakeChan returns a new channel value for the given channel type.
|
||||
func (c *Compiler) emitMakeChan(expr *ssa.MakeChan) (llvm.Value, error) {
|
||||
valueType, err := c.getLLVMType(expr.Type().(*types.Chan).Elem())
|
||||
if err != nil {
|
||||
return llvm.Value{}, err
|
||||
}
|
||||
if c.targetData.TypeAllocSize(valueType) > c.targetData.TypeAllocSize(c.intType) {
|
||||
// Values bigger than int overflow the data part of the coroutine.
|
||||
// TODO: make the coroutine data part big enough to hold these bigger
|
||||
// values.
|
||||
return llvm.Value{}, c.makeError(expr.Pos(), "todo: channel with values bigger than int")
|
||||
}
|
||||
chanType := c.mod.GetTypeByName("runtime.channel")
|
||||
size := c.targetData.TypeAllocSize(chanType)
|
||||
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")
|
||||
return ptr, nil
|
||||
}
|
||||
|
||||
// emitChanSend emits a pseudo chan send operation. It is lowered to the actual
|
||||
// channel send operation during goroutine lowering.
|
||||
func (c *Compiler) emitChanSend(frame *Frame, instr *ssa.Send) error {
|
||||
valueType, err := c.getLLVMType(instr.Chan.Type().(*types.Chan).Elem())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
ch, err := c.parseExpr(frame, instr.Chan)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
chanValue, err := c.parseExpr(frame, instr.X)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
valueSize := llvm.ConstInt(c.uintptrType, c.targetData.TypeAllocSize(chanValue.Type()), false)
|
||||
valueAlloca := c.builder.CreateAlloca(valueType, "chan.value")
|
||||
c.builder.CreateStore(chanValue, valueAlloca)
|
||||
valueAllocaCast := c.builder.CreateBitCast(valueAlloca, c.i8ptrType, "chan.value.i8ptr")
|
||||
c.createRuntimeCall("chanSendStub", []llvm.Value{llvm.Undef(c.i8ptrType), ch, valueAllocaCast, valueSize}, "")
|
||||
return nil
|
||||
}
|
||||
|
||||
// emitChanRecv emits a pseudo chan receive operation. It is lowered to the
|
||||
// actual channel receive operation during goroutine lowering.
|
||||
func (c *Compiler) emitChanRecv(frame *Frame, unop *ssa.UnOp) (llvm.Value, error) {
|
||||
valueType, err := c.getLLVMType(unop.X.Type().(*types.Chan).Elem())
|
||||
if err != nil {
|
||||
return llvm.Value{}, err
|
||||
}
|
||||
valueSize := llvm.ConstInt(c.uintptrType, c.targetData.TypeAllocSize(valueType), false)
|
||||
ch, err := c.parseExpr(frame, unop.X)
|
||||
if err != nil {
|
||||
return llvm.Value{}, err
|
||||
}
|
||||
valueAlloca := c.builder.CreateAlloca(valueType, "chan.value")
|
||||
valueAllocaCast := c.builder.CreateBitCast(valueAlloca, c.i8ptrType, "chan.value.i8ptr")
|
||||
valueOk := c.builder.CreateAlloca(c.ctx.Int1Type(), "chan.comma-ok.alloca")
|
||||
c.createRuntimeCall("chanRecvStub", []llvm.Value{llvm.Undef(c.i8ptrType), ch, valueAllocaCast, valueOk, valueSize}, "")
|
||||
received := c.builder.CreateLoad(valueAlloca, "chan.received")
|
||||
if unop.CommaOk {
|
||||
commaOk := c.builder.CreateLoad(valueOk, "chan.comma-ok")
|
||||
tuple := llvm.Undef(c.ctx.StructType([]llvm.Type{valueType, c.ctx.Int1Type()}, false))
|
||||
tuple = c.builder.CreateInsertValue(tuple, received, 0, "")
|
||||
tuple = c.builder.CreateInsertValue(tuple, commaOk, 1, "")
|
||||
return tuple, nil
|
||||
} else {
|
||||
return received, nil
|
||||
}
|
||||
}
|
||||
|
||||
// emitChanClose closes the given channel.
|
||||
func (c *Compiler) emitChanClose(frame *Frame, param ssa.Value) error {
|
||||
valueType, err := c.getLLVMType(param.Type().(*types.Chan).Elem())
|
||||
valueSize := llvm.ConstInt(c.uintptrType, c.targetData.TypeAllocSize(valueType), false)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
ch, err := c.parseExpr(frame, param)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
c.createRuntimeCall("chanClose", []llvm.Value{ch, valueSize}, "")
|
||||
return nil
|
||||
}
|
||||
+197
-260
@@ -14,10 +14,10 @@ import (
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/aykevl/go-llvm"
|
||||
"github.com/aykevl/tinygo/ir"
|
||||
"github.com/aykevl/tinygo/loader"
|
||||
"github.com/tinygo-org/tinygo/ir"
|
||||
"github.com/tinygo-org/tinygo/loader"
|
||||
"golang.org/x/tools/go/ssa"
|
||||
"tinygo.org/x/go-llvm"
|
||||
)
|
||||
|
||||
func init() {
|
||||
@@ -59,12 +59,6 @@ type Compiler struct {
|
||||
intType llvm.Type
|
||||
i8ptrType llvm.Type // for convenience
|
||||
uintptrType llvm.Type
|
||||
coroIdFunc llvm.Value
|
||||
coroSizeFunc llvm.Value
|
||||
coroBeginFunc llvm.Value
|
||||
coroSuspendFunc llvm.Value
|
||||
coroEndFunc llvm.Value
|
||||
coroFreeFunc llvm.Value
|
||||
initFuncs []llvm.Value
|
||||
interfaceInvokeWrappers []interfaceInvokeWrapper
|
||||
ir *ir.Program
|
||||
@@ -77,10 +71,7 @@ type Frame struct {
|
||||
blockExits map[*ssa.BasicBlock]llvm.BasicBlock // these are the exit blocks
|
||||
currentBlock *ssa.BasicBlock
|
||||
phis []Phi
|
||||
blocking bool
|
||||
taskHandle llvm.Value
|
||||
cleanupBlock llvm.BasicBlock
|
||||
suspendBlock llvm.BasicBlock
|
||||
deferPtr llvm.Value
|
||||
difunc llvm.Metadata
|
||||
allDeferFuncs []interface{}
|
||||
@@ -119,7 +110,9 @@ func NewCompiler(pkgName string, config Config) (*Compiler, error) {
|
||||
c.mod.SetTarget(config.Triple)
|
||||
c.mod.SetDataLayout(c.targetData.String())
|
||||
c.builder = c.ctx.NewBuilder()
|
||||
c.dibuilder = llvm.NewDIBuilder(c.mod)
|
||||
if c.Debug {
|
||||
c.dibuilder = llvm.NewDIBuilder(c.mod)
|
||||
}
|
||||
|
||||
c.uintptrType = c.ctx.IntType(c.targetData.PointerSize() * 8)
|
||||
if c.targetData.PointerSize() <= 4 {
|
||||
@@ -133,24 +126,6 @@ func NewCompiler(pkgName string, config Config) (*Compiler, error) {
|
||||
}
|
||||
c.i8ptrType = llvm.PointerType(c.ctx.Int8Type(), 0)
|
||||
|
||||
coroIdType := llvm.FunctionType(c.ctx.TokenType(), []llvm.Type{c.ctx.Int32Type(), c.i8ptrType, c.i8ptrType, c.i8ptrType}, false)
|
||||
c.coroIdFunc = llvm.AddFunction(c.mod, "llvm.coro.id", coroIdType)
|
||||
|
||||
coroSizeType := llvm.FunctionType(c.ctx.Int32Type(), nil, false)
|
||||
c.coroSizeFunc = llvm.AddFunction(c.mod, "llvm.coro.size.i32", coroSizeType)
|
||||
|
||||
coroBeginType := llvm.FunctionType(c.i8ptrType, []llvm.Type{c.ctx.TokenType(), c.i8ptrType}, false)
|
||||
c.coroBeginFunc = llvm.AddFunction(c.mod, "llvm.coro.begin", coroBeginType)
|
||||
|
||||
coroSuspendType := llvm.FunctionType(c.ctx.Int8Type(), []llvm.Type{c.ctx.TokenType(), c.ctx.Int1Type()}, false)
|
||||
c.coroSuspendFunc = llvm.AddFunction(c.mod, "llvm.coro.suspend", coroSuspendType)
|
||||
|
||||
coroEndType := llvm.FunctionType(c.ctx.Int1Type(), []llvm.Type{c.i8ptrType, c.ctx.Int1Type()}, false)
|
||||
c.coroEndFunc = llvm.AddFunction(c.mod, "llvm.coro.end", coroEndType)
|
||||
|
||||
coroFreeType := llvm.FunctionType(c.i8ptrType, []llvm.Type{c.ctx.TokenType(), c.i8ptrType}, false)
|
||||
c.coroFreeFunc = llvm.AddFunction(c.mod, "llvm.coro.free", coroFreeType)
|
||||
|
||||
return c, nil
|
||||
}
|
||||
|
||||
@@ -237,21 +212,19 @@ func (c *Compiler) Compile(mainPath string) error {
|
||||
|
||||
c.ir = ir.NewProgram(lprogram, mainPath)
|
||||
|
||||
// Run some DCE and analysis passes. The results are later used by the
|
||||
// compiler.
|
||||
c.ir.SimpleDCE() // remove most dead code
|
||||
c.ir.AnalyseCallgraph() // set up callgraph
|
||||
c.ir.AnalyseBlockingRecursive() // make all parents of blocking calls blocking (transitively)
|
||||
c.ir.AnalyseGoCalls() // check whether we need a scheduler
|
||||
// Run a simple dead code elimination pass.
|
||||
c.ir.SimpleDCE()
|
||||
|
||||
// Initialize debug information.
|
||||
c.cu = c.dibuilder.CreateCompileUnit(llvm.DICompileUnit{
|
||||
Language: llvm.DW_LANG_Go,
|
||||
File: mainPath,
|
||||
Dir: "",
|
||||
Producer: "TinyGo",
|
||||
Optimized: true,
|
||||
})
|
||||
if c.Debug {
|
||||
c.cu = c.dibuilder.CreateCompileUnit(llvm.DICompileUnit{
|
||||
Language: llvm.DW_LANG_Go,
|
||||
File: mainPath,
|
||||
Dir: "",
|
||||
Producer: "TinyGo",
|
||||
Optimized: true,
|
||||
})
|
||||
}
|
||||
|
||||
var frames []*Frame
|
||||
|
||||
@@ -387,43 +360,33 @@ func (c *Compiler) Compile(mainPath string) error {
|
||||
block := c.ctx.AddBasicBlock(initFn.LLVMFn, "entry")
|
||||
c.builder.SetInsertPointAtEnd(block)
|
||||
for _, fn := range c.initFuncs {
|
||||
c.builder.CreateCall(fn, []llvm.Value{llvm.Undef(c.i8ptrType)}, "")
|
||||
c.builder.CreateCall(fn, []llvm.Value{llvm.Undef(c.i8ptrType), llvm.Undef(c.i8ptrType)}, "")
|
||||
}
|
||||
c.builder.CreateRetVoid()
|
||||
|
||||
// Add a wrapper for the main.main function, either calling it directly or
|
||||
// setting up the scheduler with it.
|
||||
mainWrapper := c.ir.GetFunction(c.ir.Program.ImportedPackage("runtime").Members["mainWrapper"].(*ssa.Function))
|
||||
mainWrapper.LLVMFn.SetLinkage(llvm.InternalLinkage)
|
||||
mainWrapper.LLVMFn.SetUnnamedAddr(true)
|
||||
if c.Debug {
|
||||
difunc, err := c.attachDebugInfo(mainWrapper)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
pos := c.ir.Program.Fset.Position(mainWrapper.Pos())
|
||||
c.builder.SetCurrentDebugLocation(uint(pos.Line), uint(pos.Column), difunc, llvm.Metadata{})
|
||||
}
|
||||
block = c.ctx.AddBasicBlock(mainWrapper.LLVMFn, "entry")
|
||||
c.builder.SetInsertPointAtEnd(block)
|
||||
// Conserve for goroutine lowering. Without marking these as external, they
|
||||
// would be optimized away.
|
||||
realMain := c.mod.NamedFunction(c.ir.MainPkg().Pkg.Path() + ".main")
|
||||
if c.ir.NeedsScheduler() {
|
||||
coroutine := c.builder.CreateCall(realMain, []llvm.Value{llvm.ConstPointerNull(c.i8ptrType), llvm.Undef(c.i8ptrType)}, "")
|
||||
c.createRuntimeCall("scheduler", []llvm.Value{coroutine}, "")
|
||||
} else {
|
||||
c.builder.CreateCall(realMain, []llvm.Value{llvm.Undef(c.i8ptrType)}, "")
|
||||
}
|
||||
c.builder.CreateRetVoid()
|
||||
realMain.SetLinkage(llvm.ExternalLinkage) // keep alive until goroutine lowering
|
||||
c.mod.NamedFunction("runtime.alloc").SetLinkage(llvm.ExternalLinkage)
|
||||
c.mod.NamedFunction("runtime.free").SetLinkage(llvm.ExternalLinkage)
|
||||
c.mod.NamedFunction("runtime.chanSend").SetLinkage(llvm.ExternalLinkage)
|
||||
c.mod.NamedFunction("runtime.chanRecv").SetLinkage(llvm.ExternalLinkage)
|
||||
c.mod.NamedFunction("runtime.sleepTask").SetLinkage(llvm.ExternalLinkage)
|
||||
c.mod.NamedFunction("runtime.activateTask").SetLinkage(llvm.ExternalLinkage)
|
||||
c.mod.NamedFunction("runtime.scheduler").SetLinkage(llvm.ExternalLinkage)
|
||||
|
||||
// see: https://reviews.llvm.org/D18355
|
||||
c.mod.AddNamedMetadataOperand("llvm.module.flags",
|
||||
c.ctx.MDNode([]llvm.Metadata{
|
||||
llvm.ConstInt(c.ctx.Int32Type(), 1, false).ConstantAsMetadata(), // Error on mismatch
|
||||
llvm.GlobalContext().MDString("Debug Info Version"),
|
||||
llvm.ConstInt(c.ctx.Int32Type(), 3, false).ConstantAsMetadata(), // DWARF version
|
||||
}),
|
||||
)
|
||||
c.dibuilder.Finalize()
|
||||
if c.Debug {
|
||||
c.mod.AddNamedMetadataOperand("llvm.module.flags",
|
||||
c.ctx.MDNode([]llvm.Metadata{
|
||||
llvm.ConstInt(c.ctx.Int32Type(), 1, false).ConstantAsMetadata(), // Error on mismatch
|
||||
llvm.GlobalContext().MDString("Debug Info Version"),
|
||||
llvm.ConstInt(c.ctx.Int32Type(), 3, false).ConstantAsMetadata(), // DWARF version
|
||||
}),
|
||||
)
|
||||
c.dibuilder.Finalize()
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -535,7 +498,8 @@ func (c *Compiler) getLLVMType(goType types.Type) (llvm.Type, error) {
|
||||
}
|
||||
// make a closure type (with a function pointer type inside):
|
||||
// {context, funcptr}
|
||||
paramTypes = append(paramTypes, c.i8ptrType)
|
||||
paramTypes = append(paramTypes, c.i8ptrType) // context
|
||||
paramTypes = append(paramTypes, c.i8ptrType) // parent coroutine
|
||||
ptr := llvm.PointerType(llvm.FunctionType(returnType, paramTypes, false), 0)
|
||||
ptr = c.ctx.StructType([]llvm.Type{c.i8ptrType, ptr}, false)
|
||||
return ptr, nil
|
||||
@@ -676,16 +640,10 @@ func (c *Compiler) parseFuncDecl(f *ir.Function) (*Frame, error) {
|
||||
locals: make(map[ssa.Value]llvm.Value),
|
||||
blockEntries: make(map[*ssa.BasicBlock]llvm.BasicBlock),
|
||||
blockExits: make(map[*ssa.BasicBlock]llvm.BasicBlock),
|
||||
blocking: c.ir.IsBlocking(f),
|
||||
}
|
||||
|
||||
var retType llvm.Type
|
||||
if frame.blocking {
|
||||
if f.Signature.Results() != nil {
|
||||
return nil, c.makeError(f.Function.Pos(), "todo: return values in blocking function")
|
||||
}
|
||||
retType = c.i8ptrType
|
||||
} else if f.Signature.Results() == nil {
|
||||
if f.Signature.Results() == nil {
|
||||
retType = c.ctx.VoidType()
|
||||
} else if f.Signature.Results().Len() == 1 {
|
||||
var err error
|
||||
@@ -706,9 +664,6 @@ func (c *Compiler) parseFuncDecl(f *ir.Function) (*Frame, error) {
|
||||
}
|
||||
|
||||
var paramTypes []llvm.Type
|
||||
if frame.blocking {
|
||||
paramTypes = append(paramTypes, c.i8ptrType) // parent coroutine
|
||||
}
|
||||
for _, param := range f.Params {
|
||||
paramType, err := c.getLLVMType(param.Type())
|
||||
if err != nil {
|
||||
@@ -721,7 +676,8 @@ func (c *Compiler) parseFuncDecl(f *ir.Function) (*Frame, error) {
|
||||
// Add an extra parameter as the function context. This context is used in
|
||||
// closures and bound methods, but should be optimized away when not used.
|
||||
if !f.IsExported() {
|
||||
paramTypes = append(paramTypes, c.i8ptrType)
|
||||
paramTypes = append(paramTypes, c.i8ptrType) // context
|
||||
paramTypes = append(paramTypes, c.i8ptrType) // parent coroutine
|
||||
}
|
||||
|
||||
fnType := llvm.FunctionType(retType, paramTypes, false)
|
||||
@@ -1095,10 +1051,6 @@ func (c *Compiler) parseFunc(frame *Frame) error {
|
||||
frame.blockEntries[block] = llvmBlock
|
||||
frame.blockExits[block] = llvmBlock
|
||||
}
|
||||
if frame.blocking {
|
||||
frame.cleanupBlock = c.ctx.AddBasicBlock(frame.fn.LLVMFn, "task.cleanup")
|
||||
frame.suspendBlock = c.ctx.AddBasicBlock(frame.fn.LLVMFn, "task.suspend")
|
||||
}
|
||||
entryBlock := frame.blockEntries[frame.fn.Blocks[0]]
|
||||
c.builder.SetInsertPointAtEnd(entryBlock)
|
||||
|
||||
@@ -1137,10 +1089,14 @@ func (c *Compiler) parseFunc(frame *Frame) error {
|
||||
|
||||
// Load free variables from the context. This is a closure (or bound
|
||||
// method).
|
||||
if len(frame.fn.FreeVars) != 0 {
|
||||
context := frame.fn.LLVMFn.LastParam()
|
||||
var context llvm.Value
|
||||
if !frame.fn.IsExported() {
|
||||
parentHandle := frame.fn.LLVMFn.LastParam()
|
||||
parentHandle.SetName("parentHandle")
|
||||
context = llvm.PrevParam(parentHandle)
|
||||
context.SetName("context")
|
||||
|
||||
}
|
||||
if len(frame.fn.FreeVars) != 0 {
|
||||
// Determine the context type. It's a struct containing all variables.
|
||||
freeVarTypes := make([]llvm.Type, 0, len(frame.fn.FreeVars))
|
||||
for _, freeVar := range frame.fn.FreeVars {
|
||||
@@ -1186,39 +1142,6 @@ func (c *Compiler) parseFunc(frame *Frame) error {
|
||||
c.deferInitFunc(frame)
|
||||
}
|
||||
|
||||
if frame.blocking {
|
||||
// Coroutine initialization.
|
||||
taskState := c.builder.CreateAlloca(c.mod.GetTypeByName("runtime.taskState"), "task.state")
|
||||
stateI8 := c.builder.CreateBitCast(taskState, c.i8ptrType, "task.state.i8")
|
||||
id := c.builder.CreateCall(c.coroIdFunc, []llvm.Value{
|
||||
llvm.ConstInt(c.ctx.Int32Type(), 0, false),
|
||||
stateI8,
|
||||
llvm.ConstNull(c.i8ptrType),
|
||||
llvm.ConstNull(c.i8ptrType),
|
||||
}, "task.token")
|
||||
size := c.builder.CreateCall(c.coroSizeFunc, nil, "task.size")
|
||||
if c.targetData.TypeAllocSize(size.Type()) > c.targetData.TypeAllocSize(c.uintptrType) {
|
||||
size = c.builder.CreateTrunc(size, c.uintptrType, "task.size.uintptr")
|
||||
} else if c.targetData.TypeAllocSize(size.Type()) < c.targetData.TypeAllocSize(c.uintptrType) {
|
||||
size = c.builder.CreateZExt(size, c.uintptrType, "task.size.uintptr")
|
||||
}
|
||||
data := c.createRuntimeCall("alloc", []llvm.Value{size}, "task.data")
|
||||
frame.taskHandle = c.builder.CreateCall(c.coroBeginFunc, []llvm.Value{id, data}, "task.handle")
|
||||
|
||||
// Coroutine cleanup. Free resources associated with this coroutine.
|
||||
c.builder.SetInsertPointAtEnd(frame.cleanupBlock)
|
||||
mem := c.builder.CreateCall(c.coroFreeFunc, []llvm.Value{id, frame.taskHandle}, "task.data.free")
|
||||
c.createRuntimeCall("free", []llvm.Value{mem}, "")
|
||||
// re-insert parent coroutine
|
||||
c.createRuntimeCall("yieldToScheduler", []llvm.Value{frame.fn.LLVMFn.FirstParam()}, "")
|
||||
c.builder.CreateBr(frame.suspendBlock)
|
||||
|
||||
// Coroutine suspend. A call to llvm.coro.suspend() will branch here.
|
||||
c.builder.SetInsertPointAtEnd(frame.suspendBlock)
|
||||
c.builder.CreateCall(c.coroEndFunc, []llvm.Value{frame.taskHandle, llvm.ConstInt(c.ctx.Int1Type(), 0, false)}, "unused")
|
||||
c.builder.CreateRet(frame.taskHandle)
|
||||
}
|
||||
|
||||
// Fill blocks with instructions.
|
||||
for _, block := range frame.fn.DomPreorder() {
|
||||
if c.DumpSSA {
|
||||
@@ -1283,25 +1206,38 @@ func (c *Compiler) parseInstr(frame *Frame, instr ssa.Instruction) error {
|
||||
case *ssa.Defer:
|
||||
return c.emitDefer(frame, instr)
|
||||
case *ssa.Go:
|
||||
if instr.Common().Method != nil {
|
||||
if instr.Call.IsInvoke() {
|
||||
return c.makeError(instr.Pos(), "todo: go on method receiver")
|
||||
}
|
||||
callee := instr.Call.StaticCallee()
|
||||
if callee == nil {
|
||||
return c.makeError(instr.Pos(), "todo: go on non-direct function (function pointer, etc.)")
|
||||
}
|
||||
calleeFn := c.ir.GetFunction(callee)
|
||||
|
||||
// Execute non-blocking calls (including builtins) directly.
|
||||
// parentHandle param is ignored.
|
||||
if !c.ir.IsBlocking(c.ir.GetFunction(instr.Common().Value.(*ssa.Function))) {
|
||||
_, err := c.parseCall(frame, instr.Common(), llvm.Value{})
|
||||
return err // probably nil
|
||||
// Mark this function as a 'go' invocation and break invalid
|
||||
// interprocedural optimizations. For example, heap-to-stack
|
||||
// transformations are not sound as goroutines can outlive their parent.
|
||||
calleeType := calleeFn.LLVMFn.Type()
|
||||
calleeValue := c.builder.CreateBitCast(calleeFn.LLVMFn, c.i8ptrType, "")
|
||||
calleeValue = c.createRuntimeCall("makeGoroutine", []llvm.Value{calleeValue}, "")
|
||||
calleeValue = c.builder.CreateBitCast(calleeValue, calleeType, "")
|
||||
|
||||
// Get all function parameters to pass to the goroutine.
|
||||
var params []llvm.Value
|
||||
for _, param := range instr.Call.Args {
|
||||
val, err := c.parseExpr(frame, param)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
params = append(params, val)
|
||||
}
|
||||
if !calleeFn.IsExported() {
|
||||
params = append(params, llvm.Undef(c.i8ptrType)) // context parameter
|
||||
params = append(params, llvm.Undef(c.i8ptrType)) // parent coroutine handle
|
||||
}
|
||||
|
||||
// Start this goroutine.
|
||||
// parentHandle is nil, as the goroutine has no parent frame (it's a new
|
||||
// stack).
|
||||
handle, err := c.parseCall(frame, instr.Common(), llvm.Value{})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
c.createRuntimeCall("yieldToScheduler", []llvm.Value{handle}, "")
|
||||
c.createCall(calleeValue, params, "")
|
||||
return nil
|
||||
case *ssa.If:
|
||||
cond, err := c.parseExpr(frame, instr.Cond)
|
||||
@@ -1341,48 +1277,36 @@ func (c *Compiler) parseInstr(frame *Frame, instr ssa.Instruction) error {
|
||||
c.builder.CreateUnreachable()
|
||||
return nil
|
||||
case *ssa.Return:
|
||||
if frame.blocking {
|
||||
if len(instr.Results) != 0 {
|
||||
return c.makeError(instr.Pos(), "todo: return values from blocking function")
|
||||
if len(instr.Results) == 0 {
|
||||
c.builder.CreateRetVoid()
|
||||
return nil
|
||||
} else if len(instr.Results) == 1 {
|
||||
val, err := c.parseExpr(frame, instr.Results[0])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// Final suspend.
|
||||
continuePoint := c.builder.CreateCall(c.coroSuspendFunc, []llvm.Value{
|
||||
llvm.ConstNull(c.ctx.TokenType()),
|
||||
llvm.ConstInt(c.ctx.Int1Type(), 1, false), // final=true
|
||||
}, "")
|
||||
sw := c.builder.CreateSwitch(continuePoint, frame.suspendBlock, 2)
|
||||
sw.AddCase(llvm.ConstInt(c.ctx.Int8Type(), 1, false), frame.cleanupBlock)
|
||||
c.builder.CreateRet(val)
|
||||
return nil
|
||||
} else {
|
||||
if len(instr.Results) == 0 {
|
||||
c.builder.CreateRetVoid()
|
||||
return nil
|
||||
} else if len(instr.Results) == 1 {
|
||||
val, err := c.parseExpr(frame, instr.Results[0])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
c.builder.CreateRet(val)
|
||||
return nil
|
||||
} else {
|
||||
// Multiple return values. Put them all in a struct.
|
||||
retVal, err := c.getZeroValue(frame.fn.LLVMFn.Type().ElementType().ReturnType())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for i, result := range instr.Results {
|
||||
val, err := c.parseExpr(frame, result)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
retVal = c.builder.CreateInsertValue(retVal, val, i, "")
|
||||
}
|
||||
c.builder.CreateRet(retVal)
|
||||
return nil
|
||||
// Multiple return values. Put them all in a struct.
|
||||
retVal, err := c.getZeroValue(frame.fn.LLVMFn.Type().ElementType().ReturnType())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for i, result := range instr.Results {
|
||||
val, err := c.parseExpr(frame, result)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
retVal = c.builder.CreateInsertValue(retVal, val, i, "")
|
||||
}
|
||||
c.builder.CreateRet(retVal)
|
||||
return nil
|
||||
}
|
||||
case *ssa.RunDefers:
|
||||
return c.emitRunDefers(frame)
|
||||
case *ssa.Send:
|
||||
return c.emitChanSend(frame, instr)
|
||||
case *ssa.Store:
|
||||
llvmAddr, err := c.parseExpr(frame, instr.Addr)
|
||||
if err == ir.ErrCGoWrapper {
|
||||
@@ -1401,7 +1325,7 @@ func (c *Compiler) parseInstr(frame *Frame, instr ssa.Instruction) error {
|
||||
return nil
|
||||
}
|
||||
store := c.builder.CreateStore(llvmVal, llvmAddr)
|
||||
valType := instr.Addr.Type().(*types.Pointer).Elem()
|
||||
valType := instr.Addr.Type().Underlying().(*types.Pointer).Elem()
|
||||
if c.ir.IsVolatile(valType) {
|
||||
// Volatile store, for memory-mapped registers.
|
||||
store.SetVolatile(true)
|
||||
@@ -1448,11 +1372,17 @@ func (c *Compiler) parseBuiltin(frame *Frame, args []ssa.Value, callName string,
|
||||
return llvm.Value{}, err
|
||||
}
|
||||
switch args[0].Type().(type) {
|
||||
case *types.Chan:
|
||||
// Channel. Buffered channels haven't been implemented yet so always
|
||||
// return 0.
|
||||
return llvm.ConstInt(c.intType, 0, false), nil
|
||||
case *types.Slice:
|
||||
return c.builder.CreateExtractValue(value, 2, "cap"), nil
|
||||
default:
|
||||
return llvm.Value{}, c.makeError(pos, "todo: cap: unknown type")
|
||||
}
|
||||
case "close":
|
||||
return llvm.Value{}, c.emitChanClose(frame, args[0])
|
||||
case "complex":
|
||||
r, err := c.parseExpr(frame, args[0])
|
||||
if err != nil {
|
||||
@@ -1520,6 +1450,10 @@ func (c *Compiler) parseBuiltin(frame *Frame, args []ssa.Value, callName string,
|
||||
case *types.Basic, *types.Slice:
|
||||
// string or slice
|
||||
llvmLen = c.builder.CreateExtractValue(value, 1, "len")
|
||||
case *types.Chan:
|
||||
// Channel. Buffered channels haven't been implemented yet so always
|
||||
// return 0.
|
||||
llvmLen = llvm.ConstInt(c.intType, 0, false)
|
||||
case *types.Map:
|
||||
llvmLen = c.createRuntimeCall("hashmapLen", []llvm.Value{value}, "len")
|
||||
default:
|
||||
@@ -1606,17 +1540,8 @@ func (c *Compiler) parseBuiltin(frame *Frame, args []ssa.Value, callName string,
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Compiler) parseFunctionCall(frame *Frame, args []ssa.Value, llvmFn, context llvm.Value, blocking bool, parentHandle llvm.Value) (llvm.Value, error) {
|
||||
func (c *Compiler) parseFunctionCall(frame *Frame, args []ssa.Value, llvmFn, context llvm.Value, exported bool) (llvm.Value, error) {
|
||||
var params []llvm.Value
|
||||
if blocking {
|
||||
if parentHandle.IsNil() {
|
||||
// Started from 'go' statement.
|
||||
params = append(params, llvm.ConstNull(c.i8ptrType))
|
||||
} else {
|
||||
// Blocking function calls another blocking function.
|
||||
params = append(params, parentHandle)
|
||||
}
|
||||
}
|
||||
for _, param := range args {
|
||||
val, err := c.parseExpr(frame, param)
|
||||
if err != nil {
|
||||
@@ -1625,60 +1550,20 @@ func (c *Compiler) parseFunctionCall(frame *Frame, args []ssa.Value, llvmFn, con
|
||||
params = append(params, val)
|
||||
}
|
||||
|
||||
if !context.IsNil() {
|
||||
if !exported {
|
||||
// This function takes a context parameter.
|
||||
// Add it to the end of the parameter list.
|
||||
params = append(params, context)
|
||||
|
||||
// Parent coroutine handle.
|
||||
params = append(params, llvm.Undef(c.i8ptrType))
|
||||
}
|
||||
|
||||
if frame.blocking && llvmFn.Name() == "time.Sleep" {
|
||||
// Set task state to TASK_STATE_SLEEP and set the duration.
|
||||
c.createRuntimeCall("sleepTask", []llvm.Value{frame.taskHandle, params[0]}, "")
|
||||
|
||||
// Yield to scheduler.
|
||||
continuePoint := c.builder.CreateCall(c.coroSuspendFunc, []llvm.Value{
|
||||
llvm.ConstNull(c.ctx.TokenType()),
|
||||
llvm.ConstInt(c.ctx.Int1Type(), 0, false),
|
||||
}, "")
|
||||
wakeup := c.ctx.InsertBasicBlock(llvm.NextBasicBlock(c.builder.GetInsertBlock()), "task.wakeup")
|
||||
sw := c.builder.CreateSwitch(continuePoint, frame.suspendBlock, 2)
|
||||
sw.AddCase(llvm.ConstInt(c.ctx.Int8Type(), 0, false), wakeup)
|
||||
sw.AddCase(llvm.ConstInt(c.ctx.Int8Type(), 1, false), frame.cleanupBlock)
|
||||
c.builder.SetInsertPointAtEnd(wakeup)
|
||||
|
||||
return llvm.Value{}, nil
|
||||
}
|
||||
|
||||
result := c.createCall(llvmFn, params, "")
|
||||
if blocking && !parentHandle.IsNil() {
|
||||
// Calling a blocking function as a regular function call.
|
||||
// This is done by passing the current coroutine as a parameter to the
|
||||
// new coroutine and dropping the current coroutine from the scheduler
|
||||
// (with the TASK_STATE_CALL state). When the subroutine is finished, it
|
||||
// will reactivate the parent (this frame) in it's destroy function.
|
||||
|
||||
c.createRuntimeCall("yieldToScheduler", []llvm.Value{result}, "")
|
||||
|
||||
// Set task state to TASK_STATE_CALL.
|
||||
c.createRuntimeCall("waitForAsyncCall", []llvm.Value{frame.taskHandle}, "")
|
||||
|
||||
// Yield to the scheduler.
|
||||
continuePoint := c.builder.CreateCall(c.coroSuspendFunc, []llvm.Value{
|
||||
llvm.ConstNull(c.ctx.TokenType()),
|
||||
llvm.ConstInt(c.ctx.Int1Type(), 0, false),
|
||||
}, "")
|
||||
resume := c.ctx.InsertBasicBlock(llvm.NextBasicBlock(c.builder.GetInsertBlock()), "task.callComplete")
|
||||
sw := c.builder.CreateSwitch(continuePoint, frame.suspendBlock, 2)
|
||||
sw.AddCase(llvm.ConstInt(c.ctx.Int8Type(), 0, false), resume)
|
||||
sw.AddCase(llvm.ConstInt(c.ctx.Int8Type(), 1, false), frame.cleanupBlock)
|
||||
c.builder.SetInsertPointAtEnd(resume)
|
||||
}
|
||||
return result, nil
|
||||
return c.createCall(llvmFn, params, ""), nil
|
||||
}
|
||||
|
||||
func (c *Compiler) parseCall(frame *Frame, instr *ssa.CallCommon, parentHandle llvm.Value) (llvm.Value, error) {
|
||||
func (c *Compiler) parseCall(frame *Frame, instr *ssa.CallCommon) (llvm.Value, error) {
|
||||
if instr.IsInvoke() {
|
||||
// TODO: blocking methods (needs analysis)
|
||||
fnCast, args, err := c.getInvokeCall(frame, instr)
|
||||
if err != nil {
|
||||
return llvm.Value{}, err
|
||||
@@ -1801,6 +1686,11 @@ func (c *Compiler) parseCall(frame *Frame, instr *ssa.CallCommon, parentHandle l
|
||||
return c.builder.CreateCall(target, args, ""), nil
|
||||
}
|
||||
|
||||
switch fn.RelString(nil) {
|
||||
case "syscall.Syscall", "syscall.Syscall6":
|
||||
return c.emitSyscall(frame, instr)
|
||||
}
|
||||
|
||||
targetFunc := c.ir.GetFunction(fn)
|
||||
if targetFunc.LLVMFn.IsNil() {
|
||||
return llvm.Value{}, c.makeError(instr.Pos(), "undefined function: "+targetFunc.LinkName())
|
||||
@@ -1821,7 +1711,7 @@ func (c *Compiler) parseCall(frame *Frame, instr *ssa.CallCommon, parentHandle l
|
||||
} else {
|
||||
context = llvm.Undef(c.i8ptrType)
|
||||
}
|
||||
return c.parseFunctionCall(frame, instr.Args, targetFunc.LLVMFn, context, c.ir.IsBlocking(targetFunc), parentHandle)
|
||||
return c.parseFunctionCall(frame, instr.Args, targetFunc.LLVMFn, context, targetFunc.IsExported())
|
||||
}
|
||||
|
||||
// Builtin or function pointer.
|
||||
@@ -1833,13 +1723,12 @@ func (c *Compiler) parseCall(frame *Frame, instr *ssa.CallCommon, parentHandle l
|
||||
if err != nil {
|
||||
return llvm.Value{}, err
|
||||
}
|
||||
// TODO: blocking function pointers (needs analysis)
|
||||
// 'value' is a closure, not a raw function pointer.
|
||||
// Extract the function pointer and the context pointer.
|
||||
// closure: {context, function pointer}
|
||||
context := c.builder.CreateExtractValue(value, 0, "")
|
||||
value = c.builder.CreateExtractValue(value, 1, "")
|
||||
return c.parseFunctionCall(frame, instr.Args, value, context, false, parentHandle)
|
||||
return c.parseFunctionCall(frame, instr.Args, value, context, false)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1954,7 +1843,7 @@ func (c *Compiler) parseExpr(frame *Frame, expr ssa.Value) (llvm.Value, error) {
|
||||
case *ssa.Call:
|
||||
// Passing the current task here to the subroutine. It is only used when
|
||||
// the subroutine is blocking.
|
||||
return c.parseCall(frame, expr.Common(), frame.taskHandle)
|
||||
return c.parseCall(frame, expr.Common())
|
||||
case *ssa.ChangeInterface:
|
||||
// Do not change between interface types: always use the underlying
|
||||
// (concrete) type in the type number of the interface. Every method
|
||||
@@ -2068,7 +1957,7 @@ func (c *Compiler) parseExpr(frame *Frame, expr ssa.Value) (llvm.Value, error) {
|
||||
var bufptr, buflen llvm.Value
|
||||
switch ptrTyp := expr.X.Type().Underlying().(type) {
|
||||
case *types.Pointer:
|
||||
typ := expr.X.Type().(*types.Pointer).Elem().Underlying()
|
||||
typ := expr.X.Type().Underlying().(*types.Pointer).Elem().Underlying()
|
||||
switch typ := typ.(type) {
|
||||
case *types.Array:
|
||||
bufptr = val
|
||||
@@ -2117,10 +2006,7 @@ func (c *Compiler) parseExpr(frame *Frame, expr ssa.Value) (llvm.Value, error) {
|
||||
|
||||
// Bounds check.
|
||||
// LLVM optimizes this away in most cases.
|
||||
length, err := c.parseBuiltin(frame, []ssa.Value{expr.X}, "len", expr.Pos())
|
||||
if err != nil {
|
||||
return llvm.Value{}, err // shouldn't happen
|
||||
}
|
||||
length := c.builder.CreateExtractValue(value, 1, "len")
|
||||
c.emitBoundsCheck(frame, length, index, expr.Index.Type())
|
||||
|
||||
// Lookup byte
|
||||
@@ -2136,12 +2022,12 @@ func (c *Compiler) parseExpr(frame *Frame, expr ssa.Value) (llvm.Value, error) {
|
||||
default:
|
||||
panic("unknown lookup type: " + expr.String())
|
||||
}
|
||||
|
||||
case *ssa.MakeChan:
|
||||
return c.emitMakeChan(expr)
|
||||
case *ssa.MakeClosure:
|
||||
// A closure returns a function pointer with context:
|
||||
// {context, fp}
|
||||
return c.parseMakeClosure(frame, expr)
|
||||
|
||||
case *ssa.MakeInterface:
|
||||
val, err := c.parseExpr(frame, expr.X)
|
||||
if err != nil {
|
||||
@@ -2525,7 +2411,7 @@ func (c *Compiler) parseBinOp(op token.Token, typ types.Type, x, y llvm.Value, p
|
||||
return c.builder.CreateICmp(llvm.IntUGE, x, y, ""), nil
|
||||
}
|
||||
default:
|
||||
return llvm.Value{}, c.makeError(pos, "todo: binop on integer: "+op.String())
|
||||
panic("binop on integer: " + op.String())
|
||||
}
|
||||
} else if typ.Info()&types.IsFloat != 0 {
|
||||
// Operations on floats
|
||||
@@ -2538,22 +2424,40 @@ func (c *Compiler) parseBinOp(op token.Token, typ types.Type, x, y llvm.Value, p
|
||||
return c.builder.CreateFMul(x, y, ""), nil
|
||||
case token.QUO: // /
|
||||
return c.builder.CreateFDiv(x, y, ""), nil
|
||||
case token.REM: // %
|
||||
return c.builder.CreateFRem(x, y, ""), nil
|
||||
case token.EQL: // ==
|
||||
return c.builder.CreateFCmp(llvm.FloatOEQ, x, y, ""), nil
|
||||
return c.builder.CreateFCmp(llvm.FloatUEQ, x, y, ""), nil
|
||||
case token.NEQ: // !=
|
||||
return c.builder.CreateFCmp(llvm.FloatONE, x, y, ""), nil
|
||||
return c.builder.CreateFCmp(llvm.FloatUNE, x, y, ""), nil
|
||||
case token.LSS: // <
|
||||
return c.builder.CreateFCmp(llvm.FloatOLT, x, y, ""), nil
|
||||
return c.builder.CreateFCmp(llvm.FloatULT, x, y, ""), nil
|
||||
case token.LEQ: // <=
|
||||
return c.builder.CreateFCmp(llvm.FloatOLE, x, y, ""), nil
|
||||
return c.builder.CreateFCmp(llvm.FloatULE, x, y, ""), nil
|
||||
case token.GTR: // >
|
||||
return c.builder.CreateFCmp(llvm.FloatOGT, x, y, ""), nil
|
||||
return c.builder.CreateFCmp(llvm.FloatUGT, x, y, ""), nil
|
||||
case token.GEQ: // >=
|
||||
return c.builder.CreateFCmp(llvm.FloatOGE, x, y, ""), nil
|
||||
return c.builder.CreateFCmp(llvm.FloatUGE, x, y, ""), nil
|
||||
default:
|
||||
return llvm.Value{}, c.makeError(pos, "todo: binop on float: "+op.String())
|
||||
panic("binop on float: " + op.String())
|
||||
}
|
||||
} else if typ.Info()&types.IsComplex != 0 {
|
||||
indexr := llvm.ConstInt(c.ctx.Int32Type(), 0, false)
|
||||
indexi := llvm.ConstInt(c.ctx.Int32Type(), 1, false)
|
||||
r1 := c.builder.CreateExtractElement(x, indexr, "r1")
|
||||
r2 := c.builder.CreateExtractElement(y, indexr, "r2")
|
||||
i1 := c.builder.CreateExtractElement(x, indexi, "i1")
|
||||
i2 := c.builder.CreateExtractElement(y, indexi, "i2")
|
||||
switch op {
|
||||
case token.EQL: // ==
|
||||
req := c.builder.CreateFCmp(llvm.FloatOEQ, r1, r2, "")
|
||||
ieq := c.builder.CreateFCmp(llvm.FloatOEQ, i1, i2, "")
|
||||
return c.builder.CreateAnd(req, ieq, ""), nil
|
||||
case token.NEQ: // !=
|
||||
req := c.builder.CreateFCmp(llvm.FloatOEQ, r1, r2, "")
|
||||
ieq := c.builder.CreateFCmp(llvm.FloatOEQ, i1, i2, "")
|
||||
neq := c.builder.CreateAnd(req, ieq, "")
|
||||
return c.builder.CreateNot(neq, ""), nil
|
||||
default:
|
||||
return llvm.Value{}, c.makeError(pos, "todo: binop on complex number: "+op.String())
|
||||
}
|
||||
} else if typ.Info()&types.IsBoolean != 0 {
|
||||
// Operations on booleans
|
||||
@@ -2563,7 +2467,7 @@ func (c *Compiler) parseBinOp(op token.Token, typ types.Type, x, y llvm.Value, p
|
||||
case token.NEQ: // !=
|
||||
return c.builder.CreateICmp(llvm.IntNE, x, y, ""), nil
|
||||
default:
|
||||
return llvm.Value{}, c.makeError(pos, "todo: binop on boolean: "+op.String())
|
||||
panic("binop on bool: " + op.String())
|
||||
}
|
||||
} else if typ.Kind() == types.UnsafePointer {
|
||||
// Operations on pointers
|
||||
@@ -2573,7 +2477,7 @@ func (c *Compiler) parseBinOp(op token.Token, typ types.Type, x, y llvm.Value, p
|
||||
case token.NEQ: // !=
|
||||
return c.builder.CreateICmp(llvm.IntNE, x, y, ""), nil
|
||||
default:
|
||||
return llvm.Value{}, c.makeError(pos, "todo: binop on pointer: "+op.String())
|
||||
panic("binop on pointer: " + op.String())
|
||||
}
|
||||
} else if typ.Info()&types.IsString != 0 {
|
||||
// Operations on strings
|
||||
@@ -2596,7 +2500,7 @@ func (c *Compiler) parseBinOp(op token.Token, typ types.Type, x, y llvm.Value, p
|
||||
case token.GEQ: // >=
|
||||
return c.createRuntimeCall("stringLess", []llvm.Value{y, x}, ""), nil
|
||||
default:
|
||||
return llvm.Value{}, c.makeError(pos, "todo: binop on string: "+op.String())
|
||||
panic("binop on string: " + op.String())
|
||||
}
|
||||
} else {
|
||||
return llvm.Value{}, c.makeError(pos, "todo: unknown basic type in binop: "+typ.String())
|
||||
@@ -2752,6 +2656,19 @@ func (c *Compiler) parseConst(prefix string, expr *ssa.Const) (llvm.Value, error
|
||||
} else if typ.Info()&types.IsFloat != 0 {
|
||||
n, _ := constant.Float64Val(expr.Value)
|
||||
return llvm.ConstFloat(llvmType, n), nil
|
||||
} else if typ.Kind() == types.Complex64 {
|
||||
r, err := c.parseConst(prefix, ssa.NewConst(constant.Real(expr.Value), types.Typ[types.Float32]))
|
||||
if err != nil {
|
||||
return llvm.Value{}, err
|
||||
}
|
||||
i, err := c.parseConst(prefix, ssa.NewConst(constant.Imag(expr.Value), types.Typ[types.Float32]))
|
||||
if err != nil {
|
||||
return llvm.Value{}, err
|
||||
}
|
||||
cplx := llvm.Undef(llvm.VectorType(c.ctx.FloatType(), 2))
|
||||
cplx = c.builder.CreateInsertElement(cplx, r, llvm.ConstInt(c.ctx.Int8Type(), 0, false), "")
|
||||
cplx = c.builder.CreateInsertElement(cplx, i, llvm.ConstInt(c.ctx.Int8Type(), 1, false), "")
|
||||
return cplx, nil
|
||||
} else if typ.Kind() == types.Complex128 {
|
||||
r, err := c.parseConst(prefix, ssa.NewConst(constant.Real(expr.Value), types.Typ[types.Float64]))
|
||||
if err != nil {
|
||||
@@ -2768,6 +2685,12 @@ func (c *Compiler) parseConst(prefix string, expr *ssa.Const) (llvm.Value, error
|
||||
} else {
|
||||
return llvm.Value{}, errors.New("todo: unknown constant: " + expr.String())
|
||||
}
|
||||
case *types.Chan:
|
||||
sig, err := c.getLLVMType(expr.Type())
|
||||
if err != nil {
|
||||
return llvm.Value{}, err
|
||||
}
|
||||
return c.getZeroValue(sig)
|
||||
case *types.Signature:
|
||||
if expr.Value != nil {
|
||||
return llvm.Value{}, errors.New("non-nil signature constant")
|
||||
@@ -3063,10 +2986,20 @@ 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().(*types.Pointer).Elem()
|
||||
valType := unop.X.Type().Underlying().(*types.Pointer).Elem()
|
||||
if c.targetData.TypeAllocSize(x.Type().ElementType()) == 0 {
|
||||
// zero-length data
|
||||
return c.getZeroValue(x.Type().ElementType())
|
||||
} else if strings.HasSuffix(unop.X.String(), "$funcaddr") {
|
||||
// CGo function pointer. The cgo part has rewritten CGo function
|
||||
// pointers as stub global variables of the form:
|
||||
// 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")]
|
||||
fn := c.mod.NamedFunction(name)
|
||||
return c.builder.CreateBitCast(fn, c.i8ptrType, ""), nil
|
||||
} else {
|
||||
load := c.builder.CreateLoad(x, "")
|
||||
if c.ir.IsVolatile(valType) {
|
||||
@@ -3077,6 +3010,8 @@ func (c *Compiler) parseUnOp(frame *Frame, unop *ssa.UnOp) (llvm.Value, error) {
|
||||
}
|
||||
case token.XOR: // ^x, toggle all bits in integer
|
||||
return c.builder.CreateXor(x, llvm.ConstInt(x.Type(), ^uint64(0), false), ""), nil
|
||||
case token.ARROW: // <-x, receive from channel
|
||||
return c.emitChanRecv(frame, unop)
|
||||
default:
|
||||
return llvm.Value{}, c.makeError(unop.Pos(), "todo: unknown unop")
|
||||
}
|
||||
@@ -3129,8 +3064,10 @@ func (c *Compiler) ExternalInt64AsPtr() error {
|
||||
// Only change externally visible functions (exports and imports).
|
||||
continue
|
||||
}
|
||||
if strings.HasPrefix(fn.Name(), "llvm.") {
|
||||
// Do not try to modify the signature of internal LLVM functions.
|
||||
if strings.HasPrefix(fn.Name(), "llvm.") || strings.HasPrefix(fn.Name(), "runtime.") {
|
||||
// Do not try to modify the signature of internal LLVM functions and
|
||||
// assume that runtime functions are only temporarily exported for
|
||||
// coroutine lowering.
|
||||
continue
|
||||
}
|
||||
|
||||
|
||||
+11
-2
@@ -14,9 +14,9 @@ package compiler
|
||||
// frames.
|
||||
|
||||
import (
|
||||
"github.com/aykevl/go-llvm"
|
||||
"github.com/aykevl/tinygo/ir"
|
||||
"github.com/tinygo-org/tinygo/ir"
|
||||
"golang.org/x/tools/go/ssa"
|
||||
"tinygo.org/x/go-llvm"
|
||||
)
|
||||
|
||||
// deferInitFunc sets up this function for future deferred calls. It must be
|
||||
@@ -243,6 +243,9 @@ func (c *Compiler) emitRunDefers(frame *Frame) error {
|
||||
// with a strict calling convention.
|
||||
forwardParams = append(forwardParams, llvm.Undef(c.i8ptrType))
|
||||
|
||||
// Parent coroutine handle.
|
||||
forwardParams = append(forwardParams, llvm.Undef(c.i8ptrType))
|
||||
|
||||
fnPtr, _, err := c.getInvokeCall(frame, callback)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -277,6 +280,9 @@ func (c *Compiler) emitRunDefers(frame *Frame) error {
|
||||
// function, but we have to pass one anyway.
|
||||
forwardParams = append(forwardParams, llvm.Undef(c.i8ptrType))
|
||||
|
||||
// Parent coroutine handle.
|
||||
forwardParams = append(forwardParams, llvm.Undef(c.i8ptrType))
|
||||
|
||||
// Call real function.
|
||||
c.createCall(callback.LLVMFn, forwardParams, "")
|
||||
|
||||
@@ -305,6 +311,9 @@ func (c *Compiler) emitRunDefers(frame *Frame) error {
|
||||
forwardParams = append(forwardParams, forwardParam)
|
||||
}
|
||||
|
||||
// Parent coroutine handle.
|
||||
forwardParams = append(forwardParams, llvm.Undef(c.i8ptrType))
|
||||
|
||||
// Call deferred function.
|
||||
c.createCall(fn.LLVMFn, forwardParams, "")
|
||||
|
||||
|
||||
@@ -0,0 +1,588 @@
|
||||
package compiler
|
||||
|
||||
// This file lowers goroutine pseudo-functions into coroutines scheduled by a
|
||||
// scheduler at runtime. It uses coroutine support in LLVM for this
|
||||
// transformation: https://llvm.org/docs/Coroutines.html
|
||||
//
|
||||
// For example, take the following code:
|
||||
//
|
||||
// func main() {
|
||||
// go foo()
|
||||
// time.Sleep(2 * time.Second)
|
||||
// println("some other operation")
|
||||
// bar()
|
||||
// println("done")
|
||||
// }
|
||||
//
|
||||
// func foo() {
|
||||
// for {
|
||||
// println("foo!")
|
||||
// time.Sleep(time.Second)
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// func bar() {
|
||||
// time.Sleep(time.Second)
|
||||
// println("blocking operation completed)
|
||||
// }
|
||||
//
|
||||
// It is transformed by the IR generator in compiler.go into the following
|
||||
// pseudo-Go code:
|
||||
//
|
||||
// func main() {
|
||||
// fn := runtime.makeGoroutine(foo)
|
||||
// fn()
|
||||
// time.Sleep(2 * time.Second)
|
||||
// println("some other operation")
|
||||
// bar() // imagine an 'await' keyword in front of this call
|
||||
// println("done")
|
||||
// }
|
||||
//
|
||||
// func foo() {
|
||||
// for {
|
||||
// println("foo!")
|
||||
// time.Sleep(time.Second)
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// func bar() {
|
||||
// time.Sleep(time.Second)
|
||||
// println("blocking operation completed)
|
||||
// }
|
||||
//
|
||||
// The pass in this file transforms this code even further, to the following
|
||||
// async/await style pseudocode:
|
||||
//
|
||||
// func main(parent) {
|
||||
// hdl := llvm.makeCoroutine()
|
||||
// foo(nil) // do not pass the parent coroutine: this is an independent goroutine
|
||||
// runtime.sleepTask(hdl, 2 * time.Second) // ask the scheduler to re-activate this coroutine at the right time
|
||||
// llvm.suspend(hdl) // suspend point
|
||||
// println("some other operation")
|
||||
// bar(hdl) // await, pass a continuation (hdl) to bar
|
||||
// llvm.suspend(hdl) // suspend point, wait for the callee to re-activate
|
||||
// println("done")
|
||||
// runtime.activateTask(parent) // re-activate the parent (nop, there is no parent)
|
||||
// }
|
||||
//
|
||||
// func foo(parent) {
|
||||
// hdl := llvm.makeCoroutine()
|
||||
// for {
|
||||
// println("foo!")
|
||||
// runtime.sleepTask(hdl, time.Second) // ask the scheduler to re-activate this coroutine at the right time
|
||||
// llvm.suspend(hdl) // suspend point
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// func bar(parent) {
|
||||
// hdl := llvm.makeCoroutine()
|
||||
// runtime.sleepTask(hdl, time.Second) // ask the scheduler to re-activate this coroutine at the right time
|
||||
// llvm.suspend(hdl) // suspend point
|
||||
// println("blocking operation completed)
|
||||
// runtime.activateTask(parent) // re-activate the parent coroutine before returning
|
||||
// }
|
||||
//
|
||||
// The real LLVM code is more complicated, but this is the general idea.
|
||||
//
|
||||
// The LLVM coroutine passes will then process this file further transforming
|
||||
// these three functions into coroutines. Most of the actual work is done by the
|
||||
// scheduler, which runs in the background scheduling all coroutines.
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"strings"
|
||||
|
||||
"tinygo.org/x/go-llvm"
|
||||
)
|
||||
|
||||
type asyncFunc struct {
|
||||
taskHandle llvm.Value
|
||||
cleanupBlock llvm.BasicBlock
|
||||
suspendBlock llvm.BasicBlock
|
||||
unreachableBlock llvm.BasicBlock
|
||||
}
|
||||
|
||||
// LowerGoroutines is a pass called during optimization that transforms the IR
|
||||
// into one where all blocking functions are turned into goroutines and blocking
|
||||
// calls into await calls.
|
||||
func (c *Compiler) LowerGoroutines() error {
|
||||
needsScheduler, err := c.markAsyncFunctions()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
uses := getUses(c.mod.NamedFunction("runtime.callMain"))
|
||||
if len(uses) != 1 || uses[0].IsACallInst().IsNil() {
|
||||
panic("expected exactly 1 call of runtime.callMain, check the entry point")
|
||||
}
|
||||
mainCall := uses[0]
|
||||
|
||||
// Replace call of runtime.callMain() with a real call to main.main(),
|
||||
// optionally followed by a call to runtime.scheduler().
|
||||
c.builder.SetInsertPointBefore(mainCall)
|
||||
realMain := c.mod.NamedFunction(c.ir.MainPkg().Pkg.Path() + ".main")
|
||||
c.builder.CreateCall(realMain, []llvm.Value{llvm.Undef(c.i8ptrType), llvm.ConstPointerNull(c.i8ptrType)}, "")
|
||||
if needsScheduler {
|
||||
c.createRuntimeCall("scheduler", nil, "")
|
||||
}
|
||||
mainCall.EraseFromParentAsInstruction()
|
||||
|
||||
if !needsScheduler {
|
||||
go_scheduler := c.mod.NamedFunction("go_scheduler")
|
||||
if !go_scheduler.IsNil() {
|
||||
// This is the WebAssembly backend.
|
||||
// There is no need to export the go_scheduler function, but it is
|
||||
// still exported. Make sure it is optimized away.
|
||||
go_scheduler.SetLinkage(llvm.InternalLinkage)
|
||||
}
|
||||
}
|
||||
|
||||
// main.main was set to external linkage during IR construction. Set it to
|
||||
// internal linkage to enable interprocedural optimizations.
|
||||
realMain.SetLinkage(llvm.InternalLinkage)
|
||||
c.mod.NamedFunction("runtime.alloc").SetLinkage(llvm.InternalLinkage)
|
||||
c.mod.NamedFunction("runtime.free").SetLinkage(llvm.InternalLinkage)
|
||||
c.mod.NamedFunction("runtime.chanSend").SetLinkage(llvm.InternalLinkage)
|
||||
c.mod.NamedFunction("runtime.chanRecv").SetLinkage(llvm.InternalLinkage)
|
||||
c.mod.NamedFunction("runtime.sleepTask").SetLinkage(llvm.InternalLinkage)
|
||||
c.mod.NamedFunction("runtime.activateTask").SetLinkage(llvm.InternalLinkage)
|
||||
c.mod.NamedFunction("runtime.scheduler").SetLinkage(llvm.InternalLinkage)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// markAsyncFunctions does the bulk of the work of lowering goroutines. It
|
||||
// determines whether a scheduler is needed, and if it is, it transforms
|
||||
// blocking operations into goroutines and blocking calls into await calls.
|
||||
//
|
||||
// It does the following operations:
|
||||
// * Find all blocking functions.
|
||||
// * Determine whether a scheduler is necessary. If not, it skips the
|
||||
// following operations.
|
||||
// * Transform call instructions into await calls.
|
||||
// * Transform return instructions into final suspends.
|
||||
// * Set up the coroutine frames for async functions.
|
||||
// * Transform blocking calls into their async equivalents.
|
||||
func (c *Compiler) markAsyncFunctions() (needsScheduler bool, err error) {
|
||||
var worklist []llvm.Value
|
||||
|
||||
sleep := c.mod.NamedFunction("time.Sleep")
|
||||
if !sleep.IsNil() {
|
||||
worklist = append(worklist, sleep)
|
||||
}
|
||||
chanSendStub := c.mod.NamedFunction("runtime.chanSendStub")
|
||||
if !chanSendStub.IsNil() {
|
||||
worklist = append(worklist, chanSendStub)
|
||||
}
|
||||
chanRecvStub := c.mod.NamedFunction("runtime.chanRecvStub")
|
||||
if !chanRecvStub.IsNil() {
|
||||
worklist = append(worklist, chanRecvStub)
|
||||
}
|
||||
|
||||
if len(worklist) == 0 {
|
||||
// There are no blocking operations, so no need to transform anything.
|
||||
return false, c.lowerMakeGoroutineCalls()
|
||||
}
|
||||
|
||||
// Find all async functions.
|
||||
// Keep reducing this worklist by marking a function as recursively async
|
||||
// from the worklist and pushing all its parents that are non-async.
|
||||
// This is somewhat similar to a worklist in a mark-sweep garbage collector:
|
||||
// the work items are then grey objects.
|
||||
asyncFuncs := make(map[llvm.Value]*asyncFunc)
|
||||
asyncList := make([]llvm.Value, 0, 4)
|
||||
for len(worklist) != 0 {
|
||||
// Pick the topmost.
|
||||
f := worklist[len(worklist)-1]
|
||||
worklist = worklist[:len(worklist)-1]
|
||||
if _, ok := asyncFuncs[f]; ok {
|
||||
continue // already processed
|
||||
}
|
||||
// Add to set of async functions.
|
||||
asyncFuncs[f] = &asyncFunc{}
|
||||
asyncList = append(asyncList, f)
|
||||
|
||||
// Add all callees to the worklist.
|
||||
for _, use := range getUses(f) {
|
||||
if use.IsConstant() && use.Opcode() == llvm.BitCast {
|
||||
bitcastUses := getUses(use)
|
||||
for _, call := range bitcastUses {
|
||||
if call.IsACallInst().IsNil() || call.CalledValue().Name() != "runtime.makeGoroutine" {
|
||||
return false, errors.New("async function " + f.Name() + " incorrectly used in bitcast, expected runtime.makeGoroutine")
|
||||
}
|
||||
}
|
||||
// This is a go statement. Do not mark the parent as async, as
|
||||
// starting a goroutine is not a blocking operation.
|
||||
continue
|
||||
}
|
||||
if use.IsACallInst().IsNil() {
|
||||
// Not a call instruction. Maybe a store to a global? In any
|
||||
// case, this requires support for async calls across function
|
||||
// pointers which is not yet supported.
|
||||
return false, errors.New("async function " + f.Name() + " used as function pointer")
|
||||
}
|
||||
parent := use.InstructionParent().Parent()
|
||||
for i := 0; i < use.OperandsCount()-1; i++ {
|
||||
if use.Operand(i) == f {
|
||||
return false, errors.New("async function " + f.Name() + " used as function pointer in " + parent.Name())
|
||||
}
|
||||
}
|
||||
worklist = append(worklist, parent)
|
||||
}
|
||||
}
|
||||
|
||||
// Check whether a scheduler is needed.
|
||||
makeGoroutine := c.mod.NamedFunction("runtime.makeGoroutine")
|
||||
if c.GOOS == "js" && strings.HasPrefix(c.Triple, "wasm") {
|
||||
// JavaScript always needs a scheduler, as in general no blocking
|
||||
// operations are possible. Blocking operations block the browser UI,
|
||||
// which is very bad.
|
||||
needsScheduler = true
|
||||
} else {
|
||||
// Only use a scheduler when an async goroutine is started. When the
|
||||
// goroutine is not async (does not do any blocking operation), no
|
||||
// scheduler is necessary as it can be called directly.
|
||||
for _, use := range getUses(makeGoroutine) {
|
||||
// Input param must be const bitcast of function.
|
||||
bitcast := use.Operand(0)
|
||||
if !bitcast.IsConstant() || bitcast.Opcode() != llvm.BitCast {
|
||||
panic("expected const bitcast operand of runtime.makeGoroutine")
|
||||
}
|
||||
goroutine := bitcast.Operand(0)
|
||||
if _, ok := asyncFuncs[goroutine]; ok {
|
||||
needsScheduler = true
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if !needsScheduler {
|
||||
// No scheduler is needed. Do not transform all functions here.
|
||||
// However, make sure that all go calls (which are all non-async) are
|
||||
// transformed into regular calls.
|
||||
return false, c.lowerMakeGoroutineCalls()
|
||||
}
|
||||
|
||||
// Create a few LLVM intrinsics for coroutine support.
|
||||
|
||||
coroIdType := llvm.FunctionType(c.ctx.TokenType(), []llvm.Type{c.ctx.Int32Type(), c.i8ptrType, c.i8ptrType, c.i8ptrType}, false)
|
||||
coroIdFunc := llvm.AddFunction(c.mod, "llvm.coro.id", coroIdType)
|
||||
|
||||
coroSizeType := llvm.FunctionType(c.ctx.Int32Type(), nil, false)
|
||||
coroSizeFunc := llvm.AddFunction(c.mod, "llvm.coro.size.i32", coroSizeType)
|
||||
|
||||
coroBeginType := llvm.FunctionType(c.i8ptrType, []llvm.Type{c.ctx.TokenType(), c.i8ptrType}, false)
|
||||
coroBeginFunc := llvm.AddFunction(c.mod, "llvm.coro.begin", coroBeginType)
|
||||
|
||||
coroPromiseType := llvm.FunctionType(c.i8ptrType, []llvm.Type{c.i8ptrType, c.ctx.Int32Type(), c.ctx.Int1Type()}, false)
|
||||
coroPromiseFunc := llvm.AddFunction(c.mod, "llvm.coro.promise", coroPromiseType)
|
||||
|
||||
coroSuspendType := llvm.FunctionType(c.ctx.Int8Type(), []llvm.Type{c.ctx.TokenType(), c.ctx.Int1Type()}, false)
|
||||
coroSuspendFunc := llvm.AddFunction(c.mod, "llvm.coro.suspend", coroSuspendType)
|
||||
|
||||
coroEndType := llvm.FunctionType(c.ctx.Int1Type(), []llvm.Type{c.i8ptrType, c.ctx.Int1Type()}, false)
|
||||
coroEndFunc := llvm.AddFunction(c.mod, "llvm.coro.end", coroEndType)
|
||||
|
||||
coroFreeType := llvm.FunctionType(c.i8ptrType, []llvm.Type{c.ctx.TokenType(), c.i8ptrType}, false)
|
||||
coroFreeFunc := llvm.AddFunction(c.mod, "llvm.coro.free", coroFreeType)
|
||||
|
||||
// Transform all async functions into coroutines.
|
||||
for _, f := range asyncList {
|
||||
if f == sleep || f == chanSendStub || f == chanRecvStub {
|
||||
continue
|
||||
}
|
||||
|
||||
frame := asyncFuncs[f]
|
||||
frame.cleanupBlock = c.ctx.AddBasicBlock(f, "task.cleanup")
|
||||
frame.suspendBlock = c.ctx.AddBasicBlock(f, "task.suspend")
|
||||
frame.unreachableBlock = c.ctx.AddBasicBlock(f, "task.unreachable")
|
||||
|
||||
// Scan for async calls and return instructions that need to have
|
||||
// suspend points inserted.
|
||||
var asyncCalls []llvm.Value
|
||||
var returns []llvm.Value
|
||||
for bb := f.EntryBasicBlock(); !bb.IsNil(); bb = llvm.NextBasicBlock(bb) {
|
||||
for inst := bb.FirstInstruction(); !inst.IsNil(); inst = llvm.NextInstruction(inst) {
|
||||
if !inst.IsACallInst().IsNil() {
|
||||
callee := inst.CalledValue()
|
||||
if _, ok := asyncFuncs[callee]; !ok || callee == sleep || callee == chanSendStub || callee == chanRecvStub {
|
||||
continue
|
||||
}
|
||||
asyncCalls = append(asyncCalls, inst)
|
||||
} else if !inst.IsAReturnInst().IsNil() {
|
||||
returns = append(returns, inst)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Coroutine setup.
|
||||
c.builder.SetInsertPointBefore(f.EntryBasicBlock().FirstInstruction())
|
||||
taskState := c.builder.CreateAlloca(c.mod.GetTypeByName("runtime.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),
|
||||
stateI8,
|
||||
llvm.ConstNull(c.i8ptrType),
|
||||
llvm.ConstNull(c.i8ptrType),
|
||||
}, "task.token")
|
||||
size := c.builder.CreateCall(coroSizeFunc, nil, "task.size")
|
||||
if c.targetData.TypeAllocSize(size.Type()) > c.targetData.TypeAllocSize(c.uintptrType) {
|
||||
size = c.builder.CreateTrunc(size, c.uintptrType, "task.size.uintptr")
|
||||
} else if c.targetData.TypeAllocSize(size.Type()) < c.targetData.TypeAllocSize(c.uintptrType) {
|
||||
size = c.builder.CreateZExt(size, c.uintptrType, "task.size.uintptr")
|
||||
}
|
||||
data := c.createRuntimeCall("alloc", []llvm.Value{size}, "task.data")
|
||||
frame.taskHandle = c.builder.CreateCall(coroBeginFunc, []llvm.Value{id, data}, "task.handle")
|
||||
|
||||
// Modify async calls so this function suspends right after the child
|
||||
// returns, because the child is probably not finished yet. Wait until
|
||||
// the child reactivates the parent.
|
||||
for _, inst := range asyncCalls {
|
||||
inst.SetOperand(inst.OperandsCount()-2, frame.taskHandle)
|
||||
|
||||
// Split this basic block.
|
||||
await := c.splitBasicBlock(inst, llvm.NextBasicBlock(c.builder.GetInsertBlock()), "task.await")
|
||||
|
||||
// Set task state to TASK_STATE_CALL.
|
||||
c.builder.SetInsertPointAtEnd(inst.InstructionParent())
|
||||
|
||||
// Suspend.
|
||||
continuePoint := c.builder.CreateCall(coroSuspendFunc, []llvm.Value{
|
||||
llvm.ConstNull(c.ctx.TokenType()),
|
||||
llvm.ConstInt(c.ctx.Int1Type(), 0, false),
|
||||
}, "")
|
||||
sw := c.builder.CreateSwitch(continuePoint, frame.suspendBlock, 2)
|
||||
sw.AddCase(llvm.ConstInt(c.ctx.Int8Type(), 0, false), await)
|
||||
sw.AddCase(llvm.ConstInt(c.ctx.Int8Type(), 1, false), frame.cleanupBlock)
|
||||
}
|
||||
|
||||
// Replace return instructions with suspend points that should
|
||||
// reactivate the parent coroutine.
|
||||
for _, inst := range returns {
|
||||
if inst.OperandsCount() == 0 {
|
||||
// These properties were added by the functionattrs pass.
|
||||
// Remove them, because now we start using the parameter.
|
||||
// https://llvm.org/docs/Passes.html#functionattrs-deduce-function-attributes
|
||||
for _, kind := range []string{"nocapture", "readnone"} {
|
||||
kindID := llvm.AttributeKindID(kind)
|
||||
f.RemoveEnumAttributeAtIndex(f.ParamsCount(), kindID)
|
||||
}
|
||||
|
||||
// Reactivate the parent coroutine. This adds it back to
|
||||
// the run queue, so it is started again by the
|
||||
// scheduler when possible (possibly right after the
|
||||
// following suspend).
|
||||
c.builder.SetInsertPointBefore(inst)
|
||||
|
||||
parentHandle := f.LastParam()
|
||||
c.createRuntimeCall("activateTask", []llvm.Value{parentHandle}, "")
|
||||
|
||||
// Suspend this coroutine.
|
||||
// It would look like this is unnecessary, but if this
|
||||
// suspend point is left out, it leads to undefined
|
||||
// behavior somehow (with the unreachable instruction).
|
||||
continuePoint := c.builder.CreateCall(coroSuspendFunc, []llvm.Value{
|
||||
llvm.ConstNull(c.ctx.TokenType()),
|
||||
llvm.ConstInt(c.ctx.Int1Type(), 1, false),
|
||||
}, "ret")
|
||||
sw := c.builder.CreateSwitch(continuePoint, frame.suspendBlock, 2)
|
||||
sw.AddCase(llvm.ConstInt(c.ctx.Int8Type(), 0, false), frame.unreachableBlock)
|
||||
sw.AddCase(llvm.ConstInt(c.ctx.Int8Type(), 1, false), frame.cleanupBlock)
|
||||
inst.EraseFromParentAsInstruction()
|
||||
} else {
|
||||
panic("todo: return value from coroutine")
|
||||
}
|
||||
}
|
||||
|
||||
// Coroutine cleanup. Free resources associated with this coroutine.
|
||||
c.builder.SetInsertPointAtEnd(frame.cleanupBlock)
|
||||
mem := c.builder.CreateCall(coroFreeFunc, []llvm.Value{id, frame.taskHandle}, "task.data.free")
|
||||
c.createRuntimeCall("free", []llvm.Value{mem}, "")
|
||||
c.builder.CreateBr(frame.suspendBlock)
|
||||
|
||||
// Coroutine suspend. A call to llvm.coro.suspend() will branch here.
|
||||
c.builder.SetInsertPointAtEnd(frame.suspendBlock)
|
||||
c.builder.CreateCall(coroEndFunc, []llvm.Value{frame.taskHandle, llvm.ConstInt(c.ctx.Int1Type(), 0, false)}, "unused")
|
||||
returnType := f.Type().ElementType().ReturnType()
|
||||
if returnType.TypeKind() == llvm.VoidTypeKind {
|
||||
c.builder.CreateRetVoid()
|
||||
} else {
|
||||
c.builder.CreateRet(llvm.Undef(returnType))
|
||||
}
|
||||
|
||||
// Coroutine exit. All final suspends (return instructions) will branch
|
||||
// here.
|
||||
c.builder.SetInsertPointAtEnd(frame.unreachableBlock)
|
||||
c.builder.CreateUnreachable()
|
||||
}
|
||||
|
||||
// Transform calls to time.Sleep() into coroutine suspend points.
|
||||
for _, sleepCall := range getUses(sleep) {
|
||||
// sleepCall must be a call instruction.
|
||||
frame := asyncFuncs[sleepCall.InstructionParent().Parent()]
|
||||
duration := sleepCall.Operand(0)
|
||||
|
||||
// Set task state to TASK_STATE_SLEEP and set the duration.
|
||||
c.builder.SetInsertPointBefore(sleepCall)
|
||||
c.createRuntimeCall("sleepTask", []llvm.Value{frame.taskHandle, duration}, "")
|
||||
|
||||
// Yield to scheduler.
|
||||
continuePoint := c.builder.CreateCall(coroSuspendFunc, []llvm.Value{
|
||||
llvm.ConstNull(c.ctx.TokenType()),
|
||||
llvm.ConstInt(c.ctx.Int1Type(), 0, false),
|
||||
}, "")
|
||||
wakeup := c.splitBasicBlock(sleepCall, llvm.NextBasicBlock(c.builder.GetInsertBlock()), "task.wakeup")
|
||||
c.builder.SetInsertPointBefore(sleepCall)
|
||||
sw := c.builder.CreateSwitch(continuePoint, frame.suspendBlock, 2)
|
||||
sw.AddCase(llvm.ConstInt(c.ctx.Int8Type(), 0, false), wakeup)
|
||||
sw.AddCase(llvm.ConstInt(c.ctx.Int8Type(), 1, false), frame.cleanupBlock)
|
||||
sleepCall.EraseFromParentAsInstruction()
|
||||
}
|
||||
|
||||
// Transform calls to runtime.chanSendStub into channel send operations.
|
||||
for _, sendOp := range getUses(chanSendStub) {
|
||||
// sendOp must be a call instruction.
|
||||
frame := asyncFuncs[sendOp.InstructionParent().Parent()]
|
||||
|
||||
// Send the value over the channel, or block.
|
||||
sendOp.SetOperand(0, frame.taskHandle)
|
||||
sendOp.SetOperand(sendOp.OperandsCount()-1, c.mod.NamedFunction("runtime.chanSend"))
|
||||
|
||||
// Use taskState.data to store the value to send:
|
||||
// *(*valueType)(&coroutine.promise().data) = valueToSend
|
||||
// runtime.chanSend(coroutine, ch)
|
||||
bitcast := sendOp.Operand(2)
|
||||
valueAlloca := bitcast.Operand(0)
|
||||
c.builder.SetInsertPointBefore(valueAlloca)
|
||||
promiseType := c.mod.GetTypeByName("runtime.taskState")
|
||||
promiseRaw := c.builder.CreateCall(coroPromiseFunc, []llvm.Value{
|
||||
frame.taskHandle,
|
||||
llvm.ConstInt(c.ctx.Int32Type(), uint64(c.targetData.PrefTypeAlignment(promiseType)), false),
|
||||
llvm.ConstInt(c.ctx.Int1Type(), 0, false),
|
||||
}, "task.promise.raw")
|
||||
promise := c.builder.CreateBitCast(promiseRaw, llvm.PointerType(promiseType, 0), "task.promise")
|
||||
dataPtr := c.builder.CreateGEP(promise, []llvm.Value{
|
||||
llvm.ConstInt(c.ctx.Int32Type(), 0, false),
|
||||
llvm.ConstInt(c.ctx.Int32Type(), 2, false),
|
||||
}, "task.promise.data")
|
||||
sendOp.SetOperand(2, llvm.Undef(c.i8ptrType))
|
||||
valueAlloca.ReplaceAllUsesWith(c.builder.CreateBitCast(dataPtr, valueAlloca.Type(), ""))
|
||||
bitcast.EraseFromParentAsInstruction()
|
||||
valueAlloca.EraseFromParentAsInstruction()
|
||||
|
||||
// Yield to scheduler.
|
||||
c.builder.SetInsertPointBefore(llvm.NextInstruction(sendOp))
|
||||
continuePoint := c.builder.CreateCall(coroSuspendFunc, []llvm.Value{
|
||||
llvm.ConstNull(c.ctx.TokenType()),
|
||||
llvm.ConstInt(c.ctx.Int1Type(), 0, false),
|
||||
}, "")
|
||||
sw := c.builder.CreateSwitch(continuePoint, frame.suspendBlock, 2)
|
||||
wakeup := c.splitBasicBlock(sw, llvm.NextBasicBlock(c.builder.GetInsertBlock()), "task.sent")
|
||||
sw.AddCase(llvm.ConstInt(c.ctx.Int8Type(), 0, false), wakeup)
|
||||
sw.AddCase(llvm.ConstInt(c.ctx.Int8Type(), 1, false), frame.cleanupBlock)
|
||||
}
|
||||
|
||||
// Transform calls to runtime.chanRecvStub into channel receive operations.
|
||||
for _, recvOp := range getUses(chanRecvStub) {
|
||||
// recvOp must be a call instruction.
|
||||
frame := asyncFuncs[recvOp.InstructionParent().Parent()]
|
||||
|
||||
bitcast := recvOp.Operand(2)
|
||||
commaOk := recvOp.Operand(3)
|
||||
valueAlloca := bitcast.Operand(0)
|
||||
|
||||
// Receive the value over the channel, or block.
|
||||
recvOp.SetOperand(0, frame.taskHandle)
|
||||
recvOp.SetOperand(recvOp.OperandsCount()-1, c.mod.NamedFunction("runtime.chanRecv"))
|
||||
recvOp.SetOperand(2, llvm.Undef(c.i8ptrType))
|
||||
bitcast.EraseFromParentAsInstruction()
|
||||
|
||||
// Yield to scheduler.
|
||||
c.builder.SetInsertPointBefore(llvm.NextInstruction(recvOp))
|
||||
continuePoint := c.builder.CreateCall(coroSuspendFunc, []llvm.Value{
|
||||
llvm.ConstNull(c.ctx.TokenType()),
|
||||
llvm.ConstInt(c.ctx.Int1Type(), 0, false),
|
||||
}, "")
|
||||
sw := c.builder.CreateSwitch(continuePoint, frame.suspendBlock, 2)
|
||||
wakeup := c.splitBasicBlock(sw, llvm.NextBasicBlock(c.builder.GetInsertBlock()), "task.received")
|
||||
c.builder.SetInsertPointAtEnd(recvOp.InstructionParent())
|
||||
sw.AddCase(llvm.ConstInt(c.ctx.Int8Type(), 0, false), wakeup)
|
||||
sw.AddCase(llvm.ConstInt(c.ctx.Int8Type(), 1, false), frame.cleanupBlock)
|
||||
|
||||
// The value to receive is stored in taskState.data:
|
||||
// runtime.chanRecv(coroutine, ch)
|
||||
// promise := coroutine.promise()
|
||||
// valueReceived := *(*valueType)(&promise.data)
|
||||
// ok := promise.commaOk
|
||||
c.builder.SetInsertPointBefore(wakeup.FirstInstruction())
|
||||
promiseType := c.mod.GetTypeByName("runtime.taskState")
|
||||
promiseRaw := c.builder.CreateCall(coroPromiseFunc, []llvm.Value{
|
||||
frame.taskHandle,
|
||||
llvm.ConstInt(c.ctx.Int32Type(), uint64(c.targetData.PrefTypeAlignment(promiseType)), false),
|
||||
llvm.ConstInt(c.ctx.Int1Type(), 0, false),
|
||||
}, "task.promise.raw")
|
||||
promise := c.builder.CreateBitCast(promiseRaw, llvm.PointerType(promiseType, 0), "task.promise")
|
||||
dataPtr := c.builder.CreateGEP(promise, []llvm.Value{
|
||||
llvm.ConstInt(c.ctx.Int32Type(), 0, false),
|
||||
llvm.ConstInt(c.ctx.Int32Type(), 2, false),
|
||||
}, "task.promise.data")
|
||||
valueAlloca.ReplaceAllUsesWith(c.builder.CreateBitCast(dataPtr, valueAlloca.Type(), ""))
|
||||
valueAlloca.EraseFromParentAsInstruction()
|
||||
commaOkPtr := c.builder.CreateGEP(promise, []llvm.Value{
|
||||
llvm.ConstInt(c.ctx.Int32Type(), 0, false),
|
||||
llvm.ConstInt(c.ctx.Int32Type(), 1, false),
|
||||
}, "task.promise.comma-ok")
|
||||
commaOk.ReplaceAllUsesWith(commaOkPtr)
|
||||
recvOp.SetOperand(3, llvm.Undef(commaOk.Type()))
|
||||
}
|
||||
|
||||
return true, c.lowerMakeGoroutineCalls()
|
||||
}
|
||||
|
||||
// Lower runtime.makeGoroutine calls to regular call instructions. This is done
|
||||
// after the regular goroutine transformations. The started goroutines are
|
||||
// either non-blocking (in which case they can be called directly) or blocking,
|
||||
// in which case they will ask the scheduler themselves to be rescheduled.
|
||||
func (c *Compiler) lowerMakeGoroutineCalls() error {
|
||||
// The following Go code:
|
||||
// go startedGoroutine()
|
||||
//
|
||||
// Is translated to the following during IR construction, to preserve the
|
||||
// fact that this function should be called as a new goroutine.
|
||||
// %0 = call i8* @runtime.makeGoroutine(i8* bitcast (void (i8*, i8*)* @main.startedGoroutine to i8*), i8* undef, i8* null)
|
||||
// %1 = bitcast i8* %0 to void (i8*, i8*)*
|
||||
// call void %1(i8* undef, i8* undef)
|
||||
//
|
||||
// This function rewrites it to a direct call:
|
||||
// call void @main.startedGoroutine(i8* undef, i8* null)
|
||||
|
||||
makeGoroutine := c.mod.NamedFunction("runtime.makeGoroutine")
|
||||
for _, goroutine := range getUses(makeGoroutine) {
|
||||
bitcastIn := goroutine.Operand(0)
|
||||
origFunc := bitcastIn.Operand(0)
|
||||
uses := getUses(goroutine)
|
||||
if len(uses) != 1 || uses[0].IsABitCastInst().IsNil() {
|
||||
return errors.New("expected exactly 1 bitcast use of runtime.makeGoroutine")
|
||||
}
|
||||
bitcastOut := uses[0]
|
||||
uses = getUses(bitcastOut)
|
||||
if len(uses) != 1 || uses[0].IsACallInst().IsNil() {
|
||||
return errors.New("expected exactly 1 call use of runtime.makeGoroutine bitcast")
|
||||
}
|
||||
realCall := uses[0]
|
||||
|
||||
// Create call instruction.
|
||||
var params []llvm.Value
|
||||
for i := 0; i < realCall.OperandsCount()-1; i++ {
|
||||
params = append(params, realCall.Operand(i))
|
||||
}
|
||||
params[len(params)-1] = llvm.ConstPointerNull(c.i8ptrType) // parent coroutine handle (must be nil)
|
||||
c.builder.SetInsertPointBefore(realCall)
|
||||
c.builder.CreateCall(origFunc, params, "")
|
||||
realCall.EraseFromParentAsInstruction()
|
||||
bitcastOut.EraseFromParentAsInstruction()
|
||||
goroutine.EraseFromParentAsInstruction()
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -49,7 +49,7 @@ import (
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"github.com/aykevl/go-llvm"
|
||||
"tinygo.org/x/go-llvm"
|
||||
)
|
||||
|
||||
// signatureInfo is a Go signature of an interface method. It does not represent
|
||||
@@ -104,15 +104,6 @@ func (t *typeInfo) getMethod(signature *signatureInfo) *methodInfo {
|
||||
panic("could not find method")
|
||||
}
|
||||
|
||||
// id returns the fully-qualified type name including import path, removing the
|
||||
// $type suffix.
|
||||
func (t *typeInfo) id() string {
|
||||
if !strings.HasSuffix(t.name, "$type") {
|
||||
panic("concrete type does not have $type suffix: " + t.name)
|
||||
}
|
||||
return t.name[:len(t.name)-len("$type")]
|
||||
}
|
||||
|
||||
// typeInfoSlice implements sort.Slice, sorting the most commonly used types
|
||||
// first.
|
||||
type typeInfoSlice []*typeInfo
|
||||
@@ -415,7 +406,7 @@ func (p *lowerInterfacesPass) run() {
|
||||
for _, t := range p.types {
|
||||
typeSlice = append(typeSlice, t)
|
||||
}
|
||||
sort.Sort(typeSlice)
|
||||
sort.Sort(sort.Reverse(typeSlice))
|
||||
|
||||
// A type code must fit in 16 bits.
|
||||
if len(typeSlice) >= 1<<16 {
|
||||
@@ -423,9 +414,7 @@ func (p *lowerInterfacesPass) run() {
|
||||
}
|
||||
|
||||
// Assign a type code for each type.
|
||||
for i, t := range typeSlice {
|
||||
t.num = uint64(i + 1)
|
||||
}
|
||||
p.assignTypeCodes(typeSlice)
|
||||
|
||||
// Replace each call to runtime.makeInterface with the constant type code.
|
||||
for _, use := range makeInterfaceUses {
|
||||
@@ -444,7 +433,7 @@ func (p *lowerInterfacesPass) run() {
|
||||
var commaOk llvm.Value
|
||||
if t.countMakeInterfaces == 0 {
|
||||
// impossible type assert: optimize accordingly
|
||||
commaOk = llvm.ConstInt(llvm.Int1Type(), 0, false)
|
||||
commaOk = llvm.ConstInt(p.ctx.Int1Type(), 0, false)
|
||||
} else {
|
||||
// regular type assert
|
||||
p.builder.SetInsertPointBefore(use)
|
||||
@@ -631,9 +620,9 @@ func (p *lowerInterfacesPass) createInterfaceImplementsFunc(itf *interfaceInfo)
|
||||
p.builder.CreateRet(llvm.ConstInt(p.ctx.Int1Type(), 0, false))
|
||||
}
|
||||
|
||||
// getInterfaceMethodFunc return a function that returns a function pointer for
|
||||
// calling a method on an interface. It only declares the function,
|
||||
// createInterfaceMethodFunc actually defines the function.
|
||||
// getInterfaceMethodFunc returns a thunk for calling a method on an interface.
|
||||
// It only declares the function, createInterfaceMethodFunc actually defines the
|
||||
// function.
|
||||
func (p *lowerInterfacesPass) getInterfaceMethodFunc(itf *interfaceInfo, signature *signatureInfo, returnType llvm.Type, params []llvm.Type) llvm.Value {
|
||||
if fn, ok := itf.methodFuncs[signature]; ok {
|
||||
// This function has already been created.
|
||||
@@ -691,7 +680,7 @@ func (p *lowerInterfacesPass) createInterfaceMethodFunc(itf *interfaceInfo, sign
|
||||
|
||||
// Define all possible functions that can be called.
|
||||
for _, typ := range itf.types {
|
||||
bb := llvm.AddBasicBlock(fn, typ.id())
|
||||
bb := llvm.AddBasicBlock(fn, typ.name)
|
||||
sw.AddCase(llvm.ConstInt(p.uintptrType, typ.num, false), bb)
|
||||
|
||||
// The function we will redirect to when the interface has this type.
|
||||
|
||||
+102
-5
@@ -8,10 +8,12 @@ package compiler
|
||||
import (
|
||||
"go/token"
|
||||
"go/types"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/aykevl/go-llvm"
|
||||
"github.com/aykevl/tinygo/ir"
|
||||
"github.com/tinygo-org/tinygo/ir"
|
||||
"golang.org/x/tools/go/ssa"
|
||||
"tinygo.org/x/go-llvm"
|
||||
)
|
||||
|
||||
// parseMakeInterface emits the LLVM IR for the *ssa.MakeInterface instruction.
|
||||
@@ -51,7 +53,7 @@ func (c *Compiler) parseMakeInterface(val llvm.Value, typ types.Type, global str
|
||||
itfValue = c.builder.CreateIntToPtr(val, c.i8ptrType, "makeinterface.cast.int")
|
||||
case llvm.PointerTypeKind:
|
||||
itfValue = c.builder.CreateBitCast(val, c.i8ptrType, "makeinterface.cast.ptr")
|
||||
case llvm.StructTypeKind:
|
||||
case llvm.StructTypeKind, llvm.FloatTypeKind, llvm.DoubleTypeKind, llvm.VectorTypeKind:
|
||||
// A bitcast would be useful here, but bitcast doesn't allow
|
||||
// aggregate types. So we'll bitcast it using an alloca.
|
||||
// Hopefully this will get optimized away.
|
||||
@@ -79,14 +81,107 @@ func (c *Compiler) parseMakeInterface(val llvm.Value, typ types.Type, global str
|
||||
// It returns a pointer to an external global which should be replaced with the
|
||||
// real type in the interface lowering pass.
|
||||
func (c *Compiler) getTypeCode(typ types.Type) llvm.Value {
|
||||
global := c.mod.NamedGlobal(typ.String() + "$type")
|
||||
globalName := "type:" + getTypeCodeName(typ)
|
||||
global := c.mod.NamedGlobal(globalName)
|
||||
if global.IsNil() {
|
||||
global = llvm.AddGlobal(c.mod, c.ctx.Int8Type(), typ.String()+"$type")
|
||||
global = llvm.AddGlobal(c.mod, c.ctx.Int8Type(), globalName)
|
||||
global.SetGlobalConstant(true)
|
||||
}
|
||||
return global
|
||||
}
|
||||
|
||||
// getTypeCodeName returns a name for this type that can be used in the
|
||||
// interface lowering pass to assign type codes as expected by the reflect
|
||||
// package. See getTypeCodeNum.
|
||||
func getTypeCodeName(t types.Type) string {
|
||||
name := ""
|
||||
if named, ok := t.(*types.Named); ok {
|
||||
name = "~" + named.String() + ":"
|
||||
t = t.Underlying()
|
||||
}
|
||||
switch t := t.(type) {
|
||||
case *types.Array:
|
||||
return "array:" + name + strconv.FormatInt(t.Len(), 10) + ":" + getTypeCodeName(t.Elem())
|
||||
case *types.Basic:
|
||||
var kind string
|
||||
switch t.Kind() {
|
||||
case types.Bool:
|
||||
kind = "bool"
|
||||
case types.Int:
|
||||
kind = "int"
|
||||
case types.Int8:
|
||||
kind = "int8"
|
||||
case types.Int16:
|
||||
kind = "int16"
|
||||
case types.Int32:
|
||||
kind = "int32"
|
||||
case types.Int64:
|
||||
kind = "int64"
|
||||
case types.Uint:
|
||||
kind = "uint"
|
||||
case types.Uint8:
|
||||
kind = "uint8"
|
||||
case types.Uint16:
|
||||
kind = "uint16"
|
||||
case types.Uint32:
|
||||
kind = "uint32"
|
||||
case types.Uint64:
|
||||
kind = "uint64"
|
||||
case types.Uintptr:
|
||||
kind = "uintptr"
|
||||
case types.Float32:
|
||||
kind = "float32"
|
||||
case types.Float64:
|
||||
kind = "float64"
|
||||
case types.Complex64:
|
||||
kind = "complex64"
|
||||
case types.Complex128:
|
||||
kind = "complex128"
|
||||
case types.String:
|
||||
kind = "string"
|
||||
case types.UnsafePointer:
|
||||
kind = "unsafeptr"
|
||||
default:
|
||||
panic("unknown basic type: " + t.Name())
|
||||
}
|
||||
return "basic:" + name + kind
|
||||
case *types.Chan:
|
||||
return "chan:" + name + getTypeCodeName(t.Elem())
|
||||
case *types.Interface:
|
||||
methods := make([]string, t.NumMethods())
|
||||
for i := 0; i < t.NumMethods(); i++ {
|
||||
methods[i] = getTypeCodeName(t.Method(i).Type())
|
||||
}
|
||||
return "interface:" + name + "{" + strings.Join(methods, ",") + "}"
|
||||
case *types.Map:
|
||||
keyType := getTypeCodeName(t.Key())
|
||||
elemType := getTypeCodeName(t.Elem())
|
||||
return "map:" + name + "{" + keyType + "," + elemType + "}"
|
||||
case *types.Pointer:
|
||||
return "pointer:" + name + getTypeCodeName(t.Elem())
|
||||
case *types.Signature:
|
||||
params := make([]string, t.Params().Len())
|
||||
for i := 0; i < t.Params().Len(); i++ {
|
||||
params[i] = getTypeCodeName(t.Params().At(i).Type())
|
||||
}
|
||||
results := make([]string, t.Results().Len())
|
||||
for i := 0; i < t.Results().Len(); i++ {
|
||||
results[i] = getTypeCodeName(t.Results().At(i).Type())
|
||||
}
|
||||
return "func:" + name + "{" + strings.Join(params, ",") + "}{" + strings.Join(results, ",") + "}"
|
||||
case *types.Slice:
|
||||
return "slice:" + name + getTypeCodeName(t.Elem())
|
||||
case *types.Struct:
|
||||
elems := make([]string, t.NumFields())
|
||||
for i := 0; i < t.NumFields(); i++ {
|
||||
elems[i] = getTypeCodeName(t.Field(i).Type())
|
||||
}
|
||||
return "struct:" + name + "{" + strings.Join(elems, ",") + "}"
|
||||
default:
|
||||
panic("unknown type: " + t.String())
|
||||
}
|
||||
}
|
||||
|
||||
// getTypeMethodSet returns a reference (GEP) to a global method set. This
|
||||
// method set should be unreferenced after the interface lowering pass.
|
||||
func (c *Compiler) getTypeMethodSet(typ types.Type) (llvm.Value, error) {
|
||||
@@ -330,6 +425,8 @@ func (c *Compiler) getInvokeCall(frame *Frame, instr *ssa.CallCommon) (llvm.Valu
|
||||
// Add the context parameter. An interface call never takes a context but we
|
||||
// have to supply the parameter anyway.
|
||||
args = append(args, llvm.Undef(c.i8ptrType))
|
||||
// Add the parent goroutine handle.
|
||||
args = append(args, llvm.Undef(c.i8ptrType))
|
||||
|
||||
return fnCast, args, nil
|
||||
}
|
||||
|
||||
@@ -0,0 +1,95 @@
|
||||
package compiler
|
||||
|
||||
import (
|
||||
"tinygo.org/x/go-llvm"
|
||||
)
|
||||
|
||||
// This file contains helper functions for LLVM that are not exposed in the Go
|
||||
// bindings.
|
||||
|
||||
// Return a list of values (actually, instructions) where this value is used as
|
||||
// an operand.
|
||||
func getUses(value llvm.Value) []llvm.Value {
|
||||
if value.IsNil() {
|
||||
return nil
|
||||
}
|
||||
var uses []llvm.Value
|
||||
use := value.FirstUse()
|
||||
for !use.IsNil() {
|
||||
uses = append(uses, use.User())
|
||||
use = use.NextUse()
|
||||
}
|
||||
return uses
|
||||
}
|
||||
|
||||
// splitBasicBlock splits a LLVM basic block into two parts. All instructions
|
||||
// after afterInst are moved into a new basic block (created right after the
|
||||
// current one) with the given name.
|
||||
func (c *Compiler) splitBasicBlock(afterInst llvm.Value, insertAfter llvm.BasicBlock, name string) llvm.BasicBlock {
|
||||
oldBlock := afterInst.InstructionParent()
|
||||
newBlock := c.ctx.InsertBasicBlock(insertAfter, name)
|
||||
var nextInstructions []llvm.Value // values to move
|
||||
|
||||
// Collect to-be-moved instructions.
|
||||
inst := afterInst
|
||||
for {
|
||||
inst = llvm.NextInstruction(inst)
|
||||
if inst.IsNil() {
|
||||
break
|
||||
}
|
||||
nextInstructions = append(nextInstructions, inst)
|
||||
}
|
||||
|
||||
// Move instructions.
|
||||
c.builder.SetInsertPointAtEnd(newBlock)
|
||||
for _, inst := range nextInstructions {
|
||||
inst.RemoveFromParentAsInstruction()
|
||||
c.builder.Insert(inst)
|
||||
}
|
||||
|
||||
// Find PHI nodes to update.
|
||||
var phiNodes []llvm.Value // PHI nodes to update
|
||||
for bb := insertAfter.Parent().FirstBasicBlock(); !bb.IsNil(); bb = llvm.NextBasicBlock(bb) {
|
||||
for inst := bb.FirstInstruction(); !inst.IsNil(); inst = llvm.NextInstruction(inst) {
|
||||
if inst.IsAPHINode().IsNil() {
|
||||
continue
|
||||
}
|
||||
needsUpdate := false
|
||||
incomingCount := inst.IncomingCount()
|
||||
for i := 0; i < incomingCount; i++ {
|
||||
if inst.IncomingBlock(i) == oldBlock {
|
||||
needsUpdate = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !needsUpdate {
|
||||
// PHI node has no incoming edge from the old block.
|
||||
continue
|
||||
}
|
||||
phiNodes = append(phiNodes, inst)
|
||||
}
|
||||
}
|
||||
|
||||
// Update PHI nodes.
|
||||
for _, phi := range phiNodes {
|
||||
c.builder.SetInsertPointBefore(phi)
|
||||
newPhi := c.builder.CreatePHI(phi.Type(), "")
|
||||
incomingCount := phi.IncomingCount()
|
||||
incomingVals := make([]llvm.Value, incomingCount)
|
||||
incomingBlocks := make([]llvm.BasicBlock, incomingCount)
|
||||
for i := 0; i < incomingCount; i++ {
|
||||
value := phi.IncomingValue(i)
|
||||
block := phi.IncomingBlock(i)
|
||||
if block == oldBlock {
|
||||
block = newBlock
|
||||
}
|
||||
incomingVals[i] = value
|
||||
incomingBlocks[i] = block
|
||||
}
|
||||
newPhi.AddIncoming(incomingVals, incomingBlocks)
|
||||
phi.ReplaceAllUsesWith(newPhi)
|
||||
phi.EraseFromParentAsInstruction()
|
||||
}
|
||||
|
||||
return newBlock
|
||||
}
|
||||
+8
-4
@@ -6,7 +6,7 @@ import (
|
||||
"go/token"
|
||||
"go/types"
|
||||
|
||||
"github.com/aykevl/go-llvm"
|
||||
"tinygo.org/x/go-llvm"
|
||||
)
|
||||
|
||||
func (c *Compiler) emitMapLookup(keyType, valueType types.Type, m, key llvm.Value, commaOk bool, pos token.Pos) (llvm.Value, error) {
|
||||
@@ -29,7 +29,7 @@ func (c *Compiler) emitMapLookup(keyType, valueType types.Type, m, key llvm.Valu
|
||||
params := []llvm.Value{m, keyPtr, mapValuePtr}
|
||||
commaOkValue = c.createRuntimeCall("hashmapBinaryGet", params, "")
|
||||
} else {
|
||||
return llvm.Value{}, c.makeError(pos, "todo: map lookup key type: "+keyType.String())
|
||||
return llvm.Value{}, c.makeError(pos, "only strings, bools, ints or structs of bools/ints are supported as map keys, but got: "+keyType.String())
|
||||
}
|
||||
mapValue := c.builder.CreateLoad(mapValueAlloca, "")
|
||||
if commaOk {
|
||||
@@ -61,7 +61,7 @@ func (c *Compiler) emitMapUpdate(keyType types.Type, m, key, value llvm.Value, p
|
||||
c.createRuntimeCall("hashmapBinarySet", params, "")
|
||||
return nil
|
||||
} else {
|
||||
return c.makeError(pos, "todo: map update key type: "+keyType.String())
|
||||
return c.makeError(pos, "only strings, bools, ints or structs of bools/ints are supported as map keys, but got: "+keyType.String())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -80,7 +80,7 @@ func (c *Compiler) emitMapDelete(keyType types.Type, m, key llvm.Value, pos toke
|
||||
c.createRuntimeCall("hashmapBinaryDelete", params, "")
|
||||
return nil
|
||||
} else {
|
||||
return c.makeError(pos, "todo: map delete key type: "+keyType.String())
|
||||
return c.makeError(pos, "only strings, bools, ints or structs of bools/ints are supported as map keys, but got: "+keyType.String())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -120,6 +120,10 @@ func hashmapIsBinaryKey(keyType types.Type) bool {
|
||||
}
|
||||
}
|
||||
return true
|
||||
case *types.Array:
|
||||
return hashmapIsBinaryKey(keyType.Elem())
|
||||
case *types.Named:
|
||||
return hashmapIsBinaryKey(keyType.Underlying())
|
||||
default:
|
||||
return false
|
||||
}
|
||||
|
||||
+17
-16
@@ -3,7 +3,7 @@ package compiler
|
||||
import (
|
||||
"errors"
|
||||
|
||||
"github.com/aykevl/go-llvm"
|
||||
"tinygo.org/x/go-llvm"
|
||||
)
|
||||
|
||||
// Run the LLVM optimizer over the module.
|
||||
@@ -52,9 +52,18 @@ func (c *Compiler) Optimize(optLevel, sizeLevel int, inlinerThreshold uint) erro
|
||||
// Run TinyGo-specific interprocedural optimizations.
|
||||
c.OptimizeAllocs()
|
||||
c.OptimizeStringToBytes()
|
||||
|
||||
err := c.LowerGoroutines()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
// Must be run at any optimization level.
|
||||
c.LowerInterfaces()
|
||||
err := c.LowerGoroutines()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if err := c.Verify(); err != nil {
|
||||
return errors.New("optimizations caused a verification failure")
|
||||
@@ -70,6 +79,13 @@ func (c *Compiler) Optimize(optLevel, sizeLevel int, inlinerThreshold uint) erro
|
||||
}
|
||||
}
|
||||
|
||||
// Run function passes again, because without it, llvm.coro.size.i32()
|
||||
// doesn't get lowered.
|
||||
for fn := c.mod.FirstFunction(); !fn.IsNil(); fn = llvm.NextFunction(fn) {
|
||||
funcPasses.RunFunc(fn)
|
||||
}
|
||||
funcPasses.FinalizeFunc()
|
||||
|
||||
// Run module passes.
|
||||
modPasses := llvm.NewPassManager()
|
||||
defer modPasses.Dispose()
|
||||
@@ -324,18 +340,3 @@ func (c *Compiler) hasFlag(call, param llvm.Value, kind string) bool {
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// Return a list of values (actually, instructions) where this value is used as
|
||||
// an operand.
|
||||
func getUses(value llvm.Value) []llvm.Value {
|
||||
if value.IsNil() {
|
||||
return nil
|
||||
}
|
||||
var uses []llvm.Value
|
||||
use := value.FirstUse()
|
||||
for !use.IsNil() {
|
||||
uses = append(uses, use.User())
|
||||
use = use.NextUse()
|
||||
}
|
||||
return uses
|
||||
}
|
||||
|
||||
@@ -0,0 +1,156 @@
|
||||
package compiler
|
||||
|
||||
import (
|
||||
"math/big"
|
||||
"strings"
|
||||
)
|
||||
|
||||
var basicTypes = map[string]int64{
|
||||
"bool": 1,
|
||||
"int": 2,
|
||||
"int8": 3,
|
||||
"int16": 4,
|
||||
"int32": 5,
|
||||
"int64": 6,
|
||||
"uint": 7,
|
||||
"uint8": 8,
|
||||
"uint16": 9,
|
||||
"uint32": 10,
|
||||
"uint64": 11,
|
||||
"uintptr": 12,
|
||||
"float32": 13,
|
||||
"float64": 14,
|
||||
"complex64": 15,
|
||||
"complex128": 16,
|
||||
"string": 17,
|
||||
"unsafeptr": 18,
|
||||
}
|
||||
|
||||
func (c *Compiler) assignTypeCodes(typeSlice typeInfoSlice) {
|
||||
fn := c.mod.NamedFunction("reflect.ValueOf")
|
||||
if fn.IsNil() {
|
||||
// reflect.ValueOf is never used, so we can use the most efficient
|
||||
// encoding possible.
|
||||
for i, t := range typeSlice {
|
||||
t.num = uint64(i + 1)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Assign typecodes the way the reflect package expects.
|
||||
fallbackIndex := 1
|
||||
namedTypes := make(map[string]int)
|
||||
for _, t := range typeSlice {
|
||||
if t.name[:5] != "type:" {
|
||||
panic("expected type name to start with 'type:'")
|
||||
}
|
||||
num := c.getTypeCodeNum(t.name[5:], &fallbackIndex, namedTypes)
|
||||
if num.BitLen() > c.uintptrType.IntTypeWidth() || !num.IsUint64() {
|
||||
// TODO: support this in some way, using a side table for example.
|
||||
// That's less efficient but better than not working at all.
|
||||
// Particularly important on systems with 16-bit pointers (e.g.
|
||||
// AVR).
|
||||
panic("compiler: could not store type code number inside interface type code")
|
||||
}
|
||||
t.num = num.Uint64()
|
||||
}
|
||||
}
|
||||
|
||||
// getTypeCodeNum returns the typecode for a given type as expected by the
|
||||
// reflect package. Also see getTypeCodeName, which serializes types to a string
|
||||
// based on a types.Type value for this function.
|
||||
func (c *Compiler) getTypeCodeNum(id string, fallbackIndex *int, namedTypes map[string]int) *big.Int {
|
||||
// Note: see src/reflect/type.go for bit allocations.
|
||||
// A type can be named or unnamed. Example of both:
|
||||
// basic:~foo:uint64
|
||||
// basic:uint64
|
||||
// Extract the class (basic, slice, pointer, etc.), the name, and the
|
||||
// contents of this type ID string. Allocate bits based on that, as
|
||||
// src/runtime/types.go expects.
|
||||
class := id[:strings.IndexByte(id, ':')]
|
||||
value := id[len(class)+1:]
|
||||
name := ""
|
||||
if value[0] == '~' {
|
||||
name = value[1:strings.IndexByte(value, ':')]
|
||||
value = value[len(name)+2:]
|
||||
}
|
||||
if class == "basic" {
|
||||
// Basic types follow the following bit pattern:
|
||||
// ...xxxxx0
|
||||
// where xxxxx is allocated for the 18 possible basic types and all the
|
||||
// upper bits are used to indicate the named type.
|
||||
num, ok := basicTypes[value]
|
||||
if !ok {
|
||||
panic("invalid basic type: " + id)
|
||||
}
|
||||
if name != "" {
|
||||
// This type is named, set the upper bits to the name ID.
|
||||
num |= int64(getNamedTypeNum(namedTypes, name)) << 5
|
||||
}
|
||||
return big.NewInt(num << 1)
|
||||
} else {
|
||||
// Complex types use the following bit pattern:
|
||||
// ...nxxx1
|
||||
// where xxx indicates the complex type (any non-basic type). The upper
|
||||
// bits contain whatever the type contains. Types that wrap a single
|
||||
// other type (channel, interface, pointer, slice) just contain the bits
|
||||
// of the wrapped type. Other types (like struct) have a different
|
||||
// method of encoding the contents of the type.
|
||||
var num *big.Int
|
||||
var classNumber int64
|
||||
switch class {
|
||||
case "chan":
|
||||
num = c.getTypeCodeNum(value, fallbackIndex, namedTypes)
|
||||
classNumber = 0
|
||||
case "interface":
|
||||
num = big.NewInt(int64(*fallbackIndex))
|
||||
*fallbackIndex++
|
||||
classNumber = 1
|
||||
case "pointer":
|
||||
num = c.getTypeCodeNum(value, fallbackIndex, namedTypes)
|
||||
classNumber = 2
|
||||
case "slice":
|
||||
num = c.getTypeCodeNum(value, fallbackIndex, namedTypes)
|
||||
classNumber = 3
|
||||
case "array":
|
||||
num = big.NewInt(int64(*fallbackIndex))
|
||||
*fallbackIndex++
|
||||
classNumber = 4
|
||||
case "func":
|
||||
num = big.NewInt(int64(*fallbackIndex))
|
||||
*fallbackIndex++
|
||||
classNumber = 5
|
||||
case "map":
|
||||
num = big.NewInt(int64(*fallbackIndex))
|
||||
*fallbackIndex++
|
||||
classNumber = 6
|
||||
case "struct":
|
||||
num = big.NewInt(int64(*fallbackIndex))
|
||||
*fallbackIndex++
|
||||
classNumber = 7
|
||||
default:
|
||||
panic("unknown type kind: " + id)
|
||||
}
|
||||
if name == "" {
|
||||
num.Lsh(num, 5).Or(num, big.NewInt((classNumber<<1)+1))
|
||||
} else {
|
||||
// TODO: store num in a sidetable
|
||||
num = big.NewInt(int64(getNamedTypeNum(namedTypes, name))<<1 | 1)
|
||||
num.Lsh(num, 4).Or(num, big.NewInt((classNumber<<1)+1))
|
||||
}
|
||||
return num
|
||||
}
|
||||
}
|
||||
|
||||
// getNamedTypeNum returns an appropriate (unique) number for the given named
|
||||
// type. If the name already has a number that number is returned, else a new
|
||||
// number is returned. The number is always non-zero.
|
||||
func getNamedTypeNum(namedTypes map[string]int, name string) int {
|
||||
if num, ok := namedTypes[name]; ok {
|
||||
return num
|
||||
} else {
|
||||
num = len(namedTypes) + 1
|
||||
namedTypes[name] = num
|
||||
return num
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,139 @@
|
||||
package compiler
|
||||
|
||||
// This file implements the syscall.Syscall and syscall.Syscall6 instructions as
|
||||
// compiler builtins.
|
||||
|
||||
import (
|
||||
"go/constant"
|
||||
"strconv"
|
||||
|
||||
"golang.org/x/tools/go/ssa"
|
||||
"tinygo.org/x/go-llvm"
|
||||
)
|
||||
|
||||
// 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)
|
||||
var syscallResult llvm.Value
|
||||
switch {
|
||||
case c.GOARCH == "amd64" && c.GOOS == "linux":
|
||||
// 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)}
|
||||
argTypes := []llvm.Type{c.uintptrType}
|
||||
// Constraints will look something like:
|
||||
// "={rax},0,{rdi},{rsi},{rdx},{r10},{r8},{r9},~{rcx},~{r11}"
|
||||
constraints := "={rax},0"
|
||||
for i, arg := range call.Args[1:] {
|
||||
constraints += "," + [...]string{
|
||||
"{rdi}",
|
||||
"{rsi}",
|
||||
"{rdx}",
|
||||
"{r10}",
|
||||
"{r8}",
|
||||
"{r9}",
|
||||
}[i]
|
||||
llvmValue, err := c.parseExpr(frame, arg)
|
||||
if err != nil {
|
||||
return llvm.Value{}, err
|
||||
}
|
||||
args = append(args, llvmValue)
|
||||
argTypes = append(argTypes, llvmValue.Type())
|
||||
}
|
||||
constraints += ",~{rcx},~{r11}"
|
||||
fnType := llvm.FunctionType(c.uintptrType, argTypes, false)
|
||||
target := llvm.InlineAsm(fnType, "syscall", constraints, true, false, llvm.InlineAsmDialectIntel)
|
||||
syscallResult = c.builder.CreateCall(target, args, "")
|
||||
case c.GOARCH == "arm" && c.GOOS == "linux":
|
||||
// Implement the EABI system call convention for Linux.
|
||||
// Source: syscall(2) man page.
|
||||
args := []llvm.Value{}
|
||||
argTypes := []llvm.Type{}
|
||||
// Constraints will look something like:
|
||||
// ={r0},0,{r1},{r2},{r7},~{r3}
|
||||
constraints := "={r0}"
|
||||
for i, arg := range call.Args[1:] {
|
||||
constraints += "," + [...]string{
|
||||
"0", // tie to output
|
||||
"{r1}",
|
||||
"{r2}",
|
||||
"{r3}",
|
||||
"{r4}",
|
||||
"{r5}",
|
||||
"{r6}",
|
||||
}[i]
|
||||
llvmValue, err := c.parseExpr(frame, arg)
|
||||
if err != nil {
|
||||
return llvm.Value{}, err
|
||||
}
|
||||
args = append(args, llvmValue)
|
||||
argTypes = append(argTypes, llvmValue.Type())
|
||||
}
|
||||
args = append(args, llvm.ConstInt(c.uintptrType, num, false))
|
||||
argTypes = append(argTypes, c.uintptrType)
|
||||
constraints += ",{r7}" // syscall number
|
||||
for i := len(call.Args) - 1; i < 4; i++ {
|
||||
// r0-r3 get clobbered after the syscall returns
|
||||
constraints += ",~{r" + strconv.Itoa(i) + "}"
|
||||
}
|
||||
fnType := llvm.FunctionType(c.uintptrType, argTypes, false)
|
||||
target := llvm.InlineAsm(fnType, "svc #0", constraints, true, false, 0)
|
||||
syscallResult = c.builder.CreateCall(target, args, "")
|
||||
case c.GOARCH == "arm64" && c.GOOS == "linux":
|
||||
// Source: syscall(2) man page.
|
||||
args := []llvm.Value{}
|
||||
argTypes := []llvm.Type{}
|
||||
// Constraints will look something like:
|
||||
// ={x0},0,{x1},{x2},{x8},~{x3},~{x4},~{x5},~{x6},~{x7},~{x16},~{x17}
|
||||
constraints := "={x0}"
|
||||
for i, arg := range call.Args[1:] {
|
||||
constraints += "," + [...]string{
|
||||
"0", // tie to output
|
||||
"{x1}",
|
||||
"{x2}",
|
||||
"{x3}",
|
||||
"{x4}",
|
||||
"{x5}",
|
||||
}[i]
|
||||
llvmValue, err := c.parseExpr(frame, arg)
|
||||
if err != nil {
|
||||
return llvm.Value{}, err
|
||||
}
|
||||
args = append(args, llvmValue)
|
||||
argTypes = append(argTypes, llvmValue.Type())
|
||||
}
|
||||
args = append(args, llvm.ConstInt(c.uintptrType, num, false))
|
||||
argTypes = append(argTypes, c.uintptrType)
|
||||
constraints += ",{x8}" // syscall number
|
||||
for i := len(call.Args) - 1; i < 8; i++ {
|
||||
// x0-x7 may get clobbered during the syscall following the aarch64
|
||||
// calling convention.
|
||||
constraints += ",~{x" + strconv.Itoa(i) + "}"
|
||||
}
|
||||
constraints += ",~{x16},~{x17}" // scratch registers
|
||||
fnType := llvm.FunctionType(c.uintptrType, argTypes, false)
|
||||
target := llvm.InlineAsm(fnType, "svc #0", constraints, true, false, 0)
|
||||
syscallResult = c.builder.CreateCall(target, args, "")
|
||||
default:
|
||||
return llvm.Value{}, c.makeError(call.Pos(), "unknown GOOS/GOARCH for syscall: "+c.GOOS+"/"+c.GOARCH)
|
||||
}
|
||||
// Return values: r0, r1, err uintptr
|
||||
// Pseudocode:
|
||||
// var err uintptr
|
||||
// if syscallResult < 0 && syscallResult > -4096 {
|
||||
// err = -syscallResult
|
||||
// }
|
||||
// return syscallResult, 0, err
|
||||
zero := llvm.ConstInt(c.uintptrType, 0, false)
|
||||
inrange1 := c.builder.CreateICmp(llvm.IntSLT, syscallResult, llvm.ConstInt(c.uintptrType, 0, false), "")
|
||||
inrange2 := c.builder.CreateICmp(llvm.IntSGT, syscallResult, llvm.ConstInt(c.uintptrType, 0xfffffffffffff000, true), "") // -4096
|
||||
hasError := c.builder.CreateAnd(inrange1, inrange2, "")
|
||||
errResult := c.builder.CreateSelect(hasError, c.builder.CreateNot(syscallResult, ""), zero, "syscallError")
|
||||
retval := llvm.Undef(llvm.StructType([]llvm.Type{c.uintptrType, c.uintptrType, c.uintptrType}, false))
|
||||
retval = c.builder.CreateInsertValue(retval, syscallResult, 0, "")
|
||||
retval = c.builder.CreateInsertValue(retval, zero, 1, "")
|
||||
retval = c.builder.CreateInsertValue(retval, errResult, 2, "")
|
||||
return retval, nil
|
||||
}
|
||||
+1
-1
@@ -3,7 +3,7 @@ package interp
|
||||
// This file provides useful types for errors encountered during IR evaluation.
|
||||
|
||||
import (
|
||||
"github.com/aykevl/go-llvm"
|
||||
"tinygo.org/x/go-llvm"
|
||||
)
|
||||
|
||||
type Unsupported struct {
|
||||
|
||||
+9
-3
@@ -7,7 +7,7 @@ import (
|
||||
"errors"
|
||||
"strings"
|
||||
|
||||
"github.com/aykevl/go-llvm"
|
||||
"tinygo.org/x/go-llvm"
|
||||
)
|
||||
|
||||
type frame struct {
|
||||
@@ -244,13 +244,18 @@ func (fr *frame) evalBasicBlock(bb, incoming llvm.BasicBlock, indent string) (re
|
||||
case callee.Name() == "runtime.hashmapStringSet":
|
||||
// set a string key in the map
|
||||
m := fr.getLocal(inst.Operand(0)).(*MapValue)
|
||||
// "key" is a Go string value, which in the TinyGo calling convention is split up
|
||||
// into separate pointer and length parameters.
|
||||
keyBuf := fr.getLocal(inst.Operand(1))
|
||||
keyLen := fr.getLocal(inst.Operand(2))
|
||||
valPtr := fr.getLocal(inst.Operand(3))
|
||||
m.PutString(keyBuf, keyLen, valPtr)
|
||||
case callee.Name() == "runtime.hashmapBinarySet":
|
||||
// set a binary (int etc.) key in the map
|
||||
// TODO: unimplemented
|
||||
m := fr.getLocal(inst.Operand(0)).(*MapValue)
|
||||
keyBuf := fr.getLocal(inst.Operand(1))
|
||||
valPtr := fr.getLocal(inst.Operand(2))
|
||||
m.PutBinary(keyBuf, valPtr)
|
||||
case callee.Name() == "runtime.stringConcat":
|
||||
// adding two strings together
|
||||
buf1Ptr := fr.getLocal(inst.Operand(0))
|
||||
@@ -303,7 +308,8 @@ func (fr *frame) evalBasicBlock(bb, incoming llvm.BasicBlock, indent string) (re
|
||||
ret = llvm.ConstInsertValue(ret, retLen, []uint32{2}) // cap
|
||||
fr.locals[inst] = &LocalValue{fr.Eval, ret}
|
||||
case callee.Name() == "runtime.makeInterface":
|
||||
fr.locals[inst] = &LocalValue{fr.Eval, llvm.ConstPtrToInt(inst.Operand(0), fr.TargetData.IntPtrType())}
|
||||
uintptrType := callee.Type().Context().IntType(fr.TargetData.PointerSize() * 8)
|
||||
fr.locals[inst] = &LocalValue{fr.Eval, llvm.ConstPtrToInt(inst.Operand(0), uintptrType)}
|
||||
case strings.HasPrefix(callee.Name(), "runtime.print") || callee.Name() == "runtime._panic":
|
||||
// This are all print instructions, which necessarily have side
|
||||
// effects but no results.
|
||||
|
||||
+4
-3
@@ -10,7 +10,7 @@ import (
|
||||
"errors"
|
||||
"strings"
|
||||
|
||||
"github.com/aykevl/go-llvm"
|
||||
"tinygo.org/x/go-llvm"
|
||||
)
|
||||
|
||||
type Eval struct {
|
||||
@@ -63,14 +63,15 @@ func Run(mod llvm.Module, targetData llvm.TargetData, debug bool) error {
|
||||
return errors.New("expected all instructions in " + name + " to be *.init() calls")
|
||||
}
|
||||
pkgName := initName[:len(initName)-5]
|
||||
_, err := e.Function(call.CalledValue(), []Value{&LocalValue{e, undefPtr}}, pkgName)
|
||||
fn := call.CalledValue()
|
||||
call.EraseFromParentAsInstruction()
|
||||
_, err := e.Function(fn, []Value{&LocalValue{e, undefPtr}, &LocalValue{e, undefPtr}}, pkgName)
|
||||
if err == ErrUnreachable {
|
||||
break
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
call.EraseFromParentAsInstruction()
|
||||
}
|
||||
|
||||
return nil
|
||||
|
||||
+22
-8
@@ -1,7 +1,7 @@
|
||||
package interp
|
||||
|
||||
import (
|
||||
"github.com/aykevl/go-llvm"
|
||||
"tinygo.org/x/go-llvm"
|
||||
)
|
||||
|
||||
type sideEffectSeverity int
|
||||
@@ -24,6 +24,13 @@ type sideEffectResult struct {
|
||||
// returns whether this function has side effects and if it does, which globals
|
||||
// it mentions anywhere in this function or any called functions.
|
||||
func (e *Eval) hasSideEffects(fn llvm.Value) *sideEffectResult {
|
||||
switch fn.Name() {
|
||||
case "runtime.alloc":
|
||||
// Cannot be scanned but can be interpreted.
|
||||
return &sideEffectResult{severity: sideEffectNone}
|
||||
case "runtime._panic":
|
||||
return &sideEffectResult{severity: sideEffectLimited}
|
||||
}
|
||||
if e.sideEffectFuncs == nil {
|
||||
e.sideEffectFuncs = make(map[llvm.Value]*sideEffectResult)
|
||||
}
|
||||
@@ -73,25 +80,32 @@ func (e *Eval) hasSideEffects(fn llvm.Value) *sideEffectResult {
|
||||
result.updateSeverity(sideEffectAll)
|
||||
continue
|
||||
}
|
||||
name := child.Name()
|
||||
if child.IsDeclaration() {
|
||||
if name == "runtime.makeInterface" {
|
||||
switch child.Name() {
|
||||
case "runtime.makeInterface":
|
||||
// Can be interpreted so does not have side effects.
|
||||
continue
|
||||
}
|
||||
// External function call. Assume only limited side effects
|
||||
// (no affected globals, etc.).
|
||||
if result.hasLocalSideEffects(dirtyLocals, inst) {
|
||||
if e.hasLocalSideEffects(dirtyLocals, inst) {
|
||||
result.updateSeverity(sideEffectLimited)
|
||||
}
|
||||
continue
|
||||
}
|
||||
childSideEffects := e.hasSideEffects(fn)
|
||||
childSideEffects := e.hasSideEffects(child)
|
||||
switch childSideEffects.severity {
|
||||
case sideEffectInProgress, sideEffectNone:
|
||||
// no side effects or recursive function - continue scanning
|
||||
case sideEffectLimited:
|
||||
// The return value may be problematic.
|
||||
if e.hasLocalSideEffects(dirtyLocals, inst) {
|
||||
result.updateSeverity(sideEffectLimited)
|
||||
}
|
||||
case sideEffectAll:
|
||||
result.updateSeverity(sideEffectAll)
|
||||
default:
|
||||
result.update(childSideEffects)
|
||||
panic("unreachable")
|
||||
}
|
||||
case llvm.Load, llvm.Store:
|
||||
if inst.IsVolatile() {
|
||||
@@ -118,7 +132,7 @@ func (e *Eval) hasSideEffects(fn llvm.Value) *sideEffectResult {
|
||||
// hasLocalSideEffects checks whether the given instruction flows into a branch
|
||||
// or return instruction, in which case the whole function must be marked as
|
||||
// having side effects and be called at runtime.
|
||||
func (r *sideEffectResult) hasLocalSideEffects(dirtyLocals map[llvm.Value]struct{}, inst llvm.Value) bool {
|
||||
func (e *Eval) hasLocalSideEffects(dirtyLocals map[llvm.Value]struct{}, inst llvm.Value) bool {
|
||||
if _, ok := dirtyLocals[inst]; ok {
|
||||
// It is already known that this local is dirty.
|
||||
return true
|
||||
@@ -156,7 +170,7 @@ func (r *sideEffectResult) hasLocalSideEffects(dirtyLocals map[llvm.Value]struct
|
||||
// For a list:
|
||||
// https://godoc.org/github.com/llvm-mirror/llvm/bindings/go/llvm#Opcode
|
||||
dirtyLocals[user] = struct{}{}
|
||||
if r.hasLocalSideEffects(dirtyLocals, user) {
|
||||
if e.hasLocalSideEffects(dirtyLocals, user) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
+4
-1
@@ -1,7 +1,7 @@
|
||||
package interp
|
||||
|
||||
import (
|
||||
"github.com/aykevl/go-llvm"
|
||||
"tinygo.org/x/go-llvm"
|
||||
)
|
||||
|
||||
// Return a list of values (actually, instructions) where this value is used as
|
||||
@@ -63,6 +63,9 @@ func getZeroValue(typ llvm.Type) llvm.Value {
|
||||
// getStringBytes loads the byte slice of a Go string represented as a
|
||||
// {ptr, len} pair.
|
||||
func getStringBytes(strPtr Value, strLen llvm.Value) []byte {
|
||||
if !strLen.IsConstant() {
|
||||
panic("getStringBytes with a non-constant length")
|
||||
}
|
||||
buf := make([]byte, strLen.ZExtValue())
|
||||
for i := range buf {
|
||||
c := strPtr.GetElementPtr([]uint32{uint32(i)}).Load()
|
||||
|
||||
+40
-1
@@ -5,7 +5,7 @@ package interp
|
||||
import (
|
||||
"strconv"
|
||||
|
||||
"github.com/aykevl/go-llvm"
|
||||
"tinygo.org/x/go-llvm"
|
||||
)
|
||||
|
||||
// A Value is a LLVM value with some extra methods attached for easier
|
||||
@@ -451,6 +451,13 @@ func (v *MapValue) Value() llvm.Value {
|
||||
keyBuf[i] = byte(n)
|
||||
n >>= 8
|
||||
}
|
||||
} else if key.Type().TypeKind() == llvm.ArrayTypeKind &&
|
||||
key.Type().ElementType().TypeKind() == llvm.IntegerTypeKind &&
|
||||
key.Type().ElementType().IntTypeWidth() == 8 {
|
||||
keyBuf = make([]byte, v.Eval.TargetData.TypeAllocSize(key.Type()))
|
||||
for i := range keyBuf {
|
||||
keyBuf[i] = byte(llvm.ConstExtractValue(llvmKey, []uint32{uint32(i)}).ZExtValue())
|
||||
}
|
||||
} else {
|
||||
panic("interp: map key type not implemented: " + key.Type().String())
|
||||
}
|
||||
@@ -561,6 +568,38 @@ func (v *MapValue) PutString(keyBuf, keyLen, valPtr Value) {
|
||||
v.Values = append(v.Values, &LocalValue{v.Eval, value})
|
||||
}
|
||||
|
||||
// PutBinary does a map assign operation.
|
||||
func (v *MapValue) PutBinary(keyPtr, valPtr Value) {
|
||||
if !v.Underlying.IsNil() {
|
||||
panic("map already created")
|
||||
}
|
||||
|
||||
var value llvm.Value
|
||||
switch valPtr := valPtr.(type) {
|
||||
case *PointerCastValue:
|
||||
value = valPtr.Underlying.Load()
|
||||
if v.ValueType.IsNil() {
|
||||
v.ValueType = value.Type()
|
||||
if int(v.Eval.TargetData.TypeAllocSize(v.ValueType)) != v.ValueSize {
|
||||
panic("interp: map store value type has the wrong size")
|
||||
}
|
||||
} else {
|
||||
if value.Type() != v.ValueType {
|
||||
panic("interp: map store value type is inconsistent")
|
||||
}
|
||||
}
|
||||
default:
|
||||
panic("interp: todo: handle map value pointer")
|
||||
}
|
||||
|
||||
key := keyPtr.(*PointerCastValue).Underlying.Load()
|
||||
v.KeyType = key.Type()
|
||||
|
||||
// TODO: avoid duplicate keys
|
||||
v.Keys = append(v.Keys, &LocalValue{v.Eval, key})
|
||||
v.Values = append(v.Values, &LocalValue{v.Eval, value})
|
||||
}
|
||||
|
||||
// Get FNV-1a hash of this string.
|
||||
//
|
||||
// https://en.wikipedia.org/wiki/Fowler%E2%80%93Noll%E2%80%93Vo_hash_function#FNV-1a_hash
|
||||
|
||||
@@ -7,9 +7,9 @@ import (
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"github.com/aykevl/go-llvm"
|
||||
"github.com/aykevl/tinygo/loader"
|
||||
"github.com/tinygo-org/tinygo/loader"
|
||||
"golang.org/x/tools/go/ssa"
|
||||
"tinygo.org/x/go-llvm"
|
||||
)
|
||||
|
||||
// This file provides a wrapper around go/ssa values and adds extra
|
||||
@@ -18,31 +18,26 @@ import (
|
||||
// View on all functions, types, and globals in a program, with analysis
|
||||
// results.
|
||||
type Program struct {
|
||||
Program *ssa.Program
|
||||
LoaderProgram *loader.Program
|
||||
mainPkg *ssa.Package
|
||||
Functions []*Function
|
||||
functionMap map[*ssa.Function]*Function
|
||||
Globals []*Global
|
||||
globalMap map[*ssa.Global]*Global
|
||||
comments map[string]*ast.CommentGroup
|
||||
NamedTypes []*NamedType
|
||||
needsScheduler bool
|
||||
goCalls []*ssa.Go
|
||||
Program *ssa.Program
|
||||
LoaderProgram *loader.Program
|
||||
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.
|
||||
type Function struct {
|
||||
*ssa.Function
|
||||
LLVMFn llvm.Value
|
||||
linkName string // go:linkname, go:export, go:interrupt
|
||||
exported bool // go:export
|
||||
nobounds bool // go:nobounds
|
||||
blocking bool // calculated by AnalyseBlockingRecursive
|
||||
flag bool // used by dead code elimination
|
||||
interrupt bool // go:interrupt
|
||||
parents []*Function // calculated by AnalyseCallgraph
|
||||
children []*Function // calculated by AnalyseCallgraph
|
||||
linkName string // go:linkname, go:export, go:interrupt
|
||||
exported bool // go:export
|
||||
nobounds bool // go:nobounds
|
||||
flag bool // used by dead code elimination
|
||||
interrupt bool // go:interrupt
|
||||
}
|
||||
|
||||
// Global variable, possibly constant.
|
||||
@@ -274,10 +269,16 @@ func (f *Function) parsePragmas() {
|
||||
}
|
||||
if decl, ok := f.Syntax().(*ast.FuncDecl); ok && decl.Doc != nil {
|
||||
for _, comment := range decl.Doc.List {
|
||||
if !strings.HasPrefix(comment.Text, "//go:") {
|
||||
text := comment.Text
|
||||
if strings.HasPrefix(text, "//export ") {
|
||||
// Rewrite '//export' to '//go:export' for compatibility with
|
||||
// gc.
|
||||
text = "//go:" + text[2:]
|
||||
}
|
||||
if !strings.HasPrefix(text, "//go:") {
|
||||
continue
|
||||
}
|
||||
parts := strings.Fields(comment.Text)
|
||||
parts := strings.Fields(text)
|
||||
switch parts[0] {
|
||||
case "//go:export":
|
||||
if len(parts) != 2 {
|
||||
@@ -327,7 +328,7 @@ func (f *Function) IsNoBounds() bool {
|
||||
|
||||
// Return true iff this function is externally visible.
|
||||
func (f *Function) IsExported() bool {
|
||||
return f.exported
|
||||
return f.exported || f.CName() != ""
|
||||
}
|
||||
|
||||
// Return true for functions annotated with //go:interrupt. The function name is
|
||||
@@ -335,7 +336,7 @@ func (f *Function) IsExported() bool {
|
||||
//
|
||||
// On some platforms (like AVR), interrupts need a special compiler flag.
|
||||
func (f *Function) IsInterrupt() bool {
|
||||
return f.exported
|
||||
return f.interrupt
|
||||
}
|
||||
|
||||
// Return the link name for this function.
|
||||
@@ -394,11 +395,25 @@ 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
|
||||
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 ""
|
||||
}
|
||||
|
||||
func (g *Global) Initializer() Value {
|
||||
|
||||
+2
-123
@@ -56,110 +56,6 @@ func signature(sig *types.Signature) string {
|
||||
return s
|
||||
}
|
||||
|
||||
// Fill in parents of all functions.
|
||||
//
|
||||
// All packages need to be added before this pass can run, or it will produce
|
||||
// incorrect results.
|
||||
func (p *Program) AnalyseCallgraph() {
|
||||
for _, f := range p.Functions {
|
||||
// Clear, if AnalyseCallgraph has been called before.
|
||||
f.children = nil
|
||||
f.parents = nil
|
||||
|
||||
for _, block := range f.Blocks {
|
||||
for _, instr := range block.Instrs {
|
||||
switch instr := instr.(type) {
|
||||
case *ssa.Call:
|
||||
if instr.Common().IsInvoke() {
|
||||
continue
|
||||
}
|
||||
switch call := instr.Call.Value.(type) {
|
||||
case *ssa.Builtin:
|
||||
// ignore
|
||||
case *ssa.Function:
|
||||
if isCGoInternal(call.Name()) {
|
||||
continue
|
||||
}
|
||||
child := p.GetFunction(call)
|
||||
if child.CName() != "" {
|
||||
continue // assume non-blocking
|
||||
}
|
||||
if child.RelString(nil) == "time.Sleep" {
|
||||
f.blocking = true
|
||||
}
|
||||
f.children = append(f.children, child)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
for _, f := range p.Functions {
|
||||
for _, child := range f.children {
|
||||
child.parents = append(child.parents, f)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Analyse which functions are recursively blocking.
|
||||
//
|
||||
// Depends on AnalyseCallgraph.
|
||||
func (p *Program) AnalyseBlockingRecursive() {
|
||||
worklist := make([]*Function, 0)
|
||||
|
||||
// Fill worklist with directly blocking functions.
|
||||
for _, f := range p.Functions {
|
||||
if f.blocking {
|
||||
worklist = append(worklist, f)
|
||||
}
|
||||
}
|
||||
|
||||
// Keep reducing this worklist by marking a function as recursively blocking
|
||||
// from the worklist and pushing all its parents that are non-blocking.
|
||||
// This is somewhat similar to a worklist in a mark-sweep garbage collector.
|
||||
// The work items are then grey objects.
|
||||
for len(worklist) != 0 {
|
||||
// Pick the topmost.
|
||||
f := worklist[len(worklist)-1]
|
||||
worklist = worklist[:len(worklist)-1]
|
||||
for _, parent := range f.parents {
|
||||
if !parent.blocking {
|
||||
parent.blocking = true
|
||||
worklist = append(worklist, parent)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Check whether we need a scheduler. A scheduler is only necessary when there
|
||||
// are go calls that start blocking functions (if they're not blocking, the go
|
||||
// function can be turned into a regular function call).
|
||||
//
|
||||
// Depends on AnalyseBlockingRecursive.
|
||||
func (p *Program) AnalyseGoCalls() {
|
||||
p.goCalls = nil
|
||||
for _, f := range p.Functions {
|
||||
for _, block := range f.Blocks {
|
||||
for _, instr := range block.Instrs {
|
||||
switch instr := instr.(type) {
|
||||
case *ssa.Go:
|
||||
p.goCalls = append(p.goCalls, instr)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
for _, instr := range p.goCalls {
|
||||
switch instr := instr.Call.Value.(type) {
|
||||
case *ssa.Builtin:
|
||||
case *ssa.Function:
|
||||
if p.functionMap[instr].blocking {
|
||||
p.needsScheduler = true
|
||||
}
|
||||
default:
|
||||
panic("unknown go call function type")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Simple pass that removes dead code. This pass makes later analysis passes
|
||||
// more useful.
|
||||
func (p *Program) SimpleDCE() {
|
||||
@@ -172,10 +68,11 @@ func (p *Program) SimpleDCE() {
|
||||
// functions.
|
||||
main := p.mainPkg.Members["main"].(*ssa.Function)
|
||||
runtimePkg := p.Program.ImportedPackage("runtime")
|
||||
mathPkg := p.Program.ImportedPackage("math")
|
||||
p.GetFunction(main).flag = true
|
||||
worklist := []*ssa.Function{main}
|
||||
for _, f := range p.Functions {
|
||||
if f.exported || f.Synthetic == "package initializer" || f.Pkg == runtimePkg {
|
||||
if f.exported || f.Synthetic == "package initializer" || f.Pkg == runtimePkg || (f.Pkg == mathPkg && f.Pkg != nil) {
|
||||
if f.flag || isCGoInternal(f.Name()) {
|
||||
continue
|
||||
}
|
||||
@@ -239,21 +136,3 @@ func (p *Program) SimpleDCE() {
|
||||
}
|
||||
p.Functions = livefunctions
|
||||
}
|
||||
|
||||
// Whether this function needs a scheduler.
|
||||
//
|
||||
// Depends on AnalyseGoCalls.
|
||||
func (p *Program) NeedsScheduler() bool {
|
||||
return p.needsScheduler
|
||||
}
|
||||
|
||||
// Whether this function blocks. Builtins are also accepted for convenience.
|
||||
// They will always be non-blocking.
|
||||
//
|
||||
// Depends on AnalyseBlockingRecursive.
|
||||
func (p *Program) IsBlocking(f *Function) bool {
|
||||
if !p.needsScheduler {
|
||||
return false
|
||||
}
|
||||
return f.blocking
|
||||
}
|
||||
|
||||
+1
-1
Submodule lib/cmsis-svd updated: 2ab163c2ae...b6f0a65ac3
@@ -0,0 +1,66 @@
|
||||
// +build byollvm
|
||||
|
||||
package main
|
||||
|
||||
// This file provides a Link() function that uses the bundled lld if possible.
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"os"
|
||||
"os/exec"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
/*
|
||||
#include <stdbool.h>
|
||||
#include <stdlib.h>
|
||||
bool tinygo_link_elf(int argc, char **argv);
|
||||
bool tinygo_link_wasm(int argc, char **argv);
|
||||
*/
|
||||
import "C"
|
||||
|
||||
// Link invokes a linker with the given name and flags.
|
||||
//
|
||||
// This version uses the built-in linker when trying to use lld.
|
||||
func Link(dir, linker string, flags ...string) error {
|
||||
switch linker {
|
||||
case "ld.lld", commands["ld.lld"]:
|
||||
flags = append([]string{"tinygo:" + linker}, flags...)
|
||||
var cflag *C.char
|
||||
buf := C.calloc(C.size_t(len(flags)), C.size_t(unsafe.Sizeof(cflag)))
|
||||
cflags := (*[1 << 10]*C.char)(unsafe.Pointer(buf))[:len(flags):len(flags)]
|
||||
for i, flag := range flags {
|
||||
cflag := C.CString(flag)
|
||||
cflags[i] = cflag
|
||||
defer C.free(unsafe.Pointer(cflag))
|
||||
}
|
||||
ok := C.tinygo_link_elf(C.int(len(flags)), (**C.char)(buf))
|
||||
if !ok {
|
||||
return errors.New("failed to link using built-in ld.lld")
|
||||
}
|
||||
return nil
|
||||
case "wasm-ld", commands["wasm-ld"]:
|
||||
flags = append([]string{"tinygo:" + linker}, flags...)
|
||||
var cflag *C.char
|
||||
buf := C.calloc(C.size_t(len(flags)), C.size_t(unsafe.Sizeof(cflag)))
|
||||
defer C.free(buf)
|
||||
cflags := (*[1 << 10]*C.char)(unsafe.Pointer(buf))[:len(flags):len(flags)]
|
||||
for i, flag := range flags {
|
||||
cflag := C.CString(flag)
|
||||
cflags[i] = cflag
|
||||
defer C.free(unsafe.Pointer(cflag))
|
||||
}
|
||||
ok := C.tinygo_link_wasm(C.int(len(flags)), (**C.char)(buf))
|
||||
if !ok {
|
||||
return errors.New("failed to link using built-in wasm-ld")
|
||||
}
|
||||
return nil
|
||||
default:
|
||||
// Fall back to external command.
|
||||
cmd := exec.Command(linker, flags...)
|
||||
cmd.Stdout = os.Stdout
|
||||
cmd.Stderr = os.Stderr
|
||||
cmd.Dir = dir
|
||||
return cmd.Run()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
// +build !byollvm
|
||||
|
||||
package main
|
||||
|
||||
// This file provides a Link() function that always runs an external command. It
|
||||
// is provided for when tinygo is built without linking to liblld.
|
||||
|
||||
import (
|
||||
"os"
|
||||
"os/exec"
|
||||
)
|
||||
|
||||
// Link invokes a linker with the given name and arguments.
|
||||
//
|
||||
// This version always runs the linker as an external command.
|
||||
func Link(dir, linker string, flags ...string) error {
|
||||
cmd := exec.Command(linker, flags...)
|
||||
cmd.Stdout = os.Stdout
|
||||
cmd.Stderr = os.Stderr
|
||||
cmd.Dir = dir
|
||||
return cmd.Run()
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
// +build byollvm
|
||||
|
||||
// This file provides C wrappers for liblld.
|
||||
|
||||
#include <lld/Common/Driver.h>
|
||||
|
||||
extern "C" {
|
||||
|
||||
bool tinygo_link_elf(int argc, char **argv) {
|
||||
std::vector<const char*> args(argv, argv + argc);
|
||||
return lld::elf::link(args, false);
|
||||
}
|
||||
|
||||
bool tinygo_link_wasm(int argc, char **argv) {
|
||||
std::vector<const char*> args(argv, argv + argc);
|
||||
return lld::wasm::link(args, false);
|
||||
}
|
||||
|
||||
} // external "C"
|
||||
+158
-106
@@ -9,36 +9,41 @@ import (
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"golang.org/x/tools/go/ast/astutil"
|
||||
)
|
||||
|
||||
// fileInfo holds all Cgo-related information of a given *ast.File.
|
||||
type fileInfo struct {
|
||||
*ast.File
|
||||
filename string
|
||||
functions []*functionInfo
|
||||
typedefs []*typedefInfo
|
||||
functions map[string]*functionInfo
|
||||
globals map[string]*globalInfo
|
||||
typedefs map[string]*typedefInfo
|
||||
importCPos token.Pos
|
||||
}
|
||||
|
||||
// functionInfo stores some information about a Cgo function found by libclang
|
||||
// and declared in the AST.
|
||||
type functionInfo struct {
|
||||
name string
|
||||
args []paramInfo
|
||||
result string
|
||||
args []paramInfo
|
||||
results *ast.FieldList
|
||||
}
|
||||
|
||||
// paramInfo is a parameter of a Cgo function (see functionInfo).
|
||||
type paramInfo struct {
|
||||
name string
|
||||
typeName string
|
||||
typeExpr ast.Expr
|
||||
}
|
||||
|
||||
// typedefInfo contains information about a single typedef in C.
|
||||
type typedefInfo struct {
|
||||
newName string // newly defined type name
|
||||
oldName string // old type name, may be something like "unsigned long long"
|
||||
size int // size in bytes
|
||||
typeExpr ast.Expr
|
||||
}
|
||||
|
||||
// globalInfo contains information about a declared global variable in C.
|
||||
type globalInfo struct {
|
||||
typeExpr ast.Expr
|
||||
}
|
||||
|
||||
// cgoAliases list type aliases between Go and C, for types that are equivalent
|
||||
@@ -75,8 +80,11 @@ typedef unsigned long long _Cgo_ulonglong;
|
||||
// comment with libclang, and modifies the AST to use this information.
|
||||
func (p *Package) processCgo(filename string, f *ast.File, cflags []string) error {
|
||||
info := &fileInfo{
|
||||
File: f,
|
||||
filename: filename,
|
||||
File: f,
|
||||
filename: filename,
|
||||
functions: map[string]*functionInfo{},
|
||||
globals: map[string]*globalInfo{},
|
||||
typedefs: map[string]*typedefInfo{},
|
||||
}
|
||||
|
||||
// Find `import "C"` statements in the file.
|
||||
@@ -122,6 +130,12 @@ func (p *Package) processCgo(filename string, f *ast.File, cflags []string) erro
|
||||
// Declare functions found by libclang.
|
||||
info.addFuncDecls()
|
||||
|
||||
// Declare stub function pointer values found by libclang.
|
||||
info.addFuncPtrDecls()
|
||||
|
||||
// Declare globals found by libclang.
|
||||
info.addVarDecls()
|
||||
|
||||
// Forward C types to Go types (like C.uint32_t -> uint32).
|
||||
info.addTypeAliases()
|
||||
|
||||
@@ -129,7 +143,7 @@ func (p *Package) processCgo(filename string, f *ast.File, cflags []string) erro
|
||||
info.addTypedefs()
|
||||
|
||||
// Patch the AST to use the declared types and functions.
|
||||
ast.Inspect(f, info.walker)
|
||||
f = astutil.Apply(f, info.walker, nil).(*ast.File)
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -139,16 +153,22 @@ func (p *Package) processCgo(filename string, f *ast.File, cflags []string) erro
|
||||
func (info *fileInfo) addFuncDecls() {
|
||||
// TODO: replace all uses of importCPos with the real locations from
|
||||
// libclang.
|
||||
for _, fn := range info.functions {
|
||||
names := make([]string, 0, len(info.functions))
|
||||
for name := range info.functions {
|
||||
names = append(names, name)
|
||||
}
|
||||
sort.Strings(names)
|
||||
for _, name := range names {
|
||||
fn := info.functions[name]
|
||||
obj := &ast.Object{
|
||||
Kind: ast.Fun,
|
||||
Name: mapCgoType(fn.name),
|
||||
Name: "C." + name,
|
||||
}
|
||||
args := make([]*ast.Field, len(fn.args))
|
||||
decl := &ast.FuncDecl{
|
||||
Name: &ast.Ident{
|
||||
NamePos: info.importCPos,
|
||||
Name: mapCgoType(fn.name),
|
||||
Name: "C." + name,
|
||||
Obj: obj,
|
||||
},
|
||||
Type: &ast.FuncType{
|
||||
@@ -158,16 +178,7 @@ func (info *fileInfo) addFuncDecls() {
|
||||
List: args,
|
||||
Closing: info.importCPos,
|
||||
},
|
||||
Results: &ast.FieldList{
|
||||
List: []*ast.Field{
|
||||
&ast.Field{
|
||||
Type: &ast.Ident{
|
||||
NamePos: info.importCPos,
|
||||
Name: mapCgoType(fn.result),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
Results: fn.results,
|
||||
},
|
||||
}
|
||||
obj.Decl = decl
|
||||
@@ -179,21 +190,107 @@ func (info *fileInfo) addFuncDecls() {
|
||||
Name: arg.name,
|
||||
Obj: &ast.Object{
|
||||
Kind: ast.Var,
|
||||
Name: mapCgoType(arg.name),
|
||||
Name: arg.name,
|
||||
Decl: decl,
|
||||
},
|
||||
},
|
||||
},
|
||||
Type: &ast.Ident{
|
||||
NamePos: info.importCPos,
|
||||
Name: mapCgoType(arg.typeName),
|
||||
},
|
||||
Type: arg.typeExpr,
|
||||
}
|
||||
}
|
||||
info.Decls = append(info.Decls, decl)
|
||||
}
|
||||
}
|
||||
|
||||
// addFuncPtrDecls creates stub declarations of function pointer values. These
|
||||
// values will later be replaced with the real values in the compiler.
|
||||
// It adds code like the following to the AST:
|
||||
//
|
||||
// var (
|
||||
// C.add unsafe.Pointer
|
||||
// C.mul unsafe.Pointer
|
||||
// // ...
|
||||
// )
|
||||
func (info *fileInfo) addFuncPtrDecls() {
|
||||
gen := &ast.GenDecl{
|
||||
TokPos: info.importCPos,
|
||||
Tok: token.VAR,
|
||||
Lparen: info.importCPos,
|
||||
Rparen: info.importCPos,
|
||||
}
|
||||
names := make([]string, 0, len(info.functions))
|
||||
for name := range info.functions {
|
||||
names = append(names, name)
|
||||
}
|
||||
sort.Strings(names)
|
||||
for _, name := range names {
|
||||
obj := &ast.Object{
|
||||
Kind: ast.Typ,
|
||||
Name: "C." + name + "$funcaddr",
|
||||
}
|
||||
valueSpec := &ast.ValueSpec{
|
||||
Names: []*ast.Ident{&ast.Ident{
|
||||
NamePos: info.importCPos,
|
||||
Name: "C." + name + "$funcaddr",
|
||||
Obj: obj,
|
||||
}},
|
||||
Type: &ast.SelectorExpr{
|
||||
X: &ast.Ident{
|
||||
NamePos: info.importCPos,
|
||||
Name: "unsafe",
|
||||
},
|
||||
Sel: &ast.Ident{
|
||||
NamePos: info.importCPos,
|
||||
Name: "Pointer",
|
||||
},
|
||||
},
|
||||
}
|
||||
obj.Decl = valueSpec
|
||||
gen.Specs = append(gen.Specs, valueSpec)
|
||||
}
|
||||
info.Decls = append(info.Decls, gen)
|
||||
}
|
||||
|
||||
// addVarDecls declares external C globals in the Go source.
|
||||
// It adds code like the following to the AST:
|
||||
//
|
||||
// var (
|
||||
// C.globalInt int
|
||||
// C.globalBool bool
|
||||
// // ...
|
||||
// )
|
||||
func (info *fileInfo) addVarDecls() {
|
||||
gen := &ast.GenDecl{
|
||||
TokPos: info.importCPos,
|
||||
Tok: token.VAR,
|
||||
Lparen: info.importCPos,
|
||||
Rparen: info.importCPos,
|
||||
}
|
||||
names := make([]string, 0, len(info.globals))
|
||||
for name := range info.globals {
|
||||
names = append(names, name)
|
||||
}
|
||||
sort.Strings(names)
|
||||
for _, name := range names {
|
||||
global := info.globals[name]
|
||||
obj := &ast.Object{
|
||||
Kind: ast.Typ,
|
||||
Name: "C." + name,
|
||||
}
|
||||
valueSpec := &ast.ValueSpec{
|
||||
Names: []*ast.Ident{&ast.Ident{
|
||||
NamePos: info.importCPos,
|
||||
Name: "C." + name,
|
||||
Obj: obj,
|
||||
}},
|
||||
Type: global.typeExpr,
|
||||
}
|
||||
obj.Decl = valueSpec
|
||||
gen.Specs = append(gen.Specs, valueSpec)
|
||||
}
|
||||
info.Decls = append(info.Decls, gen)
|
||||
}
|
||||
|
||||
// addTypeAliases aliases some built-in Go types with their equivalent C types.
|
||||
// It adds code like the following to the AST:
|
||||
//
|
||||
@@ -243,55 +340,32 @@ func (info *fileInfo) addTypedefs() {
|
||||
TokPos: info.importCPos,
|
||||
Tok: token.TYPE,
|
||||
}
|
||||
for _, typedef := range info.typedefs {
|
||||
newType := mapCgoType(typedef.newName)
|
||||
oldType := mapCgoType(typedef.oldName)
|
||||
switch oldType {
|
||||
// TODO: plain char (may be signed or unsigned)
|
||||
case "C.schar", "C.short", "C.int", "C.long", "C.longlong":
|
||||
switch typedef.size {
|
||||
case 1:
|
||||
oldType = "int8"
|
||||
case 2:
|
||||
oldType = "int16"
|
||||
case 4:
|
||||
oldType = "int32"
|
||||
case 8:
|
||||
oldType = "int64"
|
||||
}
|
||||
case "C.uchar", "C.ushort", "C.uint", "C.ulong", "C.ulonglong":
|
||||
switch typedef.size {
|
||||
case 1:
|
||||
oldType = "uint8"
|
||||
case 2:
|
||||
oldType = "uint16"
|
||||
case 4:
|
||||
oldType = "uint32"
|
||||
case 8:
|
||||
oldType = "uint64"
|
||||
}
|
||||
names := make([]string, 0, len(info.typedefs))
|
||||
for name := range info.typedefs {
|
||||
names = append(names, name)
|
||||
}
|
||||
sort.Strings(names)
|
||||
for _, name := range names {
|
||||
typedef := info.typedefs[name]
|
||||
typeName := "C." + name
|
||||
if strings.HasPrefix(name, "_Cgo_") {
|
||||
typeName = "C." + name[len("_Cgo_"):]
|
||||
}
|
||||
if strings.HasPrefix(newType, "C._Cgo_") {
|
||||
newType = "C." + newType[len("C._Cgo_"):]
|
||||
}
|
||||
if _, ok := cgoAliases[newType]; ok {
|
||||
if _, ok := cgoAliases[typeName]; ok {
|
||||
// This is a type that also exists in Go (defined in stdint.h).
|
||||
continue
|
||||
}
|
||||
obj := &ast.Object{
|
||||
Kind: ast.Typ,
|
||||
Name: newType,
|
||||
Name: typeName,
|
||||
}
|
||||
typeSpec := &ast.TypeSpec{
|
||||
Name: &ast.Ident{
|
||||
NamePos: info.importCPos,
|
||||
Name: newType,
|
||||
Name: typeName,
|
||||
Obj: obj,
|
||||
},
|
||||
Type: &ast.Ident{
|
||||
NamePos: info.importCPos,
|
||||
Name: oldType,
|
||||
},
|
||||
Type: typedef.typeExpr,
|
||||
}
|
||||
obj.Decl = typeSpec
|
||||
gen.Specs = append(gen.Specs, typeSpec)
|
||||
@@ -299,12 +373,12 @@ func (info *fileInfo) addTypedefs() {
|
||||
info.Decls = append(info.Decls, gen)
|
||||
}
|
||||
|
||||
// walker replaces all "C".<something> call expressions to literal
|
||||
// "C.<something>" expressions. This is impossible to write in Go (a dot cannot
|
||||
// be used in the middle of a name) so is used as a new namespace for C call
|
||||
// expressions.
|
||||
func (info *fileInfo) walker(node ast.Node) bool {
|
||||
switch node := node.(type) {
|
||||
// walker replaces all "C".<something> expressions to literal "C.<something>"
|
||||
// expressions. Such expressions are impossible to write in Go (a dot cannot be
|
||||
// used in the middle of a name) so in practice all C identifiers live in a
|
||||
// separate namespace (no _Cgo_ hacks like in gc).
|
||||
func (info *fileInfo) walker(cursor *astutil.Cursor) bool {
|
||||
switch node := cursor.Node().(type) {
|
||||
case *ast.CallExpr:
|
||||
fun, ok := node.Fun.(*ast.SelectorExpr)
|
||||
if !ok {
|
||||
@@ -314,49 +388,27 @@ func (info *fileInfo) walker(node ast.Node) bool {
|
||||
if !ok {
|
||||
return true
|
||||
}
|
||||
if x.Name == "C" {
|
||||
if _, ok := info.functions[fun.Sel.Name]; ok && x.Name == "C" {
|
||||
node.Fun = &ast.Ident{
|
||||
NamePos: x.NamePos,
|
||||
Name: mapCgoType(fun.Sel.Name),
|
||||
Name: "C." + fun.Sel.Name,
|
||||
}
|
||||
}
|
||||
case *ast.ValueSpec:
|
||||
typ, ok := node.Type.(*ast.SelectorExpr)
|
||||
if !ok {
|
||||
return true
|
||||
}
|
||||
x, ok := typ.X.(*ast.Ident)
|
||||
case *ast.SelectorExpr:
|
||||
x, ok := node.X.(*ast.Ident)
|
||||
if !ok {
|
||||
return true
|
||||
}
|
||||
if x.Name == "C" {
|
||||
node.Type = &ast.Ident{
|
||||
NamePos: x.NamePos,
|
||||
Name: mapCgoType(typ.Sel.Name),
|
||||
name := "C." + node.Sel.Name
|
||||
if _, ok := info.functions[node.Sel.Name]; ok {
|
||||
name += "$funcaddr"
|
||||
}
|
||||
cursor.Replace(&ast.Ident{
|
||||
NamePos: x.NamePos,
|
||||
Name: name,
|
||||
})
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// mapCgoType converts a C type name into a Go type name with a "C." prefix.
|
||||
func mapCgoType(t string) string {
|
||||
switch t {
|
||||
case "signed char":
|
||||
return "C.schar"
|
||||
case "long long":
|
||||
return "C.longlong"
|
||||
case "unsigned char":
|
||||
return "C.schar"
|
||||
case "unsigned short":
|
||||
return "C.ushort"
|
||||
case "unsigned int":
|
||||
return "C.uint"
|
||||
case "unsigned long":
|
||||
return "C.ulong"
|
||||
case "unsigned long long":
|
||||
return "C.ulonglong"
|
||||
default:
|
||||
return "C." + t
|
||||
}
|
||||
}
|
||||
|
||||
+17
-5
@@ -1,5 +1,10 @@
|
||||
package loader
|
||||
|
||||
import (
|
||||
"go/token"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Errors contains a list of parser errors or a list of typechecker errors for
|
||||
// the given package.
|
||||
type Errors struct {
|
||||
@@ -15,13 +20,20 @@ func (e Errors) Error() string {
|
||||
// packages is a list from the root package to the leaf package that imports one
|
||||
// of the packages in the list.
|
||||
type ImportCycleError struct {
|
||||
Packages []string
|
||||
Packages []string
|
||||
ImportPositions []token.Position
|
||||
}
|
||||
|
||||
func (e *ImportCycleError) Error() string {
|
||||
msg := "import cycle: " + e.Packages[0]
|
||||
for _, path := range e.Packages[1:] {
|
||||
msg += " → " + path
|
||||
var msg strings.Builder
|
||||
msg.WriteString("import cycle:\n\t")
|
||||
msg.WriteString(strings.Join(e.Packages, "\n\t"))
|
||||
msg.WriteString("\n at ")
|
||||
for i, pos := range e.ImportPositions {
|
||||
if i > 0 {
|
||||
msg.WriteString(", ")
|
||||
}
|
||||
msg.WriteString(pos.String())
|
||||
}
|
||||
return msg
|
||||
return msg.String()
|
||||
}
|
||||
|
||||
+122
-18
@@ -5,12 +5,14 @@ package loader
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"go/ast"
|
||||
"go/token"
|
||||
"strconv"
|
||||
"strings"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
/*
|
||||
#cgo CFLAGS: -I/usr/lib/llvm-7/include
|
||||
#cgo LDFLAGS: -L/usr/lib/llvm-7/lib -lclang
|
||||
#include <clang-c/Index.h> // if this fails, install libclang-7-dev
|
||||
#include <stdlib.h>
|
||||
|
||||
@@ -39,7 +41,7 @@ func (info *fileInfo) parseFragment(fragment string, cflags []string) error {
|
||||
// convert Go slice of strings to C array of strings.
|
||||
cmdargsC := C.malloc(C.size_t(len(cflags)) * C.size_t(unsafe.Sizeof(uintptr(0))))
|
||||
defer C.free(cmdargsC)
|
||||
cmdargs := (*[1<<30 - 1]*C.char)(cmdargsC)
|
||||
cmdargs := (*[1 << 16]*C.char)(cmdargsC)
|
||||
for i, cflag := range cflags {
|
||||
s := C.CString(cflag)
|
||||
cmdargs[i] = s
|
||||
@@ -90,30 +92,74 @@ func tinygo_clang_visitor(c, parent C.CXCursor, client_data C.CXClientData) C.in
|
||||
if C.clang_isFunctionTypeVariadic(cursorType) != 0 {
|
||||
return C.CXChildVisit_Continue // not supported
|
||||
}
|
||||
numArgs := C.clang_Cursor_getNumArguments(c)
|
||||
fn := &functionInfo{name: name}
|
||||
info.functions = append(info.functions, fn)
|
||||
for i := C.int(0); i < numArgs; i++ {
|
||||
numArgs := int(C.clang_Cursor_getNumArguments(c))
|
||||
fn := &functionInfo{}
|
||||
info.functions[name] = fn
|
||||
for i := 0; i < numArgs; i++ {
|
||||
arg := C.clang_Cursor_getArgument(c, C.uint(i))
|
||||
argName := getString(C.clang_getCursorSpelling(arg))
|
||||
argType := C.clang_getArgType(cursorType, C.uint(i))
|
||||
argTypeName := getString(C.clang_getTypeSpelling(argType))
|
||||
fn.args = append(fn.args, paramInfo{argName, argTypeName})
|
||||
if argName == "" {
|
||||
argName = "$" + strconv.Itoa(i)
|
||||
}
|
||||
fn.args = append(fn.args, paramInfo{
|
||||
name: argName,
|
||||
typeExpr: info.makeASTType(argType),
|
||||
})
|
||||
}
|
||||
resultType := C.clang_getCursorResultType(c)
|
||||
resultTypeName := getString(C.clang_getTypeSpelling(resultType))
|
||||
fn.result = resultTypeName
|
||||
if resultType.kind != C.CXType_Void {
|
||||
fn.results = &ast.FieldList{
|
||||
List: []*ast.Field{
|
||||
&ast.Field{
|
||||
Type: info.makeASTType(resultType),
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
case C.CXCursor_TypedefDecl:
|
||||
typedefType := C.clang_getCursorType(c)
|
||||
name := getString(C.clang_getTypedefName(typedefType))
|
||||
underlyingType := C.clang_getTypedefDeclUnderlyingType(c)
|
||||
underlyingTypeName := getString(C.clang_getTypeSpelling(underlyingType))
|
||||
typeSize := C.clang_Type_getSizeOf(underlyingType)
|
||||
info.typedefs = append(info.typedefs, &typedefInfo{
|
||||
newName: name,
|
||||
oldName: underlyingTypeName,
|
||||
size: int(typeSize),
|
||||
})
|
||||
expr := info.makeASTType(underlyingType)
|
||||
if strings.HasPrefix(name, "_Cgo_") {
|
||||
expr := expr.(*ast.Ident)
|
||||
typeSize := C.clang_Type_getSizeOf(underlyingType)
|
||||
switch expr.Name {
|
||||
// TODO: plain char (may be signed or unsigned)
|
||||
case "C.schar", "C.short", "C.int", "C.long", "C.longlong":
|
||||
switch typeSize {
|
||||
case 1:
|
||||
expr.Name = "int8"
|
||||
case 2:
|
||||
expr.Name = "int16"
|
||||
case 4:
|
||||
expr.Name = "int32"
|
||||
case 8:
|
||||
expr.Name = "int64"
|
||||
}
|
||||
case "C.uchar", "C.ushort", "C.uint", "C.ulong", "C.ulonglong":
|
||||
switch typeSize {
|
||||
case 1:
|
||||
expr.Name = "uint8"
|
||||
case 2:
|
||||
expr.Name = "uint16"
|
||||
case 4:
|
||||
expr.Name = "uint32"
|
||||
case 8:
|
||||
expr.Name = "uint64"
|
||||
}
|
||||
}
|
||||
}
|
||||
info.typedefs[name] = &typedefInfo{
|
||||
typeExpr: expr,
|
||||
}
|
||||
case C.CXCursor_VarDecl:
|
||||
name := getString(C.clang_getCursorSpelling(c))
|
||||
cursorType := C.clang_getCursorType(c)
|
||||
info.globals[name] = &globalInfo{
|
||||
typeExpr: info.makeASTType(cursorType),
|
||||
}
|
||||
}
|
||||
return C.CXChildVisit_Continue
|
||||
}
|
||||
@@ -124,3 +170,61 @@ func getString(clangString C.CXString) (s string) {
|
||||
C.clang_disposeString(clangString)
|
||||
return
|
||||
}
|
||||
|
||||
// makeASTType return the ast.Expr for the given libclang type. In other words,
|
||||
// it converts a libclang type to a type in the Go AST.
|
||||
func (info *fileInfo) makeASTType(typ C.CXType) ast.Expr {
|
||||
var typeName string
|
||||
switch typ.kind {
|
||||
case C.CXType_SChar:
|
||||
typeName = "schar"
|
||||
case C.CXType_UChar:
|
||||
typeName = "uchar"
|
||||
case C.CXType_Short:
|
||||
typeName = "short"
|
||||
case C.CXType_UShort:
|
||||
typeName = "ushort"
|
||||
case C.CXType_Int:
|
||||
typeName = "int"
|
||||
case C.CXType_UInt:
|
||||
typeName = "uint"
|
||||
case C.CXType_Long:
|
||||
typeName = "long"
|
||||
case C.CXType_ULong:
|
||||
typeName = "ulong"
|
||||
case C.CXType_LongLong:
|
||||
typeName = "longlong"
|
||||
case C.CXType_ULongLong:
|
||||
typeName = "ulonglong"
|
||||
case C.CXType_Pointer:
|
||||
return &ast.StarExpr{
|
||||
Star: info.importCPos,
|
||||
X: info.makeASTType(C.clang_getPointeeType(typ)),
|
||||
}
|
||||
case C.CXType_FunctionProto:
|
||||
// Be compatible with gc, which uses the *[0]byte type for function
|
||||
// pointer types.
|
||||
// Return type [0]byte because this is a function type, not a pointer to
|
||||
// this function type.
|
||||
return &ast.ArrayType{
|
||||
Lbrack: info.importCPos,
|
||||
Len: &ast.BasicLit{
|
||||
ValuePos: info.importCPos,
|
||||
Kind: token.INT,
|
||||
Value: "0",
|
||||
},
|
||||
Elt: &ast.Ident{
|
||||
NamePos: info.importCPos,
|
||||
Name: "byte",
|
||||
},
|
||||
}
|
||||
default:
|
||||
// Fallback, probably incorrect but at least the error points to an odd
|
||||
// type name.
|
||||
typeName = getString(C.clang_getTypeSpelling(typ))
|
||||
}
|
||||
return &ast.Ident{
|
||||
NamePos: info.importCPos,
|
||||
Name: "C." + typeName,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
// +build !byollvm
|
||||
|
||||
package loader
|
||||
|
||||
/*
|
||||
#cgo CFLAGS: -I/usr/lib/llvm-7/include
|
||||
#cgo LDFLAGS: -L/usr/lib/llvm-7/lib -lclang
|
||||
*/
|
||||
import "C"
|
||||
+7
-2
@@ -166,7 +166,9 @@ func (p *Program) Parse() error {
|
||||
err := pkg.importRecursively()
|
||||
if err != nil {
|
||||
if err, ok := err.(*ImportCycleError); ok {
|
||||
err.Packages = append([]string{pkg.ImportPath}, err.Packages...)
|
||||
if pkg.ImportPath != err.Packages[0] {
|
||||
err.Packages = append([]string{pkg.ImportPath}, err.Packages...)
|
||||
}
|
||||
}
|
||||
return err
|
||||
}
|
||||
@@ -339,10 +341,13 @@ func (p *Package) importRecursively() error {
|
||||
return err
|
||||
}
|
||||
if importedPkg.Importing {
|
||||
return &ImportCycleError{[]string{p.ImportPath, importedPkg.ImportPath}}
|
||||
return &ImportCycleError{[]string{p.ImportPath, importedPkg.ImportPath}, p.ImportPos[to]}
|
||||
}
|
||||
err = importedPkg.importRecursively()
|
||||
if err != nil {
|
||||
if err, ok := err.(*ImportCycleError); ok {
|
||||
err.Packages = append([]string{p.ImportPath}, err.Packages...)
|
||||
}
|
||||
return err
|
||||
}
|
||||
p.Imports[to] = importedPkg
|
||||
|
||||
@@ -11,18 +11,33 @@ import (
|
||||
"os/exec"
|
||||
"os/signal"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strconv"
|
||||
"strings"
|
||||
"syscall"
|
||||
|
||||
"github.com/aykevl/tinygo/compiler"
|
||||
"github.com/aykevl/tinygo/interp"
|
||||
"github.com/aykevl/tinygo/loader"
|
||||
"github.com/tinygo-org/tinygo/compiler"
|
||||
"github.com/tinygo-org/tinygo/interp"
|
||||
"github.com/tinygo-org/tinygo/loader"
|
||||
)
|
||||
|
||||
var commands = map[string]string{
|
||||
"ar": "ar",
|
||||
"clang": "clang-7",
|
||||
"ar": "ar",
|
||||
"clang": "clang-7",
|
||||
"ld.lld": "ld.lld-7",
|
||||
"wasm-ld": "wasm-ld-7",
|
||||
}
|
||||
|
||||
// commandError is an error type to wrap os/exec.Command errors. This provides
|
||||
// some more information regarding what went wrong while running a command.
|
||||
type commandError struct {
|
||||
Msg string
|
||||
File string
|
||||
Err error
|
||||
}
|
||||
|
||||
func (e *commandError) Error() string {
|
||||
return e.Msg + " " + e.File + ": " + e.Err.Error()
|
||||
}
|
||||
|
||||
type BuildConfig struct {
|
||||
@@ -174,13 +189,12 @@ func Compile(pkgName, outpath string, spec *TargetSpec, config *BuildConfig, act
|
||||
|
||||
// Load builtins library from the cache, possibly compiling it on the
|
||||
// fly.
|
||||
var cachePath string
|
||||
var librt string
|
||||
if spec.RTLib == "compiler-rt" {
|
||||
librt, err := loadBuiltins(spec.Triple)
|
||||
librt, err = loadBuiltins(spec.Triple)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
cachePath, _ = filepath.Split(librt)
|
||||
}
|
||||
|
||||
// Prepare link command.
|
||||
@@ -188,7 +202,7 @@ func Compile(pkgName, outpath string, spec *TargetSpec, config *BuildConfig, act
|
||||
tmppath := executable // final file
|
||||
ldflags := append(spec.LDFlags, "-o", executable, objfile)
|
||||
if spec.RTLib == "compiler-rt" {
|
||||
ldflags = append(ldflags, "-L", cachePath, "-lrt-"+spec.Triple)
|
||||
ldflags = append(ldflags, librt)
|
||||
}
|
||||
|
||||
// Compile extra files.
|
||||
@@ -200,7 +214,7 @@ func Compile(pkgName, outpath string, spec *TargetSpec, config *BuildConfig, act
|
||||
cmd.Dir = sourceDir()
|
||||
err := cmd.Run()
|
||||
if err != nil {
|
||||
return err
|
||||
return &commandError{"failed to build", path, err}
|
||||
}
|
||||
ldflags = append(ldflags, outpath)
|
||||
}
|
||||
@@ -216,20 +230,16 @@ func Compile(pkgName, outpath string, spec *TargetSpec, config *BuildConfig, act
|
||||
cmd.Dir = sourceDir()
|
||||
err := cmd.Run()
|
||||
if err != nil {
|
||||
return err
|
||||
return &commandError{"failed to build", path, err}
|
||||
}
|
||||
ldflags = append(ldflags, outpath)
|
||||
}
|
||||
}
|
||||
|
||||
// Link the object files together.
|
||||
cmd := exec.Command(spec.Linker, ldflags...)
|
||||
cmd.Stdout = os.Stdout
|
||||
cmd.Stderr = os.Stderr
|
||||
cmd.Dir = sourceDir()
|
||||
err = cmd.Run()
|
||||
err = Link(sourceDir(), spec.Linker, ldflags...)
|
||||
if err != nil {
|
||||
return err
|
||||
return &commandError{"failed to link", executable, err}
|
||||
}
|
||||
|
||||
if config.printSizes == "short" || config.printSizes == "full" {
|
||||
@@ -263,7 +273,7 @@ func Compile(pkgName, outpath string, spec *TargetSpec, config *BuildConfig, act
|
||||
cmd.Stderr = os.Stderr
|
||||
err = cmd.Run()
|
||||
if err != nil {
|
||||
return err
|
||||
return &commandError{"failed to extract " + format + " from", executable, err}
|
||||
}
|
||||
}
|
||||
return action(tmppath)
|
||||
@@ -325,7 +335,11 @@ func Flash(pkgName, target, port string, config *BuildConfig) error {
|
||||
cmd.Stdout = os.Stdout
|
||||
cmd.Stderr = os.Stderr
|
||||
cmd.Dir = sourceDir()
|
||||
return cmd.Run()
|
||||
err := cmd.Run()
|
||||
if err != nil {
|
||||
return &commandError{"failed to flash", tmppath, err}
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
@@ -392,7 +406,11 @@ func FlashGDB(pkgName, target, port string, ocdOutput bool, config *BuildConfig)
|
||||
cmd.Stdin = os.Stdin
|
||||
cmd.Stdout = os.Stdout
|
||||
cmd.Stderr = os.Stderr
|
||||
return cmd.Run()
|
||||
err := cmd.Run()
|
||||
if err != nil {
|
||||
return &commandError{"failed to run gdb with", tmppath, err}
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
@@ -415,8 +433,9 @@ func Run(pkgName, target string, config *BuildConfig) error {
|
||||
// Workaround for QEMU which always exits with an error.
|
||||
return nil
|
||||
}
|
||||
return &commandError{"failed to run compiled binary", tmppath, err}
|
||||
}
|
||||
return err
|
||||
return nil
|
||||
} else {
|
||||
// Run in an emulator.
|
||||
args := append(spec.Emulator[1:], tmppath)
|
||||
@@ -429,13 +448,16 @@ func Run(pkgName, target string, config *BuildConfig) error {
|
||||
// Workaround for QEMU which always exits with an error.
|
||||
return nil
|
||||
}
|
||||
return &commandError{"failed to run emulator with", tmppath, err}
|
||||
}
|
||||
return err
|
||||
return nil
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func usage() {
|
||||
fmt.Fprintln(os.Stderr, "TinyGo is a Go compiler for small places.")
|
||||
fmt.Fprintln(os.Stderr, "version:", version)
|
||||
fmt.Fprintf(os.Stderr, "usage: %s command [-printir] [-target=<target>] -o <output> <input>\n", os.Args[0])
|
||||
fmt.Fprintln(os.Stderr, "\ncommands:")
|
||||
fmt.Fprintln(os.Stderr, " build: compile packages and dependencies")
|
||||
@@ -532,6 +554,20 @@ func main() {
|
||||
}
|
||||
err := Build(flag.Arg(0), *outpath, target, config)
|
||||
handleCompilerError(err)
|
||||
case "build-builtins":
|
||||
// Note: this command is only meant to be used while making a release!
|
||||
if *outpath == "" {
|
||||
fmt.Fprintln(os.Stderr, "No output filename supplied (-o).")
|
||||
usage()
|
||||
os.Exit(1)
|
||||
}
|
||||
if *target == "" {
|
||||
fmt.Fprintln(os.Stderr, "No target (-target).")
|
||||
}
|
||||
err := compileBuiltins(*target, func(path string) error {
|
||||
return moveFile(path, *outpath)
|
||||
})
|
||||
handleCompilerError(err)
|
||||
case "flash", "gdb":
|
||||
if *outpath != "" {
|
||||
fmt.Fprintln(os.Stderr, "Output cannot be specified with the flash command.")
|
||||
@@ -568,6 +604,8 @@ func main() {
|
||||
}
|
||||
case "help":
|
||||
usage()
|
||||
case "version":
|
||||
fmt.Printf("tinygo version %s %s/%s\n", version, runtime.GOOS, runtime.GOARCH)
|
||||
default:
|
||||
fmt.Fprintln(os.Stderr, "Unknown command:", command)
|
||||
usage()
|
||||
|
||||
+29
-2
@@ -23,6 +23,9 @@ func TestCompiler(t *testing.T) {
|
||||
}
|
||||
|
||||
dirMatches, err := filepath.Glob(TESTDATA + "/*/main.go")
|
||||
if err != nil {
|
||||
t.Fatal("could not read test packages:", err)
|
||||
}
|
||||
if len(matches) == 0 || len(dirMatches) == 0 {
|
||||
t.Fatal("no test files found")
|
||||
}
|
||||
@@ -39,14 +42,38 @@ func TestCompiler(t *testing.T) {
|
||||
}
|
||||
defer os.RemoveAll(tmpdir)
|
||||
|
||||
t.Log("running tests on the host...")
|
||||
t.Log("running tests on host...")
|
||||
for _, path := range matches {
|
||||
t.Run(path, func(t *testing.T) {
|
||||
runTest(path, tmpdir, "", t)
|
||||
})
|
||||
}
|
||||
|
||||
t.Log("running tests on the qemu target...")
|
||||
if testing.Short() {
|
||||
return
|
||||
}
|
||||
|
||||
t.Log("running tests for linux/arm...")
|
||||
for _, path := range matches {
|
||||
if path == "testdata/cgo/" {
|
||||
continue // TODO: improve CGo
|
||||
}
|
||||
t.Run(path, func(t *testing.T) {
|
||||
runTest(path, tmpdir, "arm--linux-gnueabi", t)
|
||||
})
|
||||
}
|
||||
|
||||
t.Log("running tests for linux/arm64...")
|
||||
for _, path := range matches {
|
||||
if path == "testdata/cgo/" {
|
||||
continue // TODO: improve CGo
|
||||
}
|
||||
t.Run(path, func(t *testing.T) {
|
||||
runTest(path, tmpdir, "aarch64--linux-gnueabi", t)
|
||||
})
|
||||
}
|
||||
|
||||
t.Log("running tests for emulated cortex-m3...")
|
||||
for _, path := range matches {
|
||||
t.Run(path, func(t *testing.T) {
|
||||
runTest(path, tmpdir, "qemu", t)
|
||||
|
||||
@@ -7,27 +7,34 @@ import (
|
||||
"time"
|
||||
)
|
||||
|
||||
// change these to test a different UART or pins if available
|
||||
var (
|
||||
uart = machine.UART0
|
||||
tx uint8 = machine.UART_TX_PIN
|
||||
rx uint8 = machine.UART_RX_PIN
|
||||
)
|
||||
|
||||
func main() {
|
||||
machine.UART0.Configure(machine.UARTConfig{})
|
||||
machine.UART0.Write([]byte("Echo console enabled. Type something then press enter:\r\n"))
|
||||
uart.Configure(machine.UARTConfig{TX: tx, RX: rx})
|
||||
uart.Write([]byte("Echo console enabled. Type something then press enter:\r\n"))
|
||||
|
||||
input := make([]byte, 64)
|
||||
i := 0
|
||||
for {
|
||||
if machine.UART0.Buffered() > 0 {
|
||||
data, _ := machine.UART0.ReadByte()
|
||||
if uart.Buffered() > 0 {
|
||||
data, _ := uart.ReadByte()
|
||||
|
||||
switch data {
|
||||
case 13:
|
||||
// return key
|
||||
machine.UART0.Write([]byte("\r\n"))
|
||||
machine.UART0.Write([]byte("You typed: "))
|
||||
machine.UART0.Write(input[:i])
|
||||
machine.UART0.Write([]byte("\r\n"))
|
||||
uart.Write([]byte("\r\n"))
|
||||
uart.Write([]byte("You typed: "))
|
||||
uart.Write(input[:i])
|
||||
uart.Write([]byte("\r\n"))
|
||||
i = 0
|
||||
default:
|
||||
// just echo the character
|
||||
machine.UART0.WriteByte(data)
|
||||
uart.WriteByte(data)
|
||||
input[i] = data
|
||||
i++
|
||||
}
|
||||
|
||||
@@ -16,3 +16,9 @@ const (
|
||||
ADC4 = 4 // Used by TWI for SDA
|
||||
ADC5 = 5 // Used by TWI for SCL
|
||||
)
|
||||
|
||||
// UART pins
|
||||
const (
|
||||
UART_TX_PIN = 1
|
||||
UART_RX_PIN = 0
|
||||
)
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
// +build sam,atsamd21g18a,itsybitsy_m0
|
||||
|
||||
package machine
|
||||
|
||||
// GPIO Pins
|
||||
const (
|
||||
D0 = 11 // UART0 RX
|
||||
D1 = 10 // UART0 TX
|
||||
D2 = 14
|
||||
D3 = 9 // PWM available
|
||||
D4 = 8 // PWM available
|
||||
D5 = 15 // PWM available
|
||||
D6 = 20 // PWM available
|
||||
D7 = 21 // PWM available
|
||||
D8 = 6 // PWM available
|
||||
D9 = 7 // PWM available
|
||||
D10 = 18 // can be used for PWM or UART1 TX
|
||||
D11 = 16 // can be used for PWM or UART1 RX
|
||||
D12 = 19 // PWM available
|
||||
D13 = 17 // PWM available
|
||||
)
|
||||
|
||||
// Analog pins
|
||||
const (
|
||||
A0 = 2 // ADC/AIN[0]
|
||||
// A1 = 8 // ADC/AIN[2] TODO: requires PORTB
|
||||
// A2 = 9 // ADC/AIN[3] TODO: requires PORTB
|
||||
A3 = 4 // ADC/AIN[4]
|
||||
A4 = 5 // ADC/AIN[5]
|
||||
//A5 = 2 // ADC/AIN[10] TODO: requires PORTB
|
||||
)
|
||||
|
||||
const (
|
||||
LED = D13
|
||||
)
|
||||
|
||||
// UART0 pins
|
||||
const (
|
||||
UART_TX_PIN = D1
|
||||
UART_RX_PIN = D0
|
||||
)
|
||||
|
||||
// I2C pins
|
||||
const (
|
||||
SDA_PIN = 22 // SDA: SERCOM3/PAD[0]
|
||||
SCL_PIN = 23 // SCL: SERCOM3/PAD[1]
|
||||
)
|
||||
@@ -0,0 +1,49 @@
|
||||
package machine
|
||||
|
||||
const bufferSize = 128
|
||||
|
||||
//go:volatile
|
||||
type volatileByte byte
|
||||
|
||||
// RingBuffer is ring buffer implementation inspired by post at
|
||||
// https://www.embeddedrelated.com/showthread/comp.arch.embedded/77084-1.php
|
||||
//
|
||||
// It has some limitations currently due to how "volatile" variables that are
|
||||
// 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
|
||||
}
|
||||
|
||||
// NewRingBuffer returns a new ring buffer.
|
||||
func NewRingBuffer() *RingBuffer {
|
||||
return &RingBuffer{}
|
||||
}
|
||||
|
||||
// Used returns how many bytes in buffer have been used.
|
||||
func (rb *RingBuffer) Used() uint8 {
|
||||
return uint8(rb.head - rb.tail)
|
||||
}
|
||||
|
||||
// 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)
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// Get returns a byte from the buffer. If the buffer is empty,
|
||||
// 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
|
||||
}
|
||||
return 0, false
|
||||
}
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
// +build avr nrf stm32f103xx
|
||||
// +build avr nrf sam stm32f103xx
|
||||
|
||||
package machine
|
||||
|
||||
|
||||
@@ -211,6 +211,11 @@ func (i2c I2C) readByte() byte {
|
||||
return byte(*avr.TWDR)
|
||||
}
|
||||
|
||||
// UART on the AVR.
|
||||
type UART struct {
|
||||
Buffer *RingBuffer
|
||||
}
|
||||
|
||||
// Configure the UART on the AVR. Defaults to 9600 baud on Arduino.
|
||||
func (uart UART) Configure(config UARTConfig) {
|
||||
if config.BaudRate == 0 {
|
||||
@@ -248,6 +253,6 @@ func handleUSART_RX() {
|
||||
// Ensure no error.
|
||||
if (*avr.UCSR0A & (avr.UCSR0A_FE0 | avr.UCSR0A_DOR0 | avr.UCSR0A_UPE0)) == 0 {
|
||||
// Put data from UDR register into buffer.
|
||||
bufferPut(byte(data))
|
||||
UART0.Receive(byte(data))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,908 @@
|
||||
// +build sam,atsamd21g18a
|
||||
|
||||
// Peripheral abstraction layer for the atsamd21.
|
||||
//
|
||||
// Datasheet:
|
||||
// http://ww1.microchip.com/downloads/en/DeviceDoc/SAMD21-Family-DataSheet-DS40001882D.pdf
|
||||
//
|
||||
package machine
|
||||
|
||||
import (
|
||||
"device/arm"
|
||||
"device/sam"
|
||||
"errors"
|
||||
)
|
||||
|
||||
const CPU_FREQUENCY = 48000000
|
||||
|
||||
type GPIOMode uint8
|
||||
|
||||
const (
|
||||
GPIO_ANALOG = 1
|
||||
GPIO_SERCOM = 2
|
||||
GPIO_SERCOM_ALT = 3
|
||||
GPIO_TIMER = 4
|
||||
GPIO_TIMER_ALT = 5
|
||||
GPIO_COM = 6
|
||||
GPIO_AC_CLK = 7
|
||||
GPIO_DIGITAL = 8
|
||||
GPIO_INPUT = 9
|
||||
GPIO_INPUT_PULLUP = 10
|
||||
GPIO_OUTPUT = 11
|
||||
GPIO_PWM = GPIO_TIMER
|
||||
GPIO_PWM_ALT = GPIO_TIMER_ALT
|
||||
)
|
||||
|
||||
// Configure this pin with the given configuration.
|
||||
func (p GPIO) Configure(config GPIOConfig) {
|
||||
switch config.Mode {
|
||||
case GPIO_OUTPUT:
|
||||
sam.PORT.DIRSET0 = (1 << p.Pin)
|
||||
// output is also set to input enable so pin can read back its own value
|
||||
p.setPinCfg(sam.PORT_PINCFG0_INEN)
|
||||
|
||||
case GPIO_INPUT:
|
||||
sam.PORT.DIRCLR0 = (1 << p.Pin)
|
||||
p.setPinCfg(sam.PORT_PINCFG0_INEN)
|
||||
|
||||
case GPIO_SERCOM:
|
||||
if p.Pin&1 > 0 {
|
||||
// odd pin, so save the even pins
|
||||
val := p.getPMux() & sam.PORT_PMUX0_PMUXE_Msk
|
||||
p.setPMux(val | (GPIO_SERCOM << sam.PORT_PMUX0_PMUXO_Pos))
|
||||
} else {
|
||||
// even pin, so save the odd pins
|
||||
val := p.getPMux() & sam.PORT_PMUX0_PMUXO_Msk
|
||||
p.setPMux(val | (GPIO_SERCOM << sam.PORT_PMUX0_PMUXE_Pos))
|
||||
}
|
||||
// enable port config
|
||||
p.setPinCfg(sam.PORT_PINCFG0_PMUXEN | sam.PORT_PINCFG0_DRVSTR | sam.PORT_PINCFG0_INEN)
|
||||
}
|
||||
}
|
||||
|
||||
// Get returns the current value of a GPIO pin.
|
||||
func (p GPIO) Get() bool {
|
||||
return (sam.PORT.IN0>>p.Pin)&1 > 0
|
||||
}
|
||||
|
||||
// Set the pin to high or low.
|
||||
// Warning: only use this on an output pin!
|
||||
func (p GPIO) Set(high bool) {
|
||||
if high {
|
||||
sam.PORT.OUTSET0 = (1 << p.Pin)
|
||||
} else {
|
||||
sam.PORT.OUTCLR0 = (1 << p.Pin)
|
||||
}
|
||||
}
|
||||
|
||||
// getPMux returns the value for the correct PMUX register for this pin.
|
||||
func (p GPIO) getPMux() sam.RegValue8 {
|
||||
return getPMux(p.Pin)
|
||||
}
|
||||
|
||||
// setPMux sets the value for the correct PMUX register for this pin.
|
||||
func (p GPIO) setPMux(val sam.RegValue8) {
|
||||
setPMux(p.Pin, val)
|
||||
}
|
||||
|
||||
// getPinCfg returns the value for the correct PINCFG register for this pin.
|
||||
func (p GPIO) getPinCfg() sam.RegValue8 {
|
||||
return getPinCfg(p.Pin)
|
||||
}
|
||||
|
||||
// setPinCfg sets the value for the correct PINCFG register for this pin.
|
||||
func (p GPIO) setPinCfg(val sam.RegValue8) {
|
||||
setPinCfg(p.Pin, val)
|
||||
}
|
||||
|
||||
// UART on the SAMD21.
|
||||
type UART struct {
|
||||
Buffer *RingBuffer
|
||||
Bus *sam.SERCOM_USART_Type
|
||||
}
|
||||
|
||||
var (
|
||||
// The first hardware serial port on the SAMD21. Uses the SERCOM0 interface.
|
||||
UART0 = UART{Bus: sam.SERCOM0_USART, Buffer: NewRingBuffer()}
|
||||
|
||||
// The second hardware serial port on the SAMD21. Uses the SERCOM1 interface.
|
||||
UART1 = UART{Bus: sam.SERCOM1_USART, Buffer: NewRingBuffer()}
|
||||
)
|
||||
|
||||
const (
|
||||
sampleRate16X = 16
|
||||
lsbFirst = 1
|
||||
sercomRXPad0 = 0
|
||||
sercomRXPad1 = 1
|
||||
sercomRXPad2 = 2
|
||||
sercomRXPad3 = 3
|
||||
sercomTXPad0 = 0 // Only for UART
|
||||
sercomTXPad2 = 1 // Only for UART
|
||||
sercomTXPad023 = 2 // Only for UART with TX on PAD0, RTS on PAD2 and CTS on PAD3
|
||||
)
|
||||
|
||||
// Configure the UART.
|
||||
func (uart UART) Configure(config UARTConfig) {
|
||||
// Default baud rate to 115200.
|
||||
if config.BaudRate == 0 {
|
||||
config.BaudRate = 115200
|
||||
}
|
||||
|
||||
// determine pins
|
||||
if config.TX == 0 {
|
||||
// use default pins
|
||||
config.TX = UART_TX_PIN
|
||||
config.RX = UART_RX_PIN
|
||||
}
|
||||
|
||||
// determine pads
|
||||
var txpad, rxpad int
|
||||
switch config.TX {
|
||||
case UART_TX_PIN:
|
||||
txpad = sercomTXPad2
|
||||
case D10:
|
||||
txpad = sercomTXPad2
|
||||
case D11:
|
||||
txpad = sercomTXPad0
|
||||
default:
|
||||
panic("Invalid TX pin for UART")
|
||||
}
|
||||
|
||||
switch config.RX {
|
||||
case UART_RX_PIN:
|
||||
rxpad = sercomRXPad3
|
||||
case D10:
|
||||
rxpad = sercomRXPad2
|
||||
case D11:
|
||||
rxpad = sercomRXPad0
|
||||
case D12:
|
||||
rxpad = sercomRXPad3
|
||||
case D13:
|
||||
rxpad = sercomRXPad1
|
||||
default:
|
||||
panic("Invalid RX pin for UART")
|
||||
}
|
||||
|
||||
// configure pins
|
||||
GPIO{config.TX}.Configure(GPIOConfig{Mode: GPIO_SERCOM})
|
||||
GPIO{config.RX}.Configure(GPIOConfig{Mode: GPIO_SERCOM})
|
||||
|
||||
// reset SERCOM0
|
||||
uart.Bus.CTRLA |= sam.SERCOM_USART_CTRLA_SWRST
|
||||
for (uart.Bus.CTRLA&sam.SERCOM_USART_CTRLA_SWRST) > 0 ||
|
||||
(uart.Bus.SYNCBUSY&sam.SERCOM_USART_SYNCBUSY_SWRST) > 0 {
|
||||
}
|
||||
|
||||
// set UART mode/sample rate
|
||||
// SERCOM_USART_CTRLA_MODE(mode) |
|
||||
// SERCOM_USART_CTRLA_SAMPR(sampleRate);
|
||||
uart.Bus.CTRLA = (sam.SERCOM_USART_CTRLA_MODE_USART_INT_CLK << sam.SERCOM_USART_CTRLA_MODE_Pos) |
|
||||
(1 << sam.SERCOM_USART_CTRLA_SAMPR_Pos) // sample rate of 16x
|
||||
|
||||
// Set baud rate
|
||||
uart.SetBaudRate(config.BaudRate)
|
||||
|
||||
// setup UART frame
|
||||
// SERCOM_USART_CTRLA_FORM( (parityMode == SERCOM_NO_PARITY ? 0 : 1) ) |
|
||||
// dataOrder << SERCOM_USART_CTRLA_DORD_Pos;
|
||||
uart.Bus.CTRLA |= (0 << sam.SERCOM_USART_CTRLA_FORM_Pos) | // no parity
|
||||
(lsbFirst << sam.SERCOM_USART_CTRLA_DORD_Pos) // data order
|
||||
|
||||
// set UART stop bits/parity
|
||||
// SERCOM_USART_CTRLB_CHSIZE(charSize) |
|
||||
// nbStopBits << SERCOM_USART_CTRLB_SBMODE_Pos |
|
||||
// (parityMode == SERCOM_NO_PARITY ? 0 : parityMode) << SERCOM_USART_CTRLB_PMODE_Pos; //If no parity use default value
|
||||
uart.Bus.CTRLB |= (0 << sam.SERCOM_USART_CTRLB_CHSIZE_Pos) | // 8 bits is 0
|
||||
(0 << sam.SERCOM_USART_CTRLB_SBMODE_Pos) | // 1 stop bit is zero
|
||||
(0 << sam.SERCOM_USART_CTRLB_PMODE_Pos) // no parity
|
||||
|
||||
// set UART pads. This is not same as pins...
|
||||
// SERCOM_USART_CTRLA_TXPO(txPad) |
|
||||
// SERCOM_USART_CTRLA_RXPO(rxPad);
|
||||
uart.Bus.CTRLA |= sam.RegValue((txpad << sam.SERCOM_USART_CTRLA_TXPO_Pos) |
|
||||
(rxpad << sam.SERCOM_USART_CTRLA_RXPO_Pos))
|
||||
|
||||
// Enable Transceiver and Receiver
|
||||
//sercom->USART.CTRLB.reg |= SERCOM_USART_CTRLB_TXEN | SERCOM_USART_CTRLB_RXEN ;
|
||||
uart.Bus.CTRLB |= (sam.SERCOM_USART_CTRLB_TXEN | sam.SERCOM_USART_CTRLB_RXEN)
|
||||
|
||||
// Enable USART1 port.
|
||||
// sercom->USART.CTRLA.bit.ENABLE = 0x1u;
|
||||
uart.Bus.CTRLA |= sam.SERCOM_USART_CTRLA_ENABLE
|
||||
for (uart.Bus.SYNCBUSY & sam.SERCOM_USART_SYNCBUSY_ENABLE) > 0 {
|
||||
}
|
||||
|
||||
// setup interrupt on receive
|
||||
uart.Bus.INTENSET = sam.SERCOM_USART_INTENSET_RXC
|
||||
|
||||
// Enable RX IRQ.
|
||||
if config.TX == UART_TX_PIN {
|
||||
// UART0
|
||||
arm.EnableIRQ(sam.IRQ_SERCOM0)
|
||||
} else {
|
||||
// UART1
|
||||
arm.EnableIRQ(sam.IRQ_SERCOM1)
|
||||
}
|
||||
}
|
||||
|
||||
// SetBaudRate sets the communication speed for the UART.
|
||||
func (uart UART) SetBaudRate(br uint32) {
|
||||
// Asynchronous fractional mode (Table 24-2 in datasheet)
|
||||
// BAUD = fref / (sampleRateValue * fbaud)
|
||||
// (multiply by 8, to calculate fractional piece)
|
||||
// uint32_t baudTimes8 = (SystemCoreClock * 8) / (16 * baudrate);
|
||||
baud := (CPU_FREQUENCY * 8) / (sampleRate16X * br)
|
||||
|
||||
// sercom->USART.BAUD.FRAC.FP = (baudTimes8 % 8);
|
||||
// sercom->USART.BAUD.FRAC.BAUD = (baudTimes8 / 8);
|
||||
uart.Bus.BAUD = sam.RegValue16(((baud % 8) << sam.SERCOM_USART_BAUD_FRAC_MODE_FP_Pos) |
|
||||
((baud / 8) << sam.SERCOM_USART_BAUD_FRAC_MODE_BAUD_Pos))
|
||||
}
|
||||
|
||||
// WriteByte writes a byte of data to the UART.
|
||||
func (uart UART) WriteByte(c byte) error {
|
||||
// wait until ready to receive
|
||||
for (uart.Bus.INTFLAG & sam.SERCOM_USART_INTFLAG_DRE) == 0 {
|
||||
}
|
||||
uart.Bus.DATA = sam.RegValue16(c)
|
||||
return nil
|
||||
}
|
||||
|
||||
//go:export SERCOM0_IRQHandler
|
||||
func handleUART0() {
|
||||
// should reset IRQ
|
||||
UART0.Receive(byte((UART0.Bus.DATA & 0xFF)))
|
||||
UART0.Bus.INTFLAG |= sam.SERCOM_USART_INTFLAG_RXC
|
||||
}
|
||||
|
||||
//go:export SERCOM1_IRQHandler
|
||||
func handleUART1() {
|
||||
// should reset IRQ
|
||||
UART1.Receive(byte((UART1.Bus.DATA & 0xFF)))
|
||||
UART1.Bus.INTFLAG |= sam.SERCOM_USART_INTFLAG_RXC
|
||||
}
|
||||
|
||||
// I2C on the SAMD21.
|
||||
type I2C struct {
|
||||
Bus *sam.SERCOM_I2CM_Type
|
||||
}
|
||||
|
||||
// Since the I2C interfaces on the SAMD21 use the SERCOMx peripherals,
|
||||
// you can have multiple ones. we currently only implement one.
|
||||
var (
|
||||
I2C0 = I2C{Bus: sam.SERCOM3_I2CM}
|
||||
)
|
||||
|
||||
// I2CConfig is used to store config info for I2C.
|
||||
type I2CConfig struct {
|
||||
Frequency uint32
|
||||
SCL uint8
|
||||
SDA uint8
|
||||
}
|
||||
|
||||
const (
|
||||
// Default rise time in nanoseconds, based on 4.7K ohm pull up resistors
|
||||
riseTimeNanoseconds = 125
|
||||
|
||||
// wire bus states
|
||||
wireUnknownState = 0
|
||||
wireIdleState = 1
|
||||
wireOwnerState = 2
|
||||
wireBusyState = 3
|
||||
|
||||
// wire commands
|
||||
wireCmdNoAction = 0
|
||||
wireCmdRepeatStart = 1
|
||||
wireCmdRead = 2
|
||||
wireCmdStop = 3
|
||||
)
|
||||
|
||||
const i2cTimeout = 1000
|
||||
|
||||
// Configure is intended to setup the I2C interface.
|
||||
func (i2c I2C) Configure(config I2CConfig) {
|
||||
// Default I2C bus speed is 100 kHz.
|
||||
if config.Frequency == 0 {
|
||||
config.Frequency = TWI_FREQ_100KHZ
|
||||
}
|
||||
|
||||
// reset SERCOM3
|
||||
i2c.Bus.CTRLA |= sam.SERCOM_I2CM_CTRLA_SWRST
|
||||
for (i2c.Bus.CTRLA&sam.SERCOM_I2CM_CTRLA_SWRST) > 0 ||
|
||||
(i2c.Bus.SYNCBUSY&sam.SERCOM_I2CM_SYNCBUSY_SWRST) > 0 {
|
||||
}
|
||||
|
||||
// Set i2c master mode
|
||||
//SERCOM_I2CM_CTRLA_MODE( I2C_MASTER_OPERATION )
|
||||
i2c.Bus.CTRLA = (sam.SERCOM_I2CM_CTRLA_MODE_I2C_MASTER << sam.SERCOM_I2CM_CTRLA_MODE_Pos) // |
|
||||
|
||||
i2c.SetBaudRate(config.Frequency)
|
||||
|
||||
// Enable I2CM port.
|
||||
// sercom->USART.CTRLA.bit.ENABLE = 0x1u;
|
||||
i2c.Bus.CTRLA |= sam.SERCOM_I2CM_CTRLA_ENABLE
|
||||
for (i2c.Bus.SYNCBUSY & sam.SERCOM_I2CM_SYNCBUSY_ENABLE) > 0 {
|
||||
}
|
||||
|
||||
// set bus idle mode
|
||||
i2c.Bus.STATUS |= (wireIdleState << sam.SERCOM_I2CM_STATUS_BUSSTATE_Pos)
|
||||
for (i2c.Bus.SYNCBUSY & sam.SERCOM_I2CM_SYNCBUSY_SYSOP) > 0 {
|
||||
}
|
||||
|
||||
// enable pins
|
||||
GPIO{SDA_PIN}.Configure(GPIOConfig{Mode: GPIO_SERCOM})
|
||||
GPIO{SCL_PIN}.Configure(GPIOConfig{Mode: GPIO_SERCOM})
|
||||
}
|
||||
|
||||
// SetBaudRate sets the communication speed for the I2C.
|
||||
func (i2c I2C) SetBaudRate(br uint32) {
|
||||
// Synchronous arithmetic baudrate, via Arduino SAMD implementation:
|
||||
// SystemCoreClock / ( 2 * baudrate) - 5 - (((SystemCoreClock / 1000000) * WIRE_RISE_TIME_NANOSECONDS) / (2 * 1000));
|
||||
baud := CPU_FREQUENCY/(2*br) - 5 - (((CPU_FREQUENCY / 1000000) * riseTimeNanoseconds) / (2 * 1000))
|
||||
i2c.Bus.BAUD = sam.RegValue(baud)
|
||||
}
|
||||
|
||||
// Tx does a single I2C transaction at the specified address.
|
||||
// It clocks out the given address, writes the bytes in w, reads back len(r)
|
||||
// bytes and stores them in r, and generates a stop condition on the bus.
|
||||
func (i2c I2C) Tx(addr uint16, w, r []byte) error {
|
||||
var err error
|
||||
if len(w) != 0 {
|
||||
// send start/address for write
|
||||
i2c.sendAddress(addr, true)
|
||||
|
||||
// wait until transmission complete
|
||||
timeout := i2cTimeout
|
||||
for (i2c.Bus.INTFLAG & sam.SERCOM_I2CM_INTFLAG_MB) == 0 {
|
||||
timeout--
|
||||
if timeout == 0 {
|
||||
return errors.New("I2C timeout on ready to write data")
|
||||
}
|
||||
}
|
||||
|
||||
// ACK received (0: ACK, 1: NACK)
|
||||
if (i2c.Bus.STATUS & sam.SERCOM_I2CM_STATUS_RXNACK) > 0 {
|
||||
return errors.New("I2C write error: expected ACK not NACK")
|
||||
}
|
||||
|
||||
// write data
|
||||
for _, b := range w {
|
||||
err = i2c.WriteByte(b)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
err = i2c.signalStop()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if len(r) != 0 {
|
||||
// send start/address for read
|
||||
i2c.sendAddress(addr, false)
|
||||
|
||||
// wait transmission complete
|
||||
for (i2c.Bus.INTFLAG & sam.SERCOM_I2CM_INTFLAG_SB) == 0 {
|
||||
// If the slave NACKS the address, the MB bit will be set.
|
||||
// In that case, send a stop condition and return error.
|
||||
if (i2c.Bus.INTFLAG & sam.SERCOM_I2CM_INTFLAG_MB) > 0 {
|
||||
i2c.Bus.CTRLB |= (wireCmdStop << sam.SERCOM_I2CM_CTRLB_CMD_Pos) // Stop condition
|
||||
return errors.New("I2C read error: expected ACK not NACK")
|
||||
}
|
||||
}
|
||||
|
||||
// ACK received (0: ACK, 1: NACK)
|
||||
if (i2c.Bus.STATUS & sam.SERCOM_I2CM_STATUS_RXNACK) > 0 {
|
||||
return errors.New("I2C read error: expected ACK not NACK")
|
||||
}
|
||||
|
||||
// read first byte
|
||||
r[0] = i2c.readByte()
|
||||
for i := 1; i < len(r); i++ {
|
||||
// Send an ACK
|
||||
i2c.Bus.CTRLB &^= sam.SERCOM_I2CM_CTRLB_ACKACT
|
||||
|
||||
i2c.signalRead()
|
||||
|
||||
// Read data and send the ACK
|
||||
r[i] = i2c.readByte()
|
||||
}
|
||||
|
||||
// Send NACK to end transmission
|
||||
i2c.Bus.CTRLB |= sam.SERCOM_I2CM_CTRLB_ACKACT
|
||||
|
||||
err = i2c.signalStop()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// WriteByte writes a single byte to the I2C bus.
|
||||
func (i2c I2C) WriteByte(data byte) error {
|
||||
// Send data byte
|
||||
i2c.Bus.DATA = sam.RegValue8(data)
|
||||
|
||||
// wait until transmission successful
|
||||
timeout := i2cTimeout
|
||||
for (i2c.Bus.INTFLAG & sam.SERCOM_I2CM_INTFLAG_MB) == 0 {
|
||||
// check for bus error
|
||||
if (sam.SERCOM3_I2CM.STATUS & sam.SERCOM_I2CM_STATUS_BUSERR) > 0 {
|
||||
return errors.New("I2C bus error")
|
||||
}
|
||||
timeout--
|
||||
if timeout == 0 {
|
||||
return errors.New("I2C timeout on write data")
|
||||
}
|
||||
}
|
||||
|
||||
if (i2c.Bus.STATUS & sam.SERCOM_I2CM_STATUS_RXNACK) > 0 {
|
||||
return errors.New("I2C write error: expected ACK not NACK")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// sendAddress sends the address and start signal
|
||||
func (i2c I2C) sendAddress(address uint16, write bool) error {
|
||||
data := (address << 1)
|
||||
if !write {
|
||||
data |= 1 // set read flag
|
||||
}
|
||||
|
||||
// wait until bus ready
|
||||
timeout := i2cTimeout
|
||||
for (i2c.Bus.STATUS&(wireIdleState<<sam.SERCOM_I2CM_STATUS_BUSSTATE_Pos)) == 0 &&
|
||||
(i2c.Bus.STATUS&(wireOwnerState<<sam.SERCOM_I2CM_STATUS_BUSSTATE_Pos)) == 0 {
|
||||
timeout--
|
||||
if timeout == 0 {
|
||||
return errors.New("I2C timeout on bus ready")
|
||||
}
|
||||
}
|
||||
i2c.Bus.ADDR = sam.RegValue(data)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (i2c I2C) signalStop() error {
|
||||
i2c.Bus.CTRLB |= (wireCmdStop << sam.SERCOM_I2CM_CTRLB_CMD_Pos) // Stop command
|
||||
timeout := i2cTimeout
|
||||
for (i2c.Bus.SYNCBUSY & sam.SERCOM_I2CM_SYNCBUSY_SYSOP) > 0 {
|
||||
timeout--
|
||||
if timeout == 0 {
|
||||
return errors.New("I2C timeout on signal stop")
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (i2c I2C) signalRead() error {
|
||||
i2c.Bus.CTRLB |= (wireCmdRead << sam.SERCOM_I2CM_CTRLB_CMD_Pos) // Read command
|
||||
timeout := i2cTimeout
|
||||
for (i2c.Bus.SYNCBUSY & sam.SERCOM_I2CM_SYNCBUSY_SYSOP) > 0 {
|
||||
timeout--
|
||||
if timeout == 0 {
|
||||
return errors.New("I2C timeout on signal read")
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (i2c I2C) readByte() byte {
|
||||
for (i2c.Bus.INTFLAG & sam.SERCOM_I2CM_INTFLAG_SB) == 0 {
|
||||
}
|
||||
return byte(i2c.Bus.DATA)
|
||||
}
|
||||
|
||||
// PWM
|
||||
const period = 0xFFFF
|
||||
|
||||
// InitPWM initializes the PWM interface.
|
||||
func InitPWM() {
|
||||
// turn on timer clocks used for PWM
|
||||
sam.PM.APBCMASK |= sam.PM_APBCMASK_TCC0_ | sam.PM_APBCMASK_TCC1_ | sam.PM_APBCMASK_TCC2_
|
||||
|
||||
// Use GCLK0 for TCC0/TCC1
|
||||
sam.GCLK.CLKCTRL = sam.RegValue16((sam.GCLK_CLKCTRL_ID_TCC0_TCC1 << sam.GCLK_CLKCTRL_ID_Pos) |
|
||||
(sam.GCLK_CLKCTRL_GEN_GCLK0 << sam.GCLK_CLKCTRL_GEN_Pos) |
|
||||
sam.GCLK_CLKCTRL_CLKEN)
|
||||
for (sam.GCLK.STATUS & sam.GCLK_STATUS_SYNCBUSY) > 0 {
|
||||
}
|
||||
|
||||
// Use GCLK0 for TCC2/TC3
|
||||
sam.GCLK.CLKCTRL = sam.RegValue16((sam.GCLK_CLKCTRL_ID_TCC2_TC3 << sam.GCLK_CLKCTRL_ID_Pos) |
|
||||
(sam.GCLK_CLKCTRL_GEN_GCLK0 << sam.GCLK_CLKCTRL_GEN_Pos) |
|
||||
sam.GCLK_CLKCTRL_CLKEN)
|
||||
for (sam.GCLK.STATUS & sam.GCLK_STATUS_SYNCBUSY) > 0 {
|
||||
}
|
||||
}
|
||||
|
||||
// Configure configures a PWM pin for output.
|
||||
func (pwm PWM) Configure() {
|
||||
// figure out which TCCX timer for this pin
|
||||
timer := pwm.getTimer()
|
||||
|
||||
// disable timer
|
||||
timer.CTRLA &^= sam.TCC_CTRLA_ENABLE
|
||||
// Wait for synchronization
|
||||
for (timer.SYNCBUSY & sam.TCC_SYNCBUSY_ENABLE) > 0 {
|
||||
}
|
||||
|
||||
// Use "Normal PWM" (single-slope PWM)
|
||||
timer.WAVE |= sam.TCC_WAVE_WAVEGEN_NPWM
|
||||
// Wait for synchronization
|
||||
for (timer.SYNCBUSY & sam.TCC_SYNCBUSY_WAVE) > 0 {
|
||||
}
|
||||
|
||||
// Set the period (the number to count to (TOP) before resetting timer)
|
||||
//TCC0->PER.reg = period;
|
||||
timer.PER = period
|
||||
// Wait for synchronization
|
||||
for (timer.SYNCBUSY & sam.TCC_SYNCBUSY_PER) > 0 {
|
||||
}
|
||||
|
||||
// Set pin as output
|
||||
sam.PORT.DIRSET0 = (1 << pwm.Pin)
|
||||
// Set pin to low
|
||||
sam.PORT.OUTCLR0 = (1 << pwm.Pin)
|
||||
|
||||
// Enable the port multiplexer for pin
|
||||
pwm.setPinCfg(sam.PORT_PINCFG0_PMUXEN)
|
||||
|
||||
// Connect TCCX timer to pin.
|
||||
// we normally use the F channel aka ALT
|
||||
pwmConfig := GPIO_PWM_ALT
|
||||
|
||||
// in the case of PA6 or PA7 we have to use E channel
|
||||
if pwm.Pin == 6 || pwm.Pin == 7 {
|
||||
pwmConfig = GPIO_PWM
|
||||
}
|
||||
|
||||
if pwm.Pin&1 > 0 {
|
||||
// odd pin, so save the even pins
|
||||
val := pwm.getPMux() & sam.PORT_PMUX0_PMUXE_Msk
|
||||
pwm.setPMux(val | sam.RegValue8(pwmConfig<<sam.PORT_PMUX0_PMUXO_Pos))
|
||||
} else {
|
||||
// even pin, so save the odd pins
|
||||
val := pwm.getPMux() & sam.PORT_PMUX0_PMUXO_Msk
|
||||
pwm.setPMux(val | sam.RegValue8(pwmConfig<<sam.PORT_PMUX0_PMUXE_Pos))
|
||||
}
|
||||
}
|
||||
|
||||
// Set turns on the duty cycle for a PWM pin using the provided value.
|
||||
func (pwm PWM) Set(value uint16) {
|
||||
// figure out which TCCX timer for this pin
|
||||
timer := pwm.getTimer()
|
||||
|
||||
// disable output
|
||||
timer.CTRLA &^= sam.TCC_CTRLA_ENABLE
|
||||
|
||||
// Wait for synchronization
|
||||
for (timer.SYNCBUSY & sam.TCC_SYNCBUSY_ENABLE) > 0 {
|
||||
}
|
||||
|
||||
// Set PWM signal to output duty cycle
|
||||
pwm.setChannel(sam.RegValue(value))
|
||||
|
||||
// Wait for synchronization on all channels
|
||||
for (timer.SYNCBUSY & (sam.TCC_SYNCBUSY_CC0 |
|
||||
sam.TCC_SYNCBUSY_CC1 |
|
||||
sam.TCC_SYNCBUSY_CC2 |
|
||||
sam.TCC_SYNCBUSY_CC3)) > 0 {
|
||||
}
|
||||
|
||||
// enable
|
||||
timer.CTRLA |= sam.TCC_CTRLA_ENABLE
|
||||
// Wait for synchronization
|
||||
for (timer.SYNCBUSY & sam.TCC_SYNCBUSY_ENABLE) > 0 {
|
||||
}
|
||||
}
|
||||
|
||||
// getPMux returns the value for the correct PMUX register for this pin.
|
||||
func (pwm PWM) getPMux() sam.RegValue8 {
|
||||
return getPMux(pwm.Pin)
|
||||
}
|
||||
|
||||
// setPMux sets the value for the correct PMUX register for this pin.
|
||||
func (pwm PWM) setPMux(val sam.RegValue8) {
|
||||
setPMux(pwm.Pin, val)
|
||||
}
|
||||
|
||||
// getPinCfg returns the value for the correct PINCFG register for this pin.
|
||||
func (pwm PWM) getPinCfg() sam.RegValue8 {
|
||||
return getPinCfg(pwm.Pin)
|
||||
}
|
||||
|
||||
// setPinCfg sets the value for the correct PINCFG register for this pin.
|
||||
func (pwm PWM) setPinCfg(val sam.RegValue8) {
|
||||
setPinCfg(pwm.Pin, val)
|
||||
}
|
||||
|
||||
// getPMux returns the value for the correct PMUX register for this pin.
|
||||
func getPMux(p uint8) sam.RegValue8 {
|
||||
pin := p >> 1
|
||||
switch pin {
|
||||
case 0:
|
||||
return sam.PORT.PMUX0_0
|
||||
case 1:
|
||||
return sam.PORT.PMUX0_1
|
||||
case 2:
|
||||
return sam.PORT.PMUX0_2
|
||||
case 3:
|
||||
return sam.PORT.PMUX0_3
|
||||
case 4:
|
||||
return sam.PORT.PMUX0_4
|
||||
case 5:
|
||||
return sam.PORT.PMUX0_5
|
||||
case 6:
|
||||
return sam.PORT.PMUX0_6
|
||||
case 7:
|
||||
return sam.PORT.PMUX0_7
|
||||
case 8:
|
||||
return sam.PORT.PMUX0_8
|
||||
case 9:
|
||||
return sam.PORT.PMUX0_9
|
||||
case 10:
|
||||
return sam.PORT.PMUX0_10
|
||||
case 11:
|
||||
return sam.PORT.PMUX0_11
|
||||
case 12:
|
||||
return sam.PORT.PMUX0_12
|
||||
case 13:
|
||||
return sam.PORT.PMUX0_13
|
||||
case 14:
|
||||
return sam.PORT.PMUX0_14
|
||||
case 15:
|
||||
return sam.PORT.PMUX0_15
|
||||
default:
|
||||
return 0
|
||||
}
|
||||
}
|
||||
|
||||
// setPMux sets the value for the correct PMUX register for this pin.
|
||||
func setPMux(p uint8, val sam.RegValue8) {
|
||||
pin := p >> 1
|
||||
switch pin {
|
||||
case 0:
|
||||
sam.PORT.PMUX0_0 = val
|
||||
case 1:
|
||||
sam.PORT.PMUX0_1 = val
|
||||
case 2:
|
||||
sam.PORT.PMUX0_2 = val
|
||||
case 3:
|
||||
sam.PORT.PMUX0_3 = val
|
||||
case 4:
|
||||
sam.PORT.PMUX0_4 = val
|
||||
case 5:
|
||||
sam.PORT.PMUX0_5 = val
|
||||
case 6:
|
||||
sam.PORT.PMUX0_6 = val
|
||||
case 7:
|
||||
sam.PORT.PMUX0_7 = val
|
||||
case 8:
|
||||
sam.PORT.PMUX0_8 = val
|
||||
case 9:
|
||||
sam.PORT.PMUX0_9 = val
|
||||
case 10:
|
||||
sam.PORT.PMUX0_10 = val
|
||||
case 11:
|
||||
sam.PORT.PMUX0_11 = val
|
||||
case 12:
|
||||
sam.PORT.PMUX0_12 = val
|
||||
case 13:
|
||||
sam.PORT.PMUX0_13 = val
|
||||
case 14:
|
||||
sam.PORT.PMUX0_14 = val
|
||||
case 15:
|
||||
sam.PORT.PMUX0_15 = val
|
||||
}
|
||||
}
|
||||
|
||||
// getPinCfg returns the value for the correct PINCFG register for this pin.
|
||||
func getPinCfg(p uint8) sam.RegValue8 {
|
||||
switch p {
|
||||
case 0:
|
||||
return sam.PORT.PINCFG0_0
|
||||
case 1:
|
||||
return sam.PORT.PINCFG0_1
|
||||
case 2:
|
||||
return sam.PORT.PINCFG0_2
|
||||
case 3:
|
||||
return sam.PORT.PINCFG0_3
|
||||
case 4:
|
||||
return sam.PORT.PINCFG0_4
|
||||
case 5:
|
||||
return sam.PORT.PINCFG0_5
|
||||
case 6:
|
||||
return sam.PORT.PINCFG0_6
|
||||
case 7:
|
||||
return sam.PORT.PINCFG0_7
|
||||
case 8:
|
||||
return sam.PORT.PINCFG0_8
|
||||
case 9:
|
||||
return sam.PORT.PINCFG0_9
|
||||
case 10:
|
||||
return sam.PORT.PINCFG0_10
|
||||
case 11:
|
||||
return sam.PORT.PINCFG0_11
|
||||
case 12:
|
||||
return sam.PORT.PINCFG0_12
|
||||
case 13:
|
||||
return sam.PORT.PINCFG0_13
|
||||
case 14:
|
||||
return sam.PORT.PINCFG0_14
|
||||
case 15:
|
||||
return sam.PORT.PINCFG0_15
|
||||
case 16:
|
||||
return sam.PORT.PINCFG0_16
|
||||
case 17:
|
||||
return sam.PORT.PINCFG0_17
|
||||
case 18:
|
||||
return sam.PORT.PINCFG0_18
|
||||
case 19:
|
||||
return sam.PORT.PINCFG0_19
|
||||
case 20:
|
||||
return sam.PORT.PINCFG0_20
|
||||
case 21:
|
||||
return sam.PORT.PINCFG0_21
|
||||
case 22:
|
||||
return sam.PORT.PINCFG0_22
|
||||
case 23:
|
||||
return sam.PORT.PINCFG0_23
|
||||
case 24:
|
||||
return sam.PORT.PINCFG0_24
|
||||
case 25:
|
||||
return sam.PORT.PINCFG0_25
|
||||
case 26:
|
||||
return sam.PORT.PINCFG0_26
|
||||
case 27:
|
||||
return sam.PORT.PINCFG0_27
|
||||
case 28:
|
||||
return sam.PORT.PINCFG0_28
|
||||
case 29:
|
||||
return sam.PORT.PINCFG0_29
|
||||
case 30:
|
||||
return sam.PORT.PINCFG0_30
|
||||
case 31:
|
||||
return sam.PORT.PINCFG0_31
|
||||
default:
|
||||
return 0
|
||||
}
|
||||
}
|
||||
|
||||
// setPinCfg sets the value for the correct PINCFG register for this pin.
|
||||
func setPinCfg(p uint8, val sam.RegValue8) {
|
||||
switch p {
|
||||
case 0:
|
||||
sam.PORT.PINCFG0_0 = val
|
||||
case 1:
|
||||
sam.PORT.PINCFG0_1 = val
|
||||
case 2:
|
||||
sam.PORT.PINCFG0_2 = val
|
||||
case 3:
|
||||
sam.PORT.PINCFG0_3 = val
|
||||
case 4:
|
||||
sam.PORT.PINCFG0_4 = val
|
||||
case 5:
|
||||
sam.PORT.PINCFG0_5 = val
|
||||
case 6:
|
||||
sam.PORT.PINCFG0_6 = val
|
||||
case 7:
|
||||
sam.PORT.PINCFG0_7 = val
|
||||
case 8:
|
||||
sam.PORT.PINCFG0_8 = val
|
||||
case 9:
|
||||
sam.PORT.PINCFG0_9 = val
|
||||
case 10:
|
||||
sam.PORT.PINCFG0_10 = val
|
||||
case 11:
|
||||
sam.PORT.PINCFG0_11 = val
|
||||
case 12:
|
||||
sam.PORT.PINCFG0_12 = val
|
||||
case 13:
|
||||
sam.PORT.PINCFG0_13 = val
|
||||
case 14:
|
||||
sam.PORT.PINCFG0_14 = val
|
||||
case 15:
|
||||
sam.PORT.PINCFG0_15 = val
|
||||
case 16:
|
||||
sam.PORT.PINCFG0_16 = val
|
||||
case 17:
|
||||
sam.PORT.PINCFG0_17 = val
|
||||
case 18:
|
||||
sam.PORT.PINCFG0_18 = val
|
||||
case 19:
|
||||
sam.PORT.PINCFG0_19 = val
|
||||
case 20:
|
||||
sam.PORT.PINCFG0_20 = val
|
||||
case 21:
|
||||
sam.PORT.PINCFG0_21 = val
|
||||
case 22:
|
||||
sam.PORT.PINCFG0_22 = val
|
||||
case 23:
|
||||
sam.PORT.PINCFG0_23 = val
|
||||
case 24:
|
||||
sam.PORT.PINCFG0_24 = val
|
||||
case 25:
|
||||
sam.PORT.PINCFG0_25 = val
|
||||
case 26:
|
||||
sam.PORT.PINCFG0_26 = val
|
||||
case 27:
|
||||
sam.PORT.PINCFG0_27 = val
|
||||
case 28:
|
||||
sam.PORT.PINCFG0_28 = val
|
||||
case 29:
|
||||
sam.PORT.PINCFG0_29 = val
|
||||
case 30:
|
||||
sam.PORT.PINCFG0_30 = val
|
||||
case 31:
|
||||
sam.PORT.PINCFG0_31 = val
|
||||
}
|
||||
}
|
||||
|
||||
// getTimer returns the timer to be used for PWM on this pin
|
||||
func (pwm PWM) getTimer() *sam.TCC_Type {
|
||||
switch pwm.Pin {
|
||||
case 6:
|
||||
return sam.TCC1
|
||||
case 7:
|
||||
return sam.TCC1
|
||||
case 8:
|
||||
return sam.TCC1
|
||||
case 9:
|
||||
return sam.TCC1
|
||||
case 14:
|
||||
return sam.TCC0
|
||||
case 15:
|
||||
return sam.TCC0
|
||||
case 16:
|
||||
return sam.TCC0
|
||||
case 17:
|
||||
return sam.TCC0
|
||||
case 18:
|
||||
return sam.TCC0
|
||||
case 19:
|
||||
return sam.TCC0
|
||||
case 20:
|
||||
return sam.TCC0
|
||||
case 21:
|
||||
return sam.TCC0
|
||||
default:
|
||||
return nil // not supported on this pin
|
||||
}
|
||||
}
|
||||
|
||||
// setChannel sets the value for the correct channel for PWM on this pin
|
||||
func (pwm PWM) setChannel(val sam.RegValue) {
|
||||
switch pwm.Pin {
|
||||
case 6:
|
||||
pwm.getTimer().CC0 = val
|
||||
case 7:
|
||||
pwm.getTimer().CC1 = val
|
||||
case 8:
|
||||
pwm.getTimer().CC0 = val
|
||||
case 9:
|
||||
pwm.getTimer().CC1 = val
|
||||
case 14:
|
||||
pwm.getTimer().CC0 = val
|
||||
case 15:
|
||||
pwm.getTimer().CC1 = val
|
||||
case 16:
|
||||
pwm.getTimer().CC2 = val
|
||||
case 17:
|
||||
pwm.getTimer().CC3 = val
|
||||
case 18:
|
||||
pwm.getTimer().CC2 = val
|
||||
case 19:
|
||||
pwm.getTimer().CC3 = val
|
||||
case 20:
|
||||
pwm.getTimer().CC2 = val
|
||||
case 21:
|
||||
pwm.getTimer().CC3 = val
|
||||
default:
|
||||
return // not supported on this pin
|
||||
}
|
||||
}
|
||||
@@ -25,6 +25,12 @@ func (p GPIO) Get() bool {
|
||||
return (val > 0)
|
||||
}
|
||||
|
||||
// UART on the AVR is a dummy implementation. UART has not been implemented for ATtiny
|
||||
// devices.
|
||||
type UART struct {
|
||||
Buffer *RingBuffer
|
||||
}
|
||||
|
||||
// Configure is a dummy implementation. UART has not been implemented for ATtiny
|
||||
// devices.
|
||||
func (uart UART) Configure(config UARTConfig) {
|
||||
|
||||
@@ -92,5 +92,5 @@ var I2C0 = I2C{}
|
||||
// UART
|
||||
var (
|
||||
// UART0 is the hardware serial port on the AVR.
|
||||
UART0 = &UART{}
|
||||
UART0 = UART{Buffer: NewRingBuffer()}
|
||||
)
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// +build !avr,!nrf,!stm32
|
||||
// +build !avr,!nrf,!sam,!stm32
|
||||
|
||||
package machine
|
||||
|
||||
|
||||
@@ -54,10 +54,15 @@ func (p GPIO) Get() bool {
|
||||
return (port.IN>>pin)&1 != 0
|
||||
}
|
||||
|
||||
// UART on the NRF.
|
||||
type UART struct {
|
||||
Buffer *RingBuffer
|
||||
}
|
||||
|
||||
// UART
|
||||
var (
|
||||
// UART0 is the hardware serial port on the NRF.
|
||||
UART0 = &UART{}
|
||||
UART0 = UART{Buffer: NewRingBuffer()}
|
||||
)
|
||||
|
||||
// Configure the UART.
|
||||
@@ -108,7 +113,7 @@ func (uart UART) WriteByte(c byte) error {
|
||||
|
||||
func (uart UART) handleInterrupt() {
|
||||
if nrf.UART0.EVENTS_RXDRDY != 0 {
|
||||
bufferPut(byte(nrf.UART0.RXD))
|
||||
uart.Receive(byte(nrf.UART0.RXD))
|
||||
nrf.UART0.EVENTS_RXDRDY = 0x0
|
||||
}
|
||||
}
|
||||
|
||||
@@ -100,11 +100,15 @@ func (p GPIO) Set(high bool) {
|
||||
}
|
||||
|
||||
// UART
|
||||
type UART struct {
|
||||
Buffer *RingBuffer
|
||||
}
|
||||
|
||||
var (
|
||||
// USART1 is the first hardware serial port on the STM32.
|
||||
// Both UART0 and UART1 refers to USART1.
|
||||
UART0 = &UART{}
|
||||
UART1 = UART0
|
||||
// Both UART0 and UART1 refer to USART1.
|
||||
UART0 = UART{Buffer: NewRingBuffer()}
|
||||
UART1 = &UART0
|
||||
)
|
||||
|
||||
// Configure the UART.
|
||||
@@ -160,7 +164,7 @@ func (uart UART) WriteByte(c byte) error {
|
||||
|
||||
//go:export USART1_IRQHandler
|
||||
func handleUART1() {
|
||||
bufferPut(byte((stm32.USART1.DR & 0xFF)))
|
||||
UART1.Receive(byte((stm32.USART1.DR & 0xFF)))
|
||||
}
|
||||
|
||||
// SPI on the STM32.
|
||||
|
||||
+22
-32
@@ -1,4 +1,4 @@
|
||||
// +build avr nrf stm32
|
||||
// +build avr nrf sam stm32
|
||||
|
||||
package machine
|
||||
|
||||
@@ -10,8 +10,19 @@ type UARTConfig struct {
|
||||
RX uint8
|
||||
}
|
||||
|
||||
type UART struct {
|
||||
}
|
||||
// To implement the UART interface for a board, you must declare a concrete type as follows:
|
||||
//
|
||||
// type UART struct {
|
||||
// Buffer *RingBuffer
|
||||
// }
|
||||
//
|
||||
// You can also add additional members to this struct depending on your implementation,
|
||||
// but the *RingBuffer is required.
|
||||
// When you are declaring your UARTs for your board, make sure that you also declare the
|
||||
// RingBuffer using the NewRingBuffer() function when you declare your UART:
|
||||
//
|
||||
// UART{Buffer: NewRingBuffer()}
|
||||
//
|
||||
|
||||
// Read from the RX buffer.
|
||||
func (uart UART) Read(data []byte) (n int, err error) {
|
||||
@@ -47,41 +58,20 @@ func (uart UART) Write(data []byte) (n int, err error) {
|
||||
// If there is no data in the buffer, returns an error.
|
||||
func (uart UART) ReadByte() (byte, error) {
|
||||
// check if RX buffer is empty
|
||||
if uart.Buffered() == 0 {
|
||||
buf, ok := uart.Buffer.Get()
|
||||
if !ok {
|
||||
return 0, errors.New("Buffer empty")
|
||||
}
|
||||
|
||||
return bufferGet(), nil
|
||||
return buf, nil
|
||||
}
|
||||
|
||||
// Buffered returns the number of bytes currently stored in the RX buffer.
|
||||
func (uart UART) Buffered() int {
|
||||
return int(bufferUsed())
|
||||
return int(uart.Buffer.Used())
|
||||
}
|
||||
|
||||
const bufferSize = 64
|
||||
|
||||
// Minimal ring buffer implementation inspired by post at
|
||||
// https://www.embeddedrelated.com/showthread/comp.arch.embedded/77084-1.php
|
||||
|
||||
//go:volatile
|
||||
type volatileByte byte
|
||||
|
||||
var rxbuffer [bufferSize]volatileByte
|
||||
var head volatileByte
|
||||
var tail volatileByte
|
||||
|
||||
func bufferUsed() uint8 { return uint8(head - tail) }
|
||||
func bufferPut(val byte) {
|
||||
if bufferUsed() != bufferSize {
|
||||
head++
|
||||
rxbuffer[head%bufferSize] = volatileByte(val)
|
||||
}
|
||||
}
|
||||
func bufferGet() byte {
|
||||
if bufferUsed() != 0 {
|
||||
tail++
|
||||
return byte(rxbuffer[tail%bufferSize])
|
||||
}
|
||||
return 0
|
||||
// Receive handles adding data to the UART's data buffer.
|
||||
// Usually called by the IRQ handler for a machine.
|
||||
func (uart UART) Receive(data byte) {
|
||||
uart.Buffer.Put(data)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
// 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 (
|
||||
"errors"
|
||||
)
|
||||
|
||||
// Portable analogs of some common system call errors.
|
||||
var (
|
||||
ErrUnsupported = errors.New("operation not supported")
|
||||
)
|
||||
|
||||
// Stdin, Stdout, and Stderr are open Files pointing to the standard input,
|
||||
// standard output, and standard error file descriptors.
|
||||
var (
|
||||
Stdin = &File{0, "/dev/stdin"}
|
||||
Stdout = &File{1, "/dev/stdout"}
|
||||
Stderr = &File{2, "/dev/stderr"}
|
||||
)
|
||||
|
||||
// File represents an open file descriptor.
|
||||
type File struct {
|
||||
fd uintptr
|
||||
name string
|
||||
}
|
||||
|
||||
// NewFile returns a new File with the given file descriptor and name.
|
||||
func NewFile(fd uintptr, name string) *File {
|
||||
return &File{fd, name}
|
||||
}
|
||||
|
||||
// Fd returns the integer Unix file descriptor referencing the open file. The
|
||||
// file descriptor is valid only until f.Close is called.
|
||||
func (f *File) Fd() uintptr {
|
||||
return f.fd
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
// +build linux
|
||||
|
||||
package os
|
||||
|
||||
import (
|
||||
"syscall"
|
||||
)
|
||||
|
||||
// Read reads up to len(b) bytes from the File. It returns the number of bytes
|
||||
// read and any error encountered. At end of file, Read returns 0, io.EOF.
|
||||
func (f *File) Read(b []byte) (n int, err error) {
|
||||
return syscall.Read(int(f.fd), b)
|
||||
}
|
||||
|
||||
// Write writes len(b) bytes to the File. It returns the number of bytes written
|
||||
// and an error, if any. Write returns a non-nil error when n != len(b).
|
||||
func (f *File) Write(b []byte) (n int, err error) {
|
||||
return syscall.Write(int(f.fd), b)
|
||||
}
|
||||
|
||||
// Close closes the File, rendering it unusable for I/O.
|
||||
func (f *File) Close() error {
|
||||
return syscall.Close(int(f.fd))
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
// +build wasm
|
||||
|
||||
package os
|
||||
|
||||
import (
|
||||
_ "unsafe"
|
||||
)
|
||||
|
||||
// Read is unsupported on this system.
|
||||
func (f *File) Read(b []byte) (n int, err error) {
|
||||
return 0, ErrUnsupported
|
||||
}
|
||||
|
||||
// Write writes len(b) bytes to the output. It returns the number of bytes
|
||||
// written or an error if this file is not stdout or stderr.
|
||||
func (f *File) Write(b []byte) (n int, err error) {
|
||||
switch f.fd {
|
||||
case Stdout.fd, Stderr.fd:
|
||||
for _, c := range b {
|
||||
putchar(c)
|
||||
}
|
||||
return len(b), nil
|
||||
default:
|
||||
return 0, ErrUnsupported
|
||||
}
|
||||
}
|
||||
|
||||
// Close is unsupported on this system.
|
||||
func (f *File) Close() error {
|
||||
return ErrUnsupported
|
||||
}
|
||||
|
||||
//go:linkname putchar runtime.putchar
|
||||
func putchar(c byte)
|
||||
+137
-11
@@ -1,9 +1,29 @@
|
||||
package reflect
|
||||
|
||||
// A Kind is the number that the compiler uses for this type.
|
||||
import (
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
// The compiler uses a compact encoding to store type information. Unlike the
|
||||
// main Go compiler, most of the types are stored directly in the type code.
|
||||
//
|
||||
// Not used directly. These types are all replaced with the number the compiler
|
||||
// uses internally for the type.
|
||||
// Type code bit allocation:
|
||||
// xxxxx0: basic types, where xxxxx is the basic type number (never 0).
|
||||
// The higher bits indicate the named type, if any.
|
||||
// nxxx1: complex types, where n indicates whether this is a named type (named
|
||||
// if set) and xxx contains the type kind number:
|
||||
// 0 (0001): Chan
|
||||
// 1 (0011): Interface
|
||||
// 2 (0101): Ptr
|
||||
// 3 (0111): Slice
|
||||
// 4 (1001): Array
|
||||
// 5 (1011): Func
|
||||
// 6 (1101): Map
|
||||
// 7 (1111): Struct
|
||||
// The higher bits are either the contents of the type depending on the
|
||||
// type (if n is clear) or indicate the number of the named type (if n
|
||||
// is set).
|
||||
|
||||
type Kind uintptr
|
||||
|
||||
// Copied from reflect/type.go
|
||||
@@ -26,18 +46,82 @@ const (
|
||||
Float64
|
||||
Complex64
|
||||
Complex128
|
||||
Array
|
||||
String
|
||||
UnsafePointer
|
||||
Chan
|
||||
Func
|
||||
Interface
|
||||
Map
|
||||
Ptr
|
||||
Slice
|
||||
String
|
||||
Array
|
||||
Func
|
||||
Map
|
||||
Struct
|
||||
UnsafePointer
|
||||
)
|
||||
|
||||
func (k Kind) String() string {
|
||||
switch k {
|
||||
case Bool:
|
||||
return "bool"
|
||||
case Int:
|
||||
return "int"
|
||||
case Int8:
|
||||
return "int8"
|
||||
case Int16:
|
||||
return "int16"
|
||||
case Int32:
|
||||
return "int32"
|
||||
case Int64:
|
||||
return "int64"
|
||||
case Uint:
|
||||
return "uint"
|
||||
case Uint8:
|
||||
return "uint8"
|
||||
case Uint16:
|
||||
return "uint16"
|
||||
case Uint32:
|
||||
return "uint32"
|
||||
case Uint64:
|
||||
return "uint64"
|
||||
case Uintptr:
|
||||
return "uintptr"
|
||||
case Float32:
|
||||
return "float32"
|
||||
case Float64:
|
||||
return "float64"
|
||||
case Complex64:
|
||||
return "complex64"
|
||||
case Complex128:
|
||||
return "complex128"
|
||||
case String:
|
||||
return "string"
|
||||
case UnsafePointer:
|
||||
return "unsafe.Pointer"
|
||||
case Chan:
|
||||
return "chan"
|
||||
case Interface:
|
||||
return "interface"
|
||||
case Ptr:
|
||||
return "ptr"
|
||||
case Slice:
|
||||
return "slice"
|
||||
case Array:
|
||||
return "array"
|
||||
case Func:
|
||||
return "func"
|
||||
case Map:
|
||||
return "map"
|
||||
case Struct:
|
||||
return "struct"
|
||||
default:
|
||||
return "invalid"
|
||||
}
|
||||
}
|
||||
|
||||
// basicType returns a new Type for this kind if Kind is a basic type.
|
||||
func (k Kind) basicType() Type {
|
||||
return Type(k << 1)
|
||||
}
|
||||
|
||||
// The typecode as used in an interface{}.
|
||||
type Type uintptr
|
||||
|
||||
@@ -50,11 +134,24 @@ func (t Type) String() string {
|
||||
}
|
||||
|
||||
func (t Type) Kind() Kind {
|
||||
return Invalid // TODO
|
||||
if t % 2 == 0 {
|
||||
// basic type
|
||||
return Kind((t >> 1) % 32)
|
||||
} else {
|
||||
return Kind(t >> 1) % 8 + 19
|
||||
}
|
||||
}
|
||||
|
||||
func (t Type) Elem() Type {
|
||||
panic("unimplemented: (reflect.Type).Elem()")
|
||||
switch t.Kind() {
|
||||
case Chan, Ptr, Slice:
|
||||
if (t >> 4) % 2 != 0 {
|
||||
panic("unimplemented: (reflect.Type).Elem() for named types")
|
||||
}
|
||||
return t >> 5
|
||||
default: // not implemented: Array, Map
|
||||
panic("unimplemented: (reflect.Type).Elem()")
|
||||
}
|
||||
}
|
||||
|
||||
func (t Type) Field(i int) StructField {
|
||||
@@ -74,7 +171,36 @@ func (t Type) NumField() int {
|
||||
}
|
||||
|
||||
func (t Type) Size() uintptr {
|
||||
panic("unimplemented: (reflect.Type).Size()")
|
||||
switch t.Kind() {
|
||||
case Bool, Int8, Uint8:
|
||||
return 1
|
||||
case Int16, Uint16:
|
||||
return 2
|
||||
case Int32, Uint32:
|
||||
return 4
|
||||
case Int64, Uint64:
|
||||
return 8
|
||||
case Int, Uint:
|
||||
return unsafe.Sizeof(int(0))
|
||||
case Uintptr:
|
||||
return unsafe.Sizeof(uintptr(0))
|
||||
case Float32:
|
||||
return 4
|
||||
case Float64:
|
||||
return 8
|
||||
case Complex64:
|
||||
return 8
|
||||
case Complex128:
|
||||
return 16
|
||||
case String:
|
||||
return unsafe.Sizeof(StringHeader{})
|
||||
case UnsafePointer, Chan, Map, Ptr:
|
||||
return unsafe.Sizeof(uintptr(0))
|
||||
case Slice:
|
||||
return unsafe.Sizeof(SliceHeader{})
|
||||
default:
|
||||
panic("unimplemented: size of type")
|
||||
}
|
||||
}
|
||||
|
||||
type StructField struct {
|
||||
|
||||
+396
-31
@@ -1,42 +1,94 @@
|
||||
package reflect
|
||||
|
||||
import (
|
||||
_ "unsafe" // for go:linkname
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
// This is the same thing as an interface{}.
|
||||
type Value struct {
|
||||
typecode Type
|
||||
value *uint8
|
||||
value unsafe.Pointer
|
||||
indirect bool
|
||||
}
|
||||
|
||||
func Indirect(v Value) Value {
|
||||
return v
|
||||
if v.Kind() != Ptr {
|
||||
return v
|
||||
}
|
||||
return v.Elem()
|
||||
}
|
||||
|
||||
func ValueOf(i interface{}) Value
|
||||
|
||||
//go:linkname _ValueOf reflect.ValueOf
|
||||
func _ValueOf(i Value) Value {
|
||||
return i
|
||||
func ValueOf(i interface{}) Value {
|
||||
v := (*interfaceHeader)(unsafe.Pointer(&i))
|
||||
return Value{
|
||||
typecode: v.typecode,
|
||||
value: v.value,
|
||||
}
|
||||
}
|
||||
|
||||
func (v Value) Interface() interface{}
|
||||
func (v Value) Interface() interface{} {
|
||||
i := interfaceHeader{
|
||||
typecode: v.typecode,
|
||||
value: v.value,
|
||||
}
|
||||
if v.indirect && v.Type().Size() <= unsafe.Sizeof(uintptr(0)) {
|
||||
// Value was indirect but must be put back directly in the interface
|
||||
// value.
|
||||
var value uintptr
|
||||
for j := v.Type().Size(); j != 0; j-- {
|
||||
value = (value << 8) | uintptr(*(*uint8)(unsafe.Pointer(uintptr(v.value) + j - 1)))
|
||||
}
|
||||
i.value = unsafe.Pointer(value)
|
||||
}
|
||||
return *(*interface{})(unsafe.Pointer(&i))
|
||||
}
|
||||
|
||||
func (v Value) Type() Type {
|
||||
return v.typecode
|
||||
}
|
||||
|
||||
func (v Value) Kind() Kind {
|
||||
return Invalid // TODO
|
||||
return v.Type().Kind()
|
||||
}
|
||||
|
||||
func (v Value) IsNil() bool {
|
||||
panic("unimplemented: (reflect.Value).IsNil()")
|
||||
switch v.Kind() {
|
||||
case Chan, Map, Ptr:
|
||||
return v.value == nil
|
||||
case Func:
|
||||
if v.value == nil {
|
||||
return true
|
||||
}
|
||||
fn := (*funcHeader)(v.value)
|
||||
return fn.Code == nil
|
||||
case Slice:
|
||||
if v.value == nil {
|
||||
return true
|
||||
}
|
||||
slice := (*SliceHeader)(v.value)
|
||||
return slice.Data == 0
|
||||
case Interface:
|
||||
if v.value == nil {
|
||||
return true
|
||||
}
|
||||
itf := (*interfaceHeader)(v.value)
|
||||
return itf.value == nil
|
||||
default:
|
||||
panic(&ValueError{"IsNil"})
|
||||
}
|
||||
}
|
||||
|
||||
func (v Value) Pointer() uintptr {
|
||||
panic("unimplemented: (reflect.Value).Pointer()")
|
||||
switch v.Kind() {
|
||||
case Chan, Map, Ptr, UnsafePointer:
|
||||
return uintptr(v.value)
|
||||
case Slice:
|
||||
slice := (*SliceHeader)(v.value)
|
||||
return slice.Data
|
||||
case Func:
|
||||
panic("unimplemented: (reflect.Value).Pointer()")
|
||||
default: // not implemented: Func
|
||||
panic(&ValueError{"Pointer"})
|
||||
}
|
||||
}
|
||||
|
||||
func (v Value) IsValid() bool {
|
||||
@@ -44,7 +96,8 @@ func (v Value) IsValid() bool {
|
||||
}
|
||||
|
||||
func (v Value) CanInterface() bool {
|
||||
panic("unimplemented: (reflect.Value).CanInterface()")
|
||||
// No Value types of private data can be constructed at the moment.
|
||||
return true
|
||||
}
|
||||
|
||||
func (v Value) CanAddr() bool {
|
||||
@@ -56,31 +109,160 @@ func (v Value) Addr() Value {
|
||||
}
|
||||
|
||||
func (v Value) CanSet() bool {
|
||||
panic("unimplemented: (reflect.Value).CanSet()")
|
||||
return v.indirect
|
||||
}
|
||||
|
||||
func (v Value) Bool() bool {
|
||||
panic("unimplemented: (reflect.Value).Bool()")
|
||||
switch v.Kind() {
|
||||
case Bool:
|
||||
if v.indirect {
|
||||
return *((*bool)(v.value))
|
||||
} else {
|
||||
return uintptr(v.value) != 0
|
||||
}
|
||||
default:
|
||||
panic(&ValueError{"Bool"})
|
||||
}
|
||||
}
|
||||
|
||||
func (v Value) Int() int64 {
|
||||
panic("unimplemented: (reflect.Value).Int()")
|
||||
switch v.Kind() {
|
||||
case Int:
|
||||
if v.indirect || unsafe.Sizeof(int(0)) > unsafe.Sizeof(uintptr(0)) {
|
||||
return int64(*(*int)(v.value))
|
||||
} else {
|
||||
return int64(int(uintptr(v.value)))
|
||||
}
|
||||
case Int8:
|
||||
if v.indirect {
|
||||
return int64(*(*int8)(v.value))
|
||||
} else {
|
||||
return int64(int8(uintptr(v.value)))
|
||||
}
|
||||
case Int16:
|
||||
if v.indirect {
|
||||
return int64(*(*int16)(v.value))
|
||||
} else {
|
||||
return int64(int16(uintptr(v.value)))
|
||||
}
|
||||
case Int32:
|
||||
if v.indirect || unsafe.Sizeof(int32(0)) > unsafe.Sizeof(uintptr(0)) {
|
||||
return int64(*(*int32)(v.value))
|
||||
} else {
|
||||
return int64(int32(uintptr(v.value)))
|
||||
}
|
||||
case Int64:
|
||||
if v.indirect || unsafe.Sizeof(int64(0)) > unsafe.Sizeof(uintptr(0)) {
|
||||
return int64(*(*int64)(v.value))
|
||||
} else {
|
||||
return int64(int64(uintptr(v.value)))
|
||||
}
|
||||
default:
|
||||
panic(&ValueError{"Int"})
|
||||
}
|
||||
}
|
||||
|
||||
func (v Value) Uint() uint64 {
|
||||
panic("unimplemented: (reflect.Value).Uint()")
|
||||
switch v.Kind() {
|
||||
case Uintptr:
|
||||
if v.indirect {
|
||||
return uint64(*(*uintptr)(v.value))
|
||||
} else {
|
||||
return uint64(uintptr(v.value))
|
||||
}
|
||||
case Uint8:
|
||||
if v.indirect {
|
||||
return uint64(*(*uint8)(v.value))
|
||||
} else {
|
||||
return uint64(uintptr(v.value))
|
||||
}
|
||||
case Uint16:
|
||||
if v.indirect {
|
||||
return uint64(*(*uint16)(v.value))
|
||||
} else {
|
||||
return uint64(uintptr(v.value))
|
||||
}
|
||||
case Uint:
|
||||
if v.indirect || unsafe.Sizeof(uint(0)) > unsafe.Sizeof(uintptr(0)) {
|
||||
return uint64(*(*uint)(v.value))
|
||||
} else {
|
||||
return uint64(uintptr(v.value))
|
||||
}
|
||||
case Uint32:
|
||||
if v.indirect || unsafe.Sizeof(uint32(0)) > unsafe.Sizeof(uintptr(0)) {
|
||||
return uint64(*(*uint32)(v.value))
|
||||
} else {
|
||||
return uint64(uintptr(v.value))
|
||||
}
|
||||
case Uint64:
|
||||
if v.indirect || unsafe.Sizeof(uint64(0)) > unsafe.Sizeof(uintptr(0)) {
|
||||
return uint64(*(*uint64)(v.value))
|
||||
} else {
|
||||
return uint64(uintptr(v.value))
|
||||
}
|
||||
default:
|
||||
panic(&ValueError{"Uint"})
|
||||
}
|
||||
}
|
||||
|
||||
func (v Value) Float() float64 {
|
||||
panic("unimplemented: (reflect.Value).Float()")
|
||||
switch v.Kind() {
|
||||
case Float32:
|
||||
if v.indirect || unsafe.Sizeof(float32(0)) > unsafe.Sizeof(uintptr(0)) {
|
||||
// The float is stored as an external value on systems with 16-bit
|
||||
// pointers.
|
||||
return float64(*(*float32)(v.value))
|
||||
} else {
|
||||
// The float is directly stored in the interface value on systems
|
||||
// with 32-bit and 64-bit pointers.
|
||||
return float64(*(*float32)(unsafe.Pointer(&v.value)))
|
||||
}
|
||||
case Float64:
|
||||
if v.indirect || unsafe.Sizeof(float64(0)) > unsafe.Sizeof(uintptr(0)) {
|
||||
// For systems with 16-bit and 32-bit pointers.
|
||||
return *(*float64)(v.value)
|
||||
} else {
|
||||
// The float is directly stored in the interface value on systems
|
||||
// with 64-bit pointers.
|
||||
return *(*float64)(unsafe.Pointer(&v.value))
|
||||
}
|
||||
default:
|
||||
panic(&ValueError{"Float"})
|
||||
}
|
||||
}
|
||||
|
||||
func (v Value) Complex() complex128 {
|
||||
panic("unimplemented: (reflect.Value).Complex()")
|
||||
switch v.Kind() {
|
||||
case Complex64:
|
||||
if v.indirect || unsafe.Sizeof(complex64(0)) > unsafe.Sizeof(uintptr(0)) {
|
||||
// The complex number is stored as an external value on systems with
|
||||
// 16-bit and 32-bit pointers.
|
||||
return complex128(*(*complex64)(v.value))
|
||||
} else {
|
||||
// The complex number is directly stored in the interface value on
|
||||
// systems with 64-bit pointers.
|
||||
return complex128(*(*complex64)(unsafe.Pointer(&v.value)))
|
||||
}
|
||||
case Complex128:
|
||||
// This is a 128-bit value, which is always stored as an external value.
|
||||
// It may be stored in the pointer directly on very uncommon
|
||||
// architectures with 128-bit pointers, however.
|
||||
return *(*complex128)(v.value)
|
||||
default:
|
||||
panic(&ValueError{"Complex"})
|
||||
}
|
||||
}
|
||||
|
||||
func (v Value) String() string {
|
||||
panic("unimplemented: (reflect.Value).String()")
|
||||
switch v.Kind() {
|
||||
case String:
|
||||
// A string value is always bigger than a pointer as it is made of a
|
||||
// pointer and a length.
|
||||
return *(*string)(v.value)
|
||||
default:
|
||||
// Special case because of the special treatment of .String() in Go.
|
||||
return "<T>"
|
||||
}
|
||||
}
|
||||
|
||||
func (v Value) Bytes() []byte {
|
||||
@@ -92,7 +274,25 @@ func (v Value) Slice(i, j int) Value {
|
||||
}
|
||||
|
||||
func (v Value) Len() int {
|
||||
panic("unimplemented: (reflect.Value).Len()")
|
||||
t := v.Type()
|
||||
switch t.Kind() {
|
||||
case Slice:
|
||||
return int((*SliceHeader)(v.value).Len)
|
||||
case String:
|
||||
return int((*StringHeader)(v.value).Len)
|
||||
default: // Array, Chan, Map
|
||||
panic("unimplemented: (reflect.Value).Len()")
|
||||
}
|
||||
}
|
||||
|
||||
func (v Value) Cap() int {
|
||||
t := v.Type()
|
||||
switch t.Kind() {
|
||||
case Slice:
|
||||
return int((*SliceHeader)(v.value).Cap)
|
||||
default: // Array, Chan
|
||||
panic("unimplemented: (reflect.Value).Cap()")
|
||||
}
|
||||
}
|
||||
|
||||
func (v Value) NumField() int {
|
||||
@@ -100,7 +300,23 @@ func (v Value) NumField() int {
|
||||
}
|
||||
|
||||
func (v Value) Elem() Value {
|
||||
panic("unimplemented: (reflect.Value).Elem()")
|
||||
switch v.Kind() {
|
||||
case Ptr:
|
||||
ptr := v.value
|
||||
if v.indirect {
|
||||
ptr = *(*unsafe.Pointer)(ptr)
|
||||
}
|
||||
if ptr == nil {
|
||||
return Value{}
|
||||
}
|
||||
return Value{
|
||||
typecode: v.Type().Elem(),
|
||||
value: ptr,
|
||||
indirect: true,
|
||||
}
|
||||
default: // not implemented: Interface
|
||||
panic(&ValueError{"Elem"})
|
||||
}
|
||||
}
|
||||
|
||||
func (v Value) Field(i int) Value {
|
||||
@@ -108,7 +324,37 @@ func (v Value) Field(i int) Value {
|
||||
}
|
||||
|
||||
func (v Value) Index(i int) Value {
|
||||
panic("unimplemented: (reflect.Value).Index()")
|
||||
switch v.Kind() {
|
||||
case Slice:
|
||||
// Extract an element from the slice.
|
||||
slice := *(*SliceHeader)(v.value)
|
||||
if uint(i) >= uint(slice.Len) {
|
||||
panic("reflect: slice index out of range")
|
||||
}
|
||||
elem := Value{
|
||||
typecode: v.Type().Elem(),
|
||||
indirect: true,
|
||||
}
|
||||
addr := uintptr(slice.Data) + elem.Type().Size() * uintptr(i) // pointer to new value
|
||||
elem.value = unsafe.Pointer(addr)
|
||||
return elem
|
||||
case String:
|
||||
// Extract a character from a string.
|
||||
// A string is never stored directly in the interface, but always as a
|
||||
// pointer to the string value.
|
||||
s := *(*StringHeader)(v.value)
|
||||
if uint(i) >= uint(s.Len) {
|
||||
panic("reflect: string index out of range")
|
||||
}
|
||||
return Value{
|
||||
typecode: Uint8.basicType(),
|
||||
value: unsafe.Pointer(uintptr(*(*uint8)(unsafe.Pointer(s.Data + uintptr(i))))),
|
||||
}
|
||||
case Array:
|
||||
panic("unimplemented: (reflect.Value).Index()")
|
||||
default:
|
||||
panic(&ValueError{"Index"})
|
||||
}
|
||||
}
|
||||
|
||||
func (v Value) MapKeys() []Value {
|
||||
@@ -120,33 +366,152 @@ func (v Value) MapIndex(key Value) Value {
|
||||
}
|
||||
|
||||
func (v Value) Set(x Value) {
|
||||
panic("unimplemented: (reflect.Value).Set()")
|
||||
if !v.indirect {
|
||||
panic("reflect: value is not addressable")
|
||||
}
|
||||
if v.Type() != x.Type() {
|
||||
if v.Kind() == Interface {
|
||||
panic("reflect: unimplemented: assigning to interface of different type")
|
||||
} else {
|
||||
panic("reflect: cannot assign")
|
||||
}
|
||||
}
|
||||
size := v.Type().Size()
|
||||
xptr := x.value
|
||||
if size <= unsafe.Sizeof(uintptr(0)) && !x.indirect {
|
||||
value := x.value
|
||||
xptr = unsafe.Pointer(&value)
|
||||
}
|
||||
memcpy(v.value, xptr, size)
|
||||
}
|
||||
|
||||
func (v Value) SetBool(x bool) {
|
||||
panic("unimplemented: (reflect.Value).SetBool()")
|
||||
if !v.indirect {
|
||||
panic("reflect: value is not addressable")
|
||||
}
|
||||
switch v.Kind() {
|
||||
case Bool:
|
||||
*(*bool)(v.value) = x
|
||||
default:
|
||||
panic(&ValueError{"SetBool"})
|
||||
}
|
||||
}
|
||||
|
||||
func (v Value) SetInt(x int64) {
|
||||
panic("unimplemented: (reflect.Value).SetInt()")
|
||||
if !v.indirect {
|
||||
panic("reflect: value is not addressable")
|
||||
}
|
||||
switch v.Kind() {
|
||||
case Int:
|
||||
*(*int)(v.value) = int(x)
|
||||
case Int8:
|
||||
*(*int8)(v.value) = int8(x)
|
||||
case Int16:
|
||||
*(*int16)(v.value) = int16(x)
|
||||
case Int32:
|
||||
*(*int32)(v.value) = int32(x)
|
||||
case Int64:
|
||||
*(*int64)(v.value) = x
|
||||
default:
|
||||
panic(&ValueError{"SetInt"})
|
||||
}
|
||||
}
|
||||
|
||||
func (v Value) SetUint(x uint64) {
|
||||
panic("unimplemented: (reflect.Value).SetUint()")
|
||||
if !v.indirect {
|
||||
panic("reflect: value is not addressable")
|
||||
}
|
||||
switch v.Kind() {
|
||||
case Uint:
|
||||
*(*uint)(v.value) = uint(x)
|
||||
case Uint8:
|
||||
*(*uint8)(v.value) = uint8(x)
|
||||
case Uint16:
|
||||
*(*uint16)(v.value) = uint16(x)
|
||||
case Uint32:
|
||||
*(*uint32)(v.value) = uint32(x)
|
||||
case Uint64:
|
||||
*(*uint64)(v.value) = x
|
||||
case Uintptr:
|
||||
*(*uintptr)(v.value) = uintptr(x)
|
||||
default:
|
||||
panic(&ValueError{"SetUint"})
|
||||
}
|
||||
}
|
||||
|
||||
func (v Value) SetFloat(x float64) {
|
||||
panic("unimplemented: (reflect.Value).SetFloat()")
|
||||
if !v.indirect {
|
||||
panic("reflect: value is not addressable")
|
||||
}
|
||||
switch v.Kind() {
|
||||
case Float32:
|
||||
*(*float32)(v.value) = float32(x)
|
||||
case Float64:
|
||||
*(*float64)(v.value) = x
|
||||
default:
|
||||
panic(&ValueError{"SetFloat"})
|
||||
}
|
||||
}
|
||||
|
||||
func (v Value) SetComplex(x complex128) {
|
||||
panic("unimplemented: (reflect.Value).SetComplex()")
|
||||
if !v.indirect {
|
||||
panic("reflect: value is not addressable")
|
||||
}
|
||||
switch v.Kind() {
|
||||
case Complex64:
|
||||
*(*complex64)(v.value) = complex64(x)
|
||||
case Complex128:
|
||||
*(*complex128)(v.value) = x
|
||||
default:
|
||||
panic(&ValueError{"SetComplex"})
|
||||
}
|
||||
}
|
||||
|
||||
func (v Value) SetString(x string) {
|
||||
panic("unimplemented: (reflect.Value).SetString()")
|
||||
if !v.indirect {
|
||||
panic("reflect: value is not addressable")
|
||||
}
|
||||
switch v.Kind() {
|
||||
case String:
|
||||
*(*string)(v.value) = x
|
||||
default:
|
||||
panic(&ValueError{"SetString"})
|
||||
}
|
||||
}
|
||||
|
||||
func MakeSlice(typ Type, len, cap int) Value {
|
||||
panic("unimplemented: reflect.MakeSlice()")
|
||||
}
|
||||
|
||||
type funcHeader struct {
|
||||
Context unsafe.Pointer
|
||||
Code unsafe.Pointer
|
||||
}
|
||||
|
||||
// This is the same thing as an interface{}.
|
||||
type interfaceHeader struct {
|
||||
typecode Type
|
||||
value unsafe.Pointer
|
||||
}
|
||||
|
||||
type SliceHeader struct {
|
||||
Data uintptr
|
||||
Len uintptr
|
||||
Cap uintptr
|
||||
}
|
||||
|
||||
type StringHeader struct {
|
||||
Data uintptr
|
||||
Len uintptr
|
||||
}
|
||||
|
||||
type ValueError struct {
|
||||
Method string
|
||||
}
|
||||
|
||||
func (e *ValueError) Error() string {
|
||||
return "reflect: call of reflect.Value." + e.Method + " on invalid type"
|
||||
}
|
||||
|
||||
//go:linkname memcpy runtime.memcpy
|
||||
func memcpy(dst, src unsafe.Pointer, size uintptr)
|
||||
|
||||
@@ -5,7 +5,9 @@ const GOARCH = "amd64"
|
||||
// The bitness of the CPU (e.g. 8, 32, 64).
|
||||
const TargetBits = 64
|
||||
|
||||
// Align on word boundary.
|
||||
// Align a pointer.
|
||||
// Note that some amd64 instructions (like movaps) expect 16-byte aligned
|
||||
// memory, thus the result must be 16-byte aligned.
|
||||
func align(ptr uintptr) uintptr {
|
||||
return (ptr + 7) &^ 7
|
||||
return (ptr + 15) &^ 15
|
||||
}
|
||||
|
||||
+146
-1
@@ -2,7 +2,152 @@ package runtime
|
||||
|
||||
// This file implements the 'chan' type and send/receive/select operations.
|
||||
|
||||
// dummy
|
||||
// A channel can be in one of the following states:
|
||||
// empty:
|
||||
// No goroutine is waiting on a send or receive operation. The 'blocked'
|
||||
// member is nil.
|
||||
// recv:
|
||||
// A goroutine tries to receive from the channel. This goroutine is stored
|
||||
// in the 'blocked' member.
|
||||
// send:
|
||||
// The reverse of send. A goroutine tries to send to the channel. This
|
||||
// goroutine is stored in the 'blocked' member.
|
||||
// closed:
|
||||
// The channel is closed. Sends will panic, receives will get a zero value
|
||||
// plus optionally the indication that the channel is zero (with the
|
||||
// commao-ok value in the coroutine).
|
||||
//
|
||||
// A send/recv transmission is completed by copying from the data element of the
|
||||
// sending coroutine to the data element of the receiving coroutine, and setting
|
||||
// the 'comma-ok' value to true.
|
||||
// A receive operation on a closed channel is completed by zeroing the data
|
||||
// element of the receiving coroutine and setting the 'comma-ok' value to false.
|
||||
|
||||
import (
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
type channel struct {
|
||||
state uint8
|
||||
blocked *coroutine
|
||||
}
|
||||
|
||||
const (
|
||||
chanStateEmpty = iota
|
||||
chanStateRecv
|
||||
chanStateSend
|
||||
chanStateClosed
|
||||
)
|
||||
|
||||
func chanSendStub(caller *coroutine, ch *channel, _ unsafe.Pointer, size uintptr)
|
||||
func chanRecvStub(caller *coroutine, ch *channel, _ unsafe.Pointer, _ *bool, size uintptr)
|
||||
|
||||
// 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.
|
||||
//
|
||||
// The unsafe.Pointer value is used during lowering. During IR generation, it
|
||||
// points to the to-be-received value. During coroutine lowering, this value is
|
||||
// replaced with a read from the coroutine promise.
|
||||
func chanSend(sender *coroutine, ch *channel, _ unsafe.Pointer, size uintptr) {
|
||||
if ch == nil {
|
||||
// A nil channel blocks forever. Do not scheduler this goroutine again.
|
||||
return
|
||||
}
|
||||
switch ch.state {
|
||||
case chanStateEmpty:
|
||||
ch.state = chanStateSend
|
||||
ch.blocked = sender
|
||||
case chanStateRecv:
|
||||
receiver := ch.blocked
|
||||
receiverPromise := receiver.promise()
|
||||
senderPromise := sender.promise()
|
||||
memcpy(unsafe.Pointer(&receiverPromise.data), unsafe.Pointer(&senderPromise.data), size)
|
||||
receiverPromise.commaOk = true
|
||||
ch.blocked = receiverPromise.next
|
||||
receiverPromise.next = nil
|
||||
activateTask(receiver)
|
||||
activateTask(sender)
|
||||
if ch.blocked == nil {
|
||||
ch.state = chanStateEmpty
|
||||
}
|
||||
case chanStateClosed:
|
||||
runtimePanic("send on closed channel")
|
||||
case chanStateSend:
|
||||
sender.promise().next = ch.blocked
|
||||
ch.blocked = sender
|
||||
}
|
||||
}
|
||||
|
||||
// chanRecv receives a single value over a channel. If there is an available
|
||||
// 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.
|
||||
//
|
||||
// The two unnamed values exist to help during lowering. The unsafe.Pointer
|
||||
// points to the value, and the *bool points to the comma-ok value. Both are
|
||||
// replaced by reads from the coroutine promise.
|
||||
func chanRecv(receiver *coroutine, ch *channel, _ unsafe.Pointer, _ *bool, size uintptr) {
|
||||
if ch == nil {
|
||||
// A nil channel blocks forever. Do not scheduler this goroutine again.
|
||||
return
|
||||
}
|
||||
switch ch.state {
|
||||
case chanStateSend:
|
||||
sender := ch.blocked
|
||||
receiverPromise := receiver.promise()
|
||||
senderPromise := sender.promise()
|
||||
memcpy(unsafe.Pointer(&receiverPromise.data), unsafe.Pointer(&senderPromise.data), size)
|
||||
receiverPromise.commaOk = true
|
||||
ch.blocked = senderPromise.next
|
||||
senderPromise.next = nil
|
||||
activateTask(receiver)
|
||||
activateTask(sender)
|
||||
if ch.blocked == nil {
|
||||
ch.state = chanStateEmpty
|
||||
}
|
||||
case chanStateEmpty:
|
||||
ch.state = chanStateRecv
|
||||
ch.blocked = receiver
|
||||
case chanStateClosed:
|
||||
receiverPromise := receiver.promise()
|
||||
memzero(unsafe.Pointer(&receiverPromise.data), size)
|
||||
receiverPromise.commaOk = false
|
||||
activateTask(receiver)
|
||||
case chanStateRecv:
|
||||
receiver.promise().next = ch.blocked
|
||||
ch.blocked = receiver
|
||||
}
|
||||
}
|
||||
|
||||
// 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) {
|
||||
if ch == nil {
|
||||
// Not allowed by the language spec.
|
||||
runtimePanic("close of nil channel")
|
||||
}
|
||||
switch ch.state {
|
||||
case chanStateClosed:
|
||||
// Not allowed by the language spec.
|
||||
runtimePanic("close of closed channel")
|
||||
case chanStateSend:
|
||||
// This panic should ideally on the sending side, not in this goroutine.
|
||||
// But when a goroutine tries to send while the channel is being closed,
|
||||
// that is clearly invalid: the send should have been completed already
|
||||
// before the close.
|
||||
runtimePanic("close channel during send")
|
||||
case chanStateRecv:
|
||||
// The receiver must be re-activated with a zero value.
|
||||
receiverPromise := ch.blocked.promise()
|
||||
memzero(unsafe.Pointer(&receiverPromise.data), size)
|
||||
receiverPromise.commaOk = false
|
||||
activateTask(ch.blocked)
|
||||
ch.state = chanStateClosed
|
||||
ch.blocked = nil
|
||||
case chanStateEmpty:
|
||||
// Easy case. No available sender or receiver.
|
||||
ch.state = chanStateClosed
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,9 +5,11 @@ package runtime
|
||||
// Interfaces are represented as a pair of {typecode, value}, where value can be
|
||||
// anything (including non-pointers).
|
||||
|
||||
import "unsafe"
|
||||
|
||||
type _interface struct {
|
||||
typecode uintptr
|
||||
value *uint8
|
||||
value unsafe.Pointer
|
||||
}
|
||||
|
||||
// Return true iff both interfaces are equal.
|
||||
|
||||
@@ -0,0 +1,224 @@
|
||||
package runtime
|
||||
|
||||
// This file redirects math stubs to their fallback implementation.
|
||||
// TODO: use optimized versions if possible.
|
||||
|
||||
import (
|
||||
_ "unsafe"
|
||||
)
|
||||
|
||||
//go:linkname math_Asin math.Asin
|
||||
func math_Asin(x float64) float64 { return math_asin(x) }
|
||||
|
||||
//go:linkname math_asin math.asin
|
||||
func math_asin(x float64) float64
|
||||
|
||||
//go:linkname math_Asinh math.Asinh
|
||||
func math_Asinh(x float64) float64 { return math_asinh(x) }
|
||||
|
||||
//go:linkname math_asinh math.asinh
|
||||
func math_asinh(x float64) float64
|
||||
|
||||
//go:linkname math_Acos math.Acos
|
||||
func math_Acos(x float64) float64 { return math_acos(x) }
|
||||
|
||||
//go:linkname math_acos math.acos
|
||||
func math_acos(x float64) float64
|
||||
|
||||
//go:linkname math_Acosh math.Acosh
|
||||
func math_Acosh(x float64) float64 { return math_acosh(x) }
|
||||
|
||||
//go:linkname math_acosh math.acosh
|
||||
func math_acosh(x float64) float64
|
||||
|
||||
//go:linkname math_Atan math.Atan
|
||||
func math_Atan(x float64) float64 { return math_atan(x) }
|
||||
|
||||
//go:linkname math_atan math.atan
|
||||
func math_atan(x float64) float64
|
||||
|
||||
//go:linkname math_Atanh math.Atanh
|
||||
func math_Atanh(x float64) float64 { return math_atanh(x) }
|
||||
|
||||
//go:linkname math_atanh math.atanh
|
||||
func math_atanh(x float64) float64
|
||||
|
||||
//go:linkname math_Atan2 math.Atan2
|
||||
func math_Atan2(y, x float64) float64 { return math_atan2(y, x) }
|
||||
|
||||
//go:linkname math_atan2 math.atan2
|
||||
func math_atan2(y, x float64) float64
|
||||
|
||||
//go:linkname math_Cbrt math.Cbrt
|
||||
func math_Cbrt(x float64) float64 { return math_cbrt(x) }
|
||||
|
||||
//go:linkname math_cbrt math.cbrt
|
||||
func math_cbrt(x float64) float64
|
||||
|
||||
//go:linkname math_Ceil math.Ceil
|
||||
func math_Ceil(x float64) float64 { return math_ceil(x) }
|
||||
|
||||
//go:linkname math_ceil math.ceil
|
||||
func math_ceil(x float64) float64
|
||||
|
||||
//go:linkname math_Cos math.Cos
|
||||
func math_Cos(x float64) float64 { return math_cos(x) }
|
||||
|
||||
//go:linkname math_cos math.cos
|
||||
func math_cos(x float64) float64
|
||||
|
||||
//go:linkname math_Cosh math.Cosh
|
||||
func math_Cosh(x float64) float64 { return math_cosh(x) }
|
||||
|
||||
//go:linkname math_cosh math.cosh
|
||||
func math_cosh(x float64) float64
|
||||
|
||||
//go:linkname math_Erf math.Erf
|
||||
func math_Erf(x float64) float64 { return math_erf(x) }
|
||||
|
||||
//go:linkname math_erf math.erf
|
||||
func math_erf(x float64) float64
|
||||
|
||||
//go:linkname math_Erfc math.Erfc
|
||||
func math_Erfc(x float64) float64 { return math_erfc(x) }
|
||||
|
||||
//go:linkname math_erfc math.erfc
|
||||
func math_erfc(x float64) float64
|
||||
|
||||
//go:linkname math_Exp math.Exp
|
||||
func math_Exp(x float64) float64 { return math_exp(x) }
|
||||
|
||||
//go:linkname math_exp math.exp
|
||||
func math_exp(x float64) float64
|
||||
|
||||
//go:linkname math_Expm1 math.Expm1
|
||||
func math_Expm1(x float64) float64 { return math_expm1(x) }
|
||||
|
||||
//go:linkname math_expm1 math.expm1
|
||||
func math_expm1(x float64) float64
|
||||
|
||||
//go:linkname math_Exp2 math.Exp2
|
||||
func math_Exp2(x float64) float64 { return math_exp2(x) }
|
||||
|
||||
//go:linkname math_exp2 math.exp2
|
||||
func math_exp2(x float64) float64
|
||||
|
||||
//go:linkname math_Floor math.Floor
|
||||
func math_Floor(x float64) float64 { return math_floor(x) }
|
||||
|
||||
//go:linkname math_floor math.floor
|
||||
func math_floor(x float64) float64
|
||||
|
||||
//go:linkname math_Frexp math.Frexp
|
||||
func math_Frexp(x float64) (float64, int) { return math_frexp(x) }
|
||||
|
||||
//go:linkname math_frexp math.frexp
|
||||
func math_frexp(x float64) (float64, int)
|
||||
|
||||
//go:linkname math_Hypot math.Hypot
|
||||
func math_Hypot(p, q float64) float64 { return math_hypot(p, q) }
|
||||
|
||||
//go:linkname math_hypot math.hypot
|
||||
func math_hypot(p, q float64) float64
|
||||
|
||||
//go:linkname math_Ldexp math.Ldexp
|
||||
func math_Ldexp(frac float64, exp int) float64 { return math_ldexp(frac, exp) }
|
||||
|
||||
//go:linkname math_ldexp math.ldexp
|
||||
func math_ldexp(frac float64, exp int) float64
|
||||
|
||||
//go:linkname math_Log math.Log
|
||||
func math_Log(x float64) float64 { return math_log(x) }
|
||||
|
||||
//go:linkname math_log math.log
|
||||
func math_log(x float64) float64
|
||||
|
||||
//go:linkname math_Log1p math.Log1p
|
||||
func math_Log1p(x float64) float64 { return math_log1p(x) }
|
||||
|
||||
//go:linkname math_log1p math.log1p
|
||||
func math_log1p(x float64) float64
|
||||
|
||||
//go:linkname math_Log10 math.Log10
|
||||
func math_Log10(x float64) float64 { return math_log10(x) }
|
||||
|
||||
//go:linkname math_log10 math.log10
|
||||
func math_log10(x float64) float64
|
||||
|
||||
//go:linkname math_Log2 math.Log2
|
||||
func math_Log2(x float64) float64 { return math_log2(x) }
|
||||
|
||||
//go:linkname math_log2 math.log2
|
||||
func math_log2(x float64) float64
|
||||
|
||||
//go:linkname math_Max math.Max
|
||||
func math_Max(x, y float64) float64 { return math_max(x, y) }
|
||||
|
||||
//go:linkname math_max math.max
|
||||
func math_max(x, y float64) float64
|
||||
|
||||
//go:linkname math_Min math.Min
|
||||
func math_Min(x, y float64) float64 { return math_min(x, y) }
|
||||
|
||||
//go:linkname math_min math.min
|
||||
func math_min(x, y float64) float64
|
||||
|
||||
//go:linkname math_Mod math.Mod
|
||||
func math_Mod(x, y float64) float64 { return math_mod(x, y) }
|
||||
|
||||
//go:linkname math_mod math.mod
|
||||
func math_mod(x, y float64) float64
|
||||
|
||||
//go:linkname math_Modf math.Modf
|
||||
func math_Modf(x float64) (float64, float64) { return math_modf(x) }
|
||||
|
||||
//go:linkname math_modf math.modf
|
||||
func math_modf(x float64) (float64, float64)
|
||||
|
||||
//go:linkname math_Pow math.Pow
|
||||
func math_Pow(x, y float64) float64 { return math_pow(x, y) }
|
||||
|
||||
//go:linkname math_pow math.pow
|
||||
func math_pow(x, y float64) float64
|
||||
|
||||
//go:linkname math_Remainder math.Remainder
|
||||
func math_Remainder(x, y float64) float64 { return math_remainder(x, y) }
|
||||
|
||||
//go:linkname math_remainder math.remainder
|
||||
func math_remainder(x, y float64) float64
|
||||
|
||||
//go:linkname math_Sin math.Sin
|
||||
func math_Sin(x float64) float64 { return math_sin(x) }
|
||||
|
||||
//go:linkname math_sin math.sin
|
||||
func math_sin(x float64) float64
|
||||
|
||||
//go:linkname math_Sinh math.Sinh
|
||||
func math_Sinh(x float64) float64 { return math_sinh(x) }
|
||||
|
||||
//go:linkname math_sinh math.sinh
|
||||
func math_sinh(x float64) float64
|
||||
|
||||
//go:linkname math_Sqrt math.Sqrt
|
||||
func math_Sqrt(x float64) float64 { return math_sqrt(x) }
|
||||
|
||||
//go:linkname math_sqrt math.sqrt
|
||||
func math_sqrt(x float64) float64
|
||||
|
||||
//go:linkname math_Tan math.Tan
|
||||
func math_Tan(x float64) float64 { return math_tan(x) }
|
||||
|
||||
//go:linkname math_tan math.tan
|
||||
func math_tan(x float64) float64
|
||||
|
||||
//go:linkname math_Tanh math.Tanh
|
||||
func math_Tanh(x float64) float64 { return math_tanh(x) }
|
||||
|
||||
//go:linkname math_tanh math.tanh
|
||||
func math_tanh(x float64) float64
|
||||
|
||||
//go:linkname math_Trunc math.Trunc
|
||||
func math_Trunc(x float64) float64 { return math_trunc(x) }
|
||||
|
||||
//go:linkname math_trunc math.trunc
|
||||
func math_trunc(x float64) float64
|
||||
@@ -4,14 +4,14 @@ import (
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
const Compiler = "tgo"
|
||||
const Compiler = "tinygo"
|
||||
|
||||
// The compiler will fill this with calls to the initialization function of each
|
||||
// package.
|
||||
func initAll()
|
||||
|
||||
// The compiler will insert the call to main.main() here, depending on whether
|
||||
// the scheduler is necessary.
|
||||
// A function call to this function is replaced withone of the following,
|
||||
// depending on whether the scheduler is necessary:
|
||||
//
|
||||
// Without scheduler:
|
||||
//
|
||||
@@ -19,9 +19,9 @@ func initAll()
|
||||
//
|
||||
// With scheduler:
|
||||
//
|
||||
// coroutine := main.main(nil)
|
||||
// scheduler(coroutine)
|
||||
func mainWrapper()
|
||||
// main.main()
|
||||
// scheduler()
|
||||
func callMain()
|
||||
|
||||
func GOMAXPROCS(n int) int {
|
||||
// Note: setting GOMAXPROCS is ignored.
|
||||
|
||||
@@ -0,0 +1,337 @@
|
||||
// +build sam,atsamd21g18a
|
||||
|
||||
package runtime
|
||||
|
||||
import (
|
||||
"device/arm"
|
||||
"device/sam"
|
||||
"machine"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
type timeUnit int64
|
||||
|
||||
//go:export Reset_Handler
|
||||
func main() {
|
||||
preinit()
|
||||
initAll()
|
||||
callMain()
|
||||
abort()
|
||||
}
|
||||
|
||||
func init() {
|
||||
initClocks()
|
||||
initRTC()
|
||||
initUARTClock()
|
||||
initI2CClock()
|
||||
|
||||
// connect to UART
|
||||
machine.UART0.Configure(machine.UARTConfig{})
|
||||
}
|
||||
|
||||
func putchar(c byte) {
|
||||
machine.UART0.WriteByte(c)
|
||||
}
|
||||
|
||||
func initClocks() {
|
||||
// Set 1 Flash Wait State for 48MHz, required for 3.3V operation according to SAMD21 Datasheet
|
||||
sam.NVMCTRL.CTRLB |= (sam.NVMCTRL_CTRLB_RWS_HALF << sam.NVMCTRL_CTRLB_RWS_Pos)
|
||||
|
||||
// Turn on the digital interface clock
|
||||
sam.PM.APBAMASK |= sam.PM_APBAMASK_GCLK_
|
||||
// turn off RTC
|
||||
sam.PM.APBAMASK &^= sam.PM_APBAMASK_RTC_
|
||||
|
||||
// Enable OSC32K clock (Internal 32.768Hz oscillator).
|
||||
// This requires registers that are not included in the SVD file.
|
||||
// This is from samd21g18a.h and nvmctrl.h:
|
||||
//
|
||||
// #define NVMCTRL_OTP4 0x00806020
|
||||
//
|
||||
// #define SYSCTRL_FUSES_OSC32K_CAL_ADDR (NVMCTRL_OTP4 + 4)
|
||||
// #define SYSCTRL_FUSES_OSC32K_CAL_Pos 6 /** (NVMCTRL_OTP4) OSC32K Calibration */
|
||||
// #define SYSCTRL_FUSES_OSC32K_CAL_Msk (0x7Fu << SYSCTRL_FUSES_OSC32K_CAL_Pos)
|
||||
// #define SYSCTRL_FUSES_OSC32K_CAL(value) ((SYSCTRL_FUSES_OSC32K_CAL_Msk & ((value) << SYSCTRL_FUSES_OSC32K_CAL_Pos)))
|
||||
// u32_t fuse = *(u32_t *)FUSES_OSC32K_CAL_ADDR;
|
||||
// u32_t calib = (fuse & FUSES_OSC32K_CAL_Msk) >> FUSES_OSC32K_CAL_Pos;
|
||||
fuse := *(*uint32)(unsafe.Pointer(uintptr(0x00806020) + 4))
|
||||
calib := (fuse & uint32(0x7f<<6)) >> 6
|
||||
|
||||
// SYSCTRL_OSC32K_CALIB(calib) |
|
||||
// SYSCTRL_OSC32K_STARTUP(0x6u) |
|
||||
// SYSCTRL_OSC32K_EN32K | SYSCTRL_OSC32K_ENABLE;
|
||||
sam.SYSCTRL.OSC32K = sam.RegValue((calib << sam.SYSCTRL_OSC32K_CALIB_Pos) |
|
||||
(0x6 << sam.SYSCTRL_OSC32K_STARTUP_Pos) |
|
||||
sam.SYSCTRL_OSC32K_EN32K |
|
||||
sam.SYSCTRL_OSC32K_EN1K |
|
||||
sam.SYSCTRL_OSC32K_ENABLE)
|
||||
// Wait for oscillator stabilization
|
||||
for (sam.SYSCTRL.PCLKSR & sam.SYSCTRL_PCLKSR_OSC32KRDY) == 0 {
|
||||
}
|
||||
|
||||
// Software reset the module to ensure it is re-initialized correctly
|
||||
sam.GCLK.CTRL = sam.GCLK_CTRL_SWRST
|
||||
// Wait for reset to complete
|
||||
for (sam.GCLK.CTRL&sam.GCLK_CTRL_SWRST) > 0 && (sam.GCLK.STATUS&sam.GCLK_STATUS_SYNCBUSY) > 0 {
|
||||
}
|
||||
|
||||
// Put OSC32K as source of Generic Clock Generator 1
|
||||
sam.GCLK.GENDIV = sam.RegValue((1 << sam.GCLK_GENDIV_ID_Pos) |
|
||||
(0 << sam.GCLK_GENDIV_DIV_Pos))
|
||||
waitForSync()
|
||||
|
||||
// GCLK_GENCTRL_ID(1) | GCLK_GENCTRL_SRC_OSC32K | GCLK_GENCTRL_GENEN;
|
||||
sam.GCLK.GENCTRL = sam.RegValue((1 << sam.GCLK_GENCTRL_ID_Pos) |
|
||||
(sam.GCLK_GENCTRL_SRC_OSC32K << sam.GCLK_GENCTRL_SRC_Pos) |
|
||||
sam.GCLK_GENCTRL_GENEN)
|
||||
waitForSync()
|
||||
|
||||
// Use Generic Clock Generator 1 as source for Generic Clock Multiplexer 0 (DFLL48M reference)
|
||||
sam.GCLK.CLKCTRL = sam.RegValue16((sam.GCLK_CLKCTRL_ID_DFLL48 << sam.GCLK_CLKCTRL_ID_Pos) |
|
||||
(sam.GCLK_CLKCTRL_GEN_GCLK1 << sam.GCLK_CLKCTRL_GEN_Pos) |
|
||||
sam.GCLK_CLKCTRL_CLKEN)
|
||||
waitForSync()
|
||||
|
||||
// Remove the OnDemand mode, Bug http://avr32.icgroup.norway.atmel.com/bugzilla/show_bug.cgi?id=9905
|
||||
sam.SYSCTRL.DFLLCTRL = sam.SYSCTRL_DFLLCTRL_ENABLE
|
||||
// Wait for ready
|
||||
for (sam.SYSCTRL.PCLKSR & sam.SYSCTRL_PCLKSR_DFLLRDY) == 0 {
|
||||
}
|
||||
|
||||
// Handle DFLL calibration based on info learned from Arduino SAMD implementation,
|
||||
// using value stored in fuse.
|
||||
// #define SYSCTRL_FUSES_DFLL48M_COARSE_CAL_ADDR (NVMCTRL_OTP4 + 4)
|
||||
// #define SYSCTRL_FUSES_DFLL48M_COARSE_CAL_Pos 26 /**< \brief (NVMCTRL_OTP4) DFLL48M Coarse Calibration */
|
||||
// #define SYSCTRL_FUSES_DFLL48M_COARSE_CAL_Msk (0x3Fu << SYSCTRL_FUSES_DFLL48M_COARSE_CAL_Pos)
|
||||
// #define SYSCTRL_FUSES_DFLL48M_COARSE_CAL(value) ((SYSCTRL_FUSES_DFLL48M_COARSE_CAL_Msk & ((value) << SYSCTRL_FUSES_DFLL48M_COARSE_CAL_Pos)))
|
||||
coarse := (fuse >> 26) & 0x3F
|
||||
if coarse == 0x3f {
|
||||
coarse = 0x1f
|
||||
}
|
||||
|
||||
sam.SYSCTRL.DFLLVAL |= sam.RegValue(coarse << sam.SYSCTRL_DFLLVAL_COARSE_Pos)
|
||||
sam.SYSCTRL.DFLLVAL |= (0x1ff << sam.SYSCTRL_DFLLVAL_FINE_Pos)
|
||||
|
||||
// Write full configuration to DFLL control register
|
||||
// SYSCTRL_DFLLMUL_CSTEP( 0x1f / 4 ) | // Coarse step is 31, half of the max value
|
||||
// SYSCTRL_DFLLMUL_FSTEP( 10 ) |
|
||||
// SYSCTRL_DFLLMUL_MUL( (48000) ) ;
|
||||
sam.SYSCTRL.DFLLMUL = sam.RegValue(((31 / 4) << sam.SYSCTRL_DFLLMUL_CSTEP_Pos) |
|
||||
(10 << sam.SYSCTRL_DFLLMUL_FSTEP_Pos) |
|
||||
(48000 << sam.SYSCTRL_DFLLMUL_MUL_Pos))
|
||||
|
||||
// disable DFLL
|
||||
sam.SYSCTRL.DFLLCTRL = 0
|
||||
waitForSync()
|
||||
|
||||
sam.SYSCTRL.DFLLCTRL |= sam.SYSCTRL_DFLLCTRL_MODE |
|
||||
sam.SYSCTRL_DFLLCTRL_CCDIS |
|
||||
sam.SYSCTRL_DFLLCTRL_USBCRM |
|
||||
sam.SYSCTRL_DFLLCTRL_BPLCKC
|
||||
// Wait for ready
|
||||
for (sam.SYSCTRL.PCLKSR & sam.SYSCTRL_PCLKSR_DFLLRDY) == 0 {
|
||||
}
|
||||
|
||||
// Re-enable the DFLL
|
||||
sam.SYSCTRL.DFLLCTRL |= sam.SYSCTRL_DFLLCTRL_ENABLE
|
||||
// Wait for ready
|
||||
for (sam.SYSCTRL.PCLKSR & sam.SYSCTRL_PCLKSR_DFLLRDY) == 0 {
|
||||
}
|
||||
|
||||
// Switch Generic Clock Generator 0 to DFLL48M. CPU will run at 48MHz.
|
||||
sam.GCLK.GENDIV = sam.RegValue((0 << sam.GCLK_GENDIV_ID_Pos) |
|
||||
(0 << sam.GCLK_GENDIV_DIV_Pos))
|
||||
waitForSync()
|
||||
|
||||
sam.GCLK.GENCTRL = sam.RegValue((0 << sam.GCLK_GENCTRL_ID_Pos) |
|
||||
(sam.GCLK_GENCTRL_SRC_DFLL48M << sam.GCLK_GENCTRL_SRC_Pos) |
|
||||
sam.GCLK_GENCTRL_IDC |
|
||||
sam.GCLK_GENCTRL_GENEN)
|
||||
waitForSync()
|
||||
|
||||
// Modify PRESCaler value of OSC8M to have 8MHz
|
||||
sam.SYSCTRL.OSC8M |= (sam.SYSCTRL_OSC8M_PRESC_0 << sam.SYSCTRL_OSC8M_PRESC_Pos)
|
||||
sam.SYSCTRL.OSC8M &^= (1 << sam.SYSCTRL_OSC8M_ONDEMAND_Pos)
|
||||
// Wait for oscillator stabilization
|
||||
for (sam.SYSCTRL.PCLKSR & sam.SYSCTRL_PCLKSR_OSC8MRDY) == 0 {
|
||||
}
|
||||
|
||||
// Use OSC8M as source for Generic Clock Generator 3
|
||||
sam.GCLK.GENDIV = sam.RegValue((3 << sam.GCLK_GENDIV_ID_Pos))
|
||||
waitForSync()
|
||||
|
||||
sam.GCLK.GENCTRL = sam.RegValue((3 << sam.GCLK_GENCTRL_ID_Pos) |
|
||||
(sam.GCLK_GENCTRL_SRC_OSC8M << sam.GCLK_GENCTRL_SRC_Pos) |
|
||||
sam.GCLK_GENCTRL_GENEN)
|
||||
waitForSync()
|
||||
|
||||
// Use OSC32K as source for Generic Clock Generator 2
|
||||
// OSC32K/1 -> GCLK2 at 32KHz
|
||||
sam.GCLK.GENDIV = sam.RegValue(2 << sam.GCLK_GENDIV_ID_Pos)
|
||||
waitForSync()
|
||||
|
||||
sam.GCLK.GENCTRL = sam.RegValue((2 << sam.GCLK_GENCTRL_ID_Pos) |
|
||||
(sam.GCLK_GENCTRL_SRC_OSC32K << sam.GCLK_GENCTRL_SRC_Pos) |
|
||||
sam.GCLK_GENCTRL_GENEN)
|
||||
waitForSync()
|
||||
|
||||
// Use GCLK2 for RTC
|
||||
sam.GCLK.CLKCTRL = sam.RegValue16((sam.GCLK_CLKCTRL_ID_RTC << sam.GCLK_CLKCTRL_ID_Pos) |
|
||||
(sam.GCLK_CLKCTRL_GEN_GCLK2 << sam.GCLK_CLKCTRL_GEN_Pos) |
|
||||
sam.GCLK_CLKCTRL_CLKEN)
|
||||
waitForSync()
|
||||
|
||||
// Set the CPU, APBA, B, and C dividers
|
||||
sam.PM.CPUSEL = sam.PM_CPUSEL_CPUDIV_DIV1
|
||||
sam.PM.APBASEL = sam.PM_APBASEL_APBADIV_DIV1
|
||||
sam.PM.APBBSEL = sam.PM_APBBSEL_APBBDIV_DIV1
|
||||
sam.PM.APBCSEL = sam.PM_APBCSEL_APBCDIV_DIV1
|
||||
|
||||
// Disable automatic NVM write operations
|
||||
sam.NVMCTRL.CTRLB |= sam.NVMCTRL_CTRLB_MANW
|
||||
}
|
||||
|
||||
func initRTC() {
|
||||
// turn on digital interface clock
|
||||
sam.PM.APBAMASK |= sam.PM_APBAMASK_RTC_
|
||||
|
||||
// disable RTC
|
||||
sam.RTC_MODE0.CTRL = 0
|
||||
waitForSync()
|
||||
|
||||
// reset RTC
|
||||
sam.RTC_MODE0.CTRL |= sam.RTC_MODE0_CTRL_SWRST
|
||||
waitForSync()
|
||||
|
||||
// set Mode0 to 32-bit counter (mode 0) with prescaler 1 and GCLK2 is 32KHz/1
|
||||
sam.RTC_MODE0.CTRL = sam.RegValue16((sam.RTC_MODE0_CTRL_MODE_COUNT32 << sam.RTC_MODE0_CTRL_MODE_Pos) |
|
||||
(sam.RTC_MODE0_CTRL_PRESCALER_DIV1 << sam.RTC_MODE0_CTRL_PRESCALER_Pos) |
|
||||
sam.RTC_MODE0_CTRL_MATCHCLR)
|
||||
waitForSync()
|
||||
|
||||
sam.RTC_MODE0.COMP0 = 0xffffffff
|
||||
waitForSync()
|
||||
|
||||
// re-enable RTC
|
||||
sam.RTC_MODE0.CTRL |= sam.RTC_MODE0_CTRL_ENABLE
|
||||
waitForSync()
|
||||
|
||||
arm.SetPriority(sam.IRQ_RTC, 0xc0)
|
||||
arm.EnableIRQ(sam.IRQ_RTC)
|
||||
}
|
||||
|
||||
func waitForSync() {
|
||||
for (sam.GCLK.STATUS & sam.GCLK_STATUS_SYNCBUSY) > 0 {
|
||||
}
|
||||
}
|
||||
|
||||
// treat all ticks params coming from runtime as being in microseconds
|
||||
const tickMicros = 1000
|
||||
|
||||
var (
|
||||
timestamp timeUnit // ticks since boottime
|
||||
timerLastCounter uint64
|
||||
)
|
||||
|
||||
//go:volatile
|
||||
type isrFlag bool
|
||||
|
||||
var timerWakeup isrFlag
|
||||
|
||||
const asyncScheduler = false
|
||||
|
||||
// sleepTicks should sleep for d number of microseconds.
|
||||
func sleepTicks(d timeUnit) {
|
||||
for d != 0 {
|
||||
ticks() // update timestamp
|
||||
ticks := uint32(d)
|
||||
timerSleep(ticks)
|
||||
d -= timeUnit(ticks)
|
||||
}
|
||||
}
|
||||
|
||||
// ticks returns number of microseconds since start.
|
||||
func ticks() timeUnit {
|
||||
// request read of count
|
||||
sam.RTC_MODE0.READREQ = sam.RTC_MODE0_READREQ_RREQ
|
||||
waitForSync()
|
||||
|
||||
rtcCounter := uint64(sam.RTC_MODE0.COUNT) * 30 // each counter tick == 30.5us
|
||||
offset := (rtcCounter - timerLastCounter) // change since last measurement
|
||||
timerLastCounter = rtcCounter
|
||||
timestamp += timeUnit(offset) // TODO: not precise
|
||||
return timestamp
|
||||
}
|
||||
|
||||
// ticks are in microseconds
|
||||
func timerSleep(ticks uint32) {
|
||||
timerWakeup = false
|
||||
if ticks < 30 {
|
||||
// have to have at least one clock count
|
||||
ticks = 30
|
||||
}
|
||||
|
||||
// request read of count
|
||||
sam.RTC_MODE0.READREQ = sam.RTC_MODE0_READREQ_RREQ
|
||||
waitForSync()
|
||||
|
||||
// set compare value
|
||||
cnt := sam.RTC_MODE0.COUNT
|
||||
sam.RTC_MODE0.COMP0 = sam.RegValue(uint32(cnt) + (ticks / 30)) // each counter tick == 30.5us
|
||||
waitForSync()
|
||||
|
||||
// enable IRQ for CMP0 compare
|
||||
sam.RTC_MODE0.INTENSET |= sam.RTC_MODE0_INTENSET_CMP0
|
||||
|
||||
for !timerWakeup {
|
||||
arm.Asm("wfi")
|
||||
}
|
||||
}
|
||||
|
||||
//go:export RTC_IRQHandler
|
||||
func handleRTC() {
|
||||
// disable IRQ for CMP0 compare
|
||||
sam.RTC_MODE0.INTFLAG = sam.RTC_MODE0_INTENSET_CMP0
|
||||
|
||||
timerWakeup = true
|
||||
}
|
||||
|
||||
func initUARTClock() {
|
||||
// Turn on clock to SERCOM0 for UART0
|
||||
sam.PM.APBCMASK |= sam.PM_APBCMASK_SERCOM0_
|
||||
|
||||
// Use GCLK0 for SERCOM0 aka UART0
|
||||
// GCLK_CLKCTRL_ID( clockId ) | // Generic Clock 0 (SERCOMx)
|
||||
// GCLK_CLKCTRL_GEN_GCLK0 | // Generic Clock Generator 0 is source
|
||||
// GCLK_CLKCTRL_CLKEN ;
|
||||
sam.GCLK.CLKCTRL = sam.RegValue16((sam.GCLK_CLKCTRL_ID_SERCOM0_CORE << sam.GCLK_CLKCTRL_ID_Pos) |
|
||||
(sam.GCLK_CLKCTRL_GEN_GCLK0 << sam.GCLK_CLKCTRL_GEN_Pos) |
|
||||
sam.GCLK_CLKCTRL_CLKEN)
|
||||
waitForSync()
|
||||
|
||||
// Turn on clock to SERCOM1 for UART1
|
||||
sam.PM.APBCMASK |= sam.PM_APBCMASK_SERCOM1_
|
||||
|
||||
// Use GCLK0 for SERCOM1 aka UART1
|
||||
// GCLK_CLKCTRL_ID( clockId ) | // Generic Clock 0 (SERCOMx)
|
||||
// GCLK_CLKCTRL_GEN_GCLK0 | // Generic Clock Generator 0 is source
|
||||
// GCLK_CLKCTRL_CLKEN ;
|
||||
sam.GCLK.CLKCTRL = sam.RegValue16((sam.GCLK_CLKCTRL_ID_SERCOM1_CORE << sam.GCLK_CLKCTRL_ID_Pos) |
|
||||
(sam.GCLK_CLKCTRL_GEN_GCLK0 << sam.GCLK_CLKCTRL_GEN_Pos) |
|
||||
sam.GCLK_CLKCTRL_CLKEN)
|
||||
waitForSync()
|
||||
}
|
||||
|
||||
func initI2CClock() {
|
||||
// Turn on clock to SERCOM3 for I2C0
|
||||
sam.PM.APBCMASK |= sam.PM_APBCMASK_SERCOM3_
|
||||
|
||||
// Use GCLK0 for SERCOM3 aka I2C0
|
||||
// GCLK_CLKCTRL_ID( clockId ) | // Generic Clock 0 (SERCOMx)
|
||||
// GCLK_CLKCTRL_GEN_GCLK0 | // Generic Clock Generator 0 is source
|
||||
// GCLK_CLKCTRL_CLKEN ;
|
||||
sam.GCLK.CLKCTRL = sam.RegValue16((sam.GCLK_CLKCTRL_ID_SERCOM3_CORE << sam.GCLK_CLKCTRL_ID_Pos) |
|
||||
(sam.GCLK_CLKCTRL_GEN_GCLK0 << sam.GCLK_CLKCTRL_GEN_Pos) |
|
||||
sam.GCLK_CLKCTRL_CLKEN)
|
||||
waitForSync()
|
||||
}
|
||||
@@ -41,7 +41,7 @@ func main() {
|
||||
preinit()
|
||||
initAll()
|
||||
postinit()
|
||||
mainWrapper()
|
||||
callMain()
|
||||
abort()
|
||||
}
|
||||
|
||||
@@ -71,6 +71,8 @@ func putchar(c byte) {
|
||||
machine.UART0.WriteByte(c)
|
||||
}
|
||||
|
||||
const asyncScheduler = false
|
||||
|
||||
// Sleep this number of ticks of 16ms.
|
||||
//
|
||||
// TODO: not very accurate. Improve accuracy by calibrating on startup and every
|
||||
|
||||
@@ -20,7 +20,7 @@ func main() {
|
||||
systemInit()
|
||||
preinit()
|
||||
initAll()
|
||||
mainWrapper()
|
||||
callMain()
|
||||
abort()
|
||||
}
|
||||
|
||||
@@ -50,6 +50,8 @@ func putchar(c byte) {
|
||||
machine.UART0.WriteByte(c)
|
||||
}
|
||||
|
||||
const asyncScheduler = false
|
||||
|
||||
func sleepTicks(d timeUnit) {
|
||||
for d != 0 {
|
||||
ticks() // update timestamp
|
||||
|
||||
@@ -20,11 +20,13 @@ var timestamp timeUnit
|
||||
func main() {
|
||||
preinit()
|
||||
initAll()
|
||||
mainWrapper()
|
||||
callMain()
|
||||
arm.SemihostingCall(arm.SemihostingReportException, arm.SemihostingApplicationExit)
|
||||
abort()
|
||||
}
|
||||
|
||||
const asyncScheduler = false
|
||||
|
||||
func sleepTicks(d timeUnit) {
|
||||
// TODO: actually sleep here for the given time.
|
||||
timestamp += d
|
||||
|
||||
@@ -8,6 +8,6 @@ type timeUnit int64
|
||||
func main() {
|
||||
preinit()
|
||||
initAll()
|
||||
mainWrapper()
|
||||
callMain()
|
||||
abort()
|
||||
}
|
||||
|
||||
@@ -107,6 +107,8 @@ func initTIM() {
|
||||
arm.EnableIRQ(stm32.IRQ_TIM3)
|
||||
}
|
||||
|
||||
const asyncScheduler = false
|
||||
|
||||
// sleepTicks should sleep for specific number of microseconds.
|
||||
func sleepTicks(d timeUnit) {
|
||||
for d != 0 {
|
||||
|
||||
@@ -46,8 +46,8 @@ func main() int {
|
||||
// Run initializers of all packages.
|
||||
initAll()
|
||||
|
||||
// Compiler-generated wrapper to main.main().
|
||||
mainWrapper()
|
||||
// Compiler-generated call to main.main().
|
||||
callMain()
|
||||
|
||||
// For libc compatibility.
|
||||
return 0
|
||||
@@ -57,6 +57,8 @@ func putchar(c byte) {
|
||||
_putchar(int(c))
|
||||
}
|
||||
|
||||
const asyncScheduler = false
|
||||
|
||||
func sleepTicks(d timeUnit) {
|
||||
usleep(uint(d) / 1000)
|
||||
}
|
||||
|
||||
+26
-10
@@ -2,11 +2,13 @@
|
||||
|
||||
package runtime
|
||||
|
||||
type timeUnit int64
|
||||
import (
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
const tickMicros = 1
|
||||
type timeUnit float64 // time in milliseconds, just like Date.now() in JavaScript
|
||||
|
||||
var timestamp timeUnit
|
||||
const tickMicros = 1000000
|
||||
|
||||
//go:export io_get_stdout
|
||||
func io_get_stdout() int32
|
||||
@@ -28,23 +30,37 @@ func _start() {
|
||||
//go:export cwa_main
|
||||
func cwa_main() {
|
||||
initAll() // _start is not called by olin/cwa so has to be called here
|
||||
mainWrapper()
|
||||
callMain()
|
||||
}
|
||||
|
||||
func putchar(c byte) {
|
||||
resource_write(stdout, &c, 1)
|
||||
}
|
||||
|
||||
func sleepTicks(d timeUnit) {
|
||||
// TODO: actually sleep here for the given time.
|
||||
timestamp += d
|
||||
//go:export go_scheduler
|
||||
func go_scheduler() {
|
||||
scheduler()
|
||||
}
|
||||
|
||||
func ticks() timeUnit {
|
||||
return timestamp
|
||||
}
|
||||
const asyncScheduler = true
|
||||
|
||||
// This function is called by the scheduler.
|
||||
// Schedule a call to runtime.scheduler, do not actually sleep.
|
||||
//go:export runtime.sleepTicks
|
||||
func sleepTicks(d timeUnit)
|
||||
|
||||
//go:export runtime.ticks
|
||||
func ticks() timeUnit
|
||||
|
||||
// Abort executes the wasm 'unreachable' instruction.
|
||||
func abort() {
|
||||
trap()
|
||||
}
|
||||
|
||||
//go:export memset
|
||||
func memset(ptr unsafe.Pointer, c byte, size uintptr) unsafe.Pointer {
|
||||
for i := uintptr(0); i < size; i++ {
|
||||
*(*byte)(unsafe.Pointer(uintptr(ptr) + i)) = c
|
||||
}
|
||||
return ptr
|
||||
}
|
||||
|
||||
+30
-68
@@ -9,14 +9,13 @@ package runtime
|
||||
// * A blocking function that calls a non-blocking function is called as
|
||||
// usual.
|
||||
// * A blocking function that calls a blocking function passes its own
|
||||
// coroutine handle as a parameter to the subroutine and will make sure it's
|
||||
// own coroutine is removed from the scheduler. When the subroutine returns,
|
||||
// it will re-insert the parent into the scheduler.
|
||||
// coroutine handle as a parameter to the subroutine. When the subroutine
|
||||
// returns, it will re-insert the parent into the scheduler.
|
||||
// Note that a goroutine is generally called a 'task' for brevity and because
|
||||
// that's the more common term among RTOSes. But a goroutine and a task are
|
||||
// basically the same thing. Although, the code often uses the word 'task' to
|
||||
// refer to both a coroutine and a goroutine, as most of the scheduler isn't
|
||||
// aware of the difference.
|
||||
// refer to both a coroutine and a goroutine, as most of the scheduler doesn't
|
||||
// care about the difference.
|
||||
//
|
||||
// For more background on coroutines in LLVM:
|
||||
// https://llvm.org/docs/Coroutines.html
|
||||
@@ -45,25 +44,20 @@ func (t *coroutine) _promise(alignment int32, from bool) unsafe.Pointer
|
||||
|
||||
// Get the promise belonging to a task.
|
||||
func (t *coroutine) promise() *taskState {
|
||||
return (*taskState)(t._promise(4, false))
|
||||
return (*taskState)(t._promise(int32(unsafe.Alignof(taskState{})), false))
|
||||
}
|
||||
|
||||
func makeGoroutine(*uint8) *uint8
|
||||
|
||||
// State/promise of a task. Internally represented as:
|
||||
//
|
||||
// {i8 state, i32 data, i8* next}
|
||||
// {i8* next, i1 commaOk, i32/i64 data}
|
||||
type taskState struct {
|
||||
state uint8
|
||||
data uint32
|
||||
next *coroutine
|
||||
next *coroutine
|
||||
commaOk bool // 'comma-ok' flag for channel receive operation
|
||||
data uint
|
||||
}
|
||||
|
||||
// Various states a task can be in.
|
||||
const (
|
||||
TASK_STATE_RUNNABLE = iota
|
||||
TASK_STATE_SLEEP
|
||||
TASK_STATE_CALL // waiting for a sub-coroutine
|
||||
)
|
||||
|
||||
// Queues used by the scheduler.
|
||||
//
|
||||
// TODO: runqueueFront can be removed by making the run queue a circular linked
|
||||
@@ -89,48 +83,28 @@ func scheduleLogTask(msg string, t *coroutine) {
|
||||
}
|
||||
}
|
||||
|
||||
// Set the task state to sleep for a given time.
|
||||
// Set the task to sleep for a given time.
|
||||
//
|
||||
// This is a compiler intrinsic.
|
||||
func sleepTask(caller *coroutine, duration int64) {
|
||||
if schedulerDebug {
|
||||
println(" set state sleep:", caller, uint32(duration/tickMicros))
|
||||
println(" set sleep:", caller, uint(duration/tickMicros))
|
||||
}
|
||||
promise := caller.promise()
|
||||
promise.state = TASK_STATE_SLEEP
|
||||
promise.data = uint32(duration / tickMicros) // TODO: longer durations
|
||||
promise.data = uint(duration / tickMicros) // TODO: longer durations
|
||||
addSleepTask(caller)
|
||||
}
|
||||
|
||||
// Wait for the result of an async call. This means that the parent goroutine
|
||||
// will be removed from the runqueue and be rescheduled by the callee.
|
||||
// Add a non-queued task to the run queue.
|
||||
//
|
||||
// This is a compiler intrinsic.
|
||||
func waitForAsyncCall(caller *coroutine) {
|
||||
scheduleLogTask(" set state call:", caller)
|
||||
promise := caller.promise()
|
||||
promise.state = TASK_STATE_CALL
|
||||
}
|
||||
|
||||
// Add a task to the runnable or sleep queue, depending on the state.
|
||||
//
|
||||
// This is a compiler intrinsic.
|
||||
func yieldToScheduler(t *coroutine) {
|
||||
if t == nil {
|
||||
// This is a compiler intrinsic, and is called from a callee to reactivate the
|
||||
// caller.
|
||||
func activateTask(task *coroutine) {
|
||||
if task == nil {
|
||||
return
|
||||
}
|
||||
// See what we should do with this task: try to execute it directly
|
||||
// again or let it sleep for a bit.
|
||||
promise := t.promise()
|
||||
if promise.state == TASK_STATE_CALL {
|
||||
scheduleLogTask(" set waiting for call:", t)
|
||||
return // calling an async task, the subroutine will re-active the parent
|
||||
} else if promise.state == TASK_STATE_SLEEP && promise.data != 0 {
|
||||
scheduleLogTask(" set sleeping:", t)
|
||||
addSleepTask(t)
|
||||
} else {
|
||||
scheduleLogTask(" set runnable:", t)
|
||||
runqueuePushBack(t)
|
||||
}
|
||||
scheduleLogTask(" set runnable:", task)
|
||||
runqueuePushBack(task)
|
||||
}
|
||||
|
||||
// Add this task to the end of the run queue. May also destroy the task if it's
|
||||
@@ -145,9 +119,6 @@ func runqueuePushBack(t *coroutine) {
|
||||
if t.promise().next != nil {
|
||||
panic("runtime: runqueuePushBack: expected next task to be nil")
|
||||
}
|
||||
if t.promise().state != TASK_STATE_RUNNABLE {
|
||||
panic("runtime: runqueuePushBack: expected task state to be runnable")
|
||||
}
|
||||
}
|
||||
if runqueueBack == nil { // empty runqueue
|
||||
scheduleLogTask(" add to runqueue front:", t)
|
||||
@@ -169,10 +140,6 @@ func runqueuePopFront() *coroutine {
|
||||
}
|
||||
if schedulerDebug {
|
||||
println(" runqueuePopFront:", t)
|
||||
// Sanity checking.
|
||||
if t.promise().state != TASK_STATE_RUNNABLE {
|
||||
panic("runtime: runqueuePopFront: task not runnable")
|
||||
}
|
||||
}
|
||||
promise := t.promise()
|
||||
runqueueFront = promise.next
|
||||
@@ -190,9 +157,6 @@ func addSleepTask(t *coroutine) {
|
||||
if t.promise().next != nil {
|
||||
panic("runtime: addSleepTask: expected next task to be nil")
|
||||
}
|
||||
if t.promise().state != TASK_STATE_SLEEP {
|
||||
panic("runtime: addSleepTask: task not sleeping")
|
||||
}
|
||||
}
|
||||
now := ticks()
|
||||
if sleepQueue == nil {
|
||||
@@ -236,11 +200,7 @@ func addSleepTask(t *coroutine) {
|
||||
}
|
||||
|
||||
// Run the scheduler until all tasks have finished.
|
||||
// It takes an initial task (main.main) to bootstrap.
|
||||
func scheduler(main *coroutine) {
|
||||
// Initial task.
|
||||
yieldToScheduler(main)
|
||||
|
||||
func scheduler() {
|
||||
// Main scheduler loop.
|
||||
for {
|
||||
scheduleLog("\n schedule")
|
||||
@@ -254,7 +214,6 @@ func scheduler(main *coroutine) {
|
||||
promise := t.promise()
|
||||
sleepQueueBaseTime += timeUnit(promise.data)
|
||||
sleepQueue = promise.next
|
||||
promise.state = TASK_STATE_RUNNABLE
|
||||
promise.next = nil
|
||||
runqueuePushBack(t)
|
||||
}
|
||||
@@ -271,9 +230,15 @@ func scheduler(main *coroutine) {
|
||||
}
|
||||
timeLeft := timeUnit(sleepQueue.promise().data) - (now - sleepQueueBaseTime)
|
||||
if schedulerDebug {
|
||||
println(" sleeping...", sleepQueue, uint32(timeLeft))
|
||||
println(" sleeping...", sleepQueue, uint(timeLeft))
|
||||
}
|
||||
sleepTicks(timeUnit(timeLeft))
|
||||
if asyncScheduler {
|
||||
// The sleepTicks function above only sets a timeout at which
|
||||
// point the scheduler will be called again. It does not really
|
||||
// sleep.
|
||||
break
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
@@ -281,8 +246,5 @@ func scheduler(main *coroutine) {
|
||||
scheduleLog(" <- runqueuePopFront")
|
||||
scheduleLogTask(" run:", t)
|
||||
t.resume()
|
||||
|
||||
// Add the just resumed task to the run queue or the sleep queue.
|
||||
yieldToScheduler(t)
|
||||
}
|
||||
}
|
||||
|
||||
+14
-4
@@ -14,8 +14,7 @@ type _string struct {
|
||||
|
||||
// The iterator state for a range over a string.
|
||||
type stringIterator struct {
|
||||
byteindex uintptr
|
||||
rangeindex uintptr
|
||||
byteindex uintptr
|
||||
}
|
||||
|
||||
// Return true iff the strings match.
|
||||
@@ -105,10 +104,10 @@ func stringNext(s string, it *stringIterator) (bool, int, rune) {
|
||||
if len(s) <= int(it.byteindex) {
|
||||
return false, 0, 0
|
||||
}
|
||||
i := int(it.byteindex)
|
||||
r, length := decodeUTF8(s, it.byteindex)
|
||||
it.byteindex += length
|
||||
it.rangeindex += 1
|
||||
return true, int(it.rangeindex), r
|
||||
return true, i, r
|
||||
}
|
||||
|
||||
// Convert a Unicode code point into an array of bytes and its length.
|
||||
@@ -166,3 +165,14 @@ func decodeUTF8(s string, index uintptr) (rune, uintptr) {
|
||||
return 0xfffd, 1
|
||||
}
|
||||
}
|
||||
|
||||
// indexByte returns the index of the first instance of c in s, or -1 if c is not present in s.
|
||||
//go:linkname indexByte strings.IndexByte
|
||||
func indexByte(s string, c byte) int {
|
||||
for i := 0; i < len(s); i++ {
|
||||
if s[i] == c {
|
||||
return i
|
||||
}
|
||||
}
|
||||
return -1
|
||||
}
|
||||
|
||||
@@ -1,13 +0,0 @@
|
||||
package runtime
|
||||
|
||||
// This file implements syscall.Syscall and the like.
|
||||
|
||||
//go:linkname syscall_Syscall syscall.Syscall
|
||||
func syscall_Syscall(trap, a1, a2, a3 uintptr) (r1, r2 uintptr, err uintptr) {
|
||||
panic("syscall")
|
||||
}
|
||||
|
||||
//go:linkname syscall_Syscall6 syscall.Syscall6
|
||||
func syscall_Syscall6(trap, a1, a2, a3, a4, a5, a6 uintptr) (r1, r2 uintptr, err uintptr) {
|
||||
panic("syscall6")
|
||||
}
|
||||
@@ -3,6 +3,7 @@ package main
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"os/user"
|
||||
@@ -210,22 +211,24 @@ func defaultTarget(goos, goarch, triple string) (*TargetSpec, error) {
|
||||
BuildTags: []string{goos, goarch},
|
||||
Compiler: commands["clang"],
|
||||
Linker: "cc",
|
||||
LDFlags: []string{"-no-pie"}, // WARNING: clang < 5.0 requires -nopie
|
||||
LDFlags: []string{"-no-pie", "-Wl,--gc-sections"}, // WARNING: clang < 5.0 requires -nopie
|
||||
Objcopy: "objcopy",
|
||||
GDB: "gdb",
|
||||
GDBCmds: []string{"run"},
|
||||
}
|
||||
if goarch != runtime.GOARCH {
|
||||
// Some educated guesses as to how to invoke helper programs.
|
||||
if goarch == "arm" {
|
||||
if goarch == "arm" && goos == "linux" {
|
||||
spec.Linker = "arm-linux-gnueabi-gcc"
|
||||
spec.Objcopy = "arm-linux-gnueabi-objcopy"
|
||||
spec.GDB = "arm-linux-gnueabi-gdb"
|
||||
spec.Emulator = []string{"qemu-arm", "-L", "/usr/arm-linux-gnueabi"}
|
||||
}
|
||||
if goarch == "arm64" {
|
||||
if goarch == "arm64" && goos == "linux" {
|
||||
spec.Linker = "aarch64-linux-gnu-gcc"
|
||||
spec.Objcopy = "aarch64-linux-gnu-objcopy"
|
||||
spec.GDB = "aarch64-linux-gnu-gdb"
|
||||
spec.Emulator = []string{"qemu-aarch64", "-L", "/usr/aarch64-linux-gnu"}
|
||||
}
|
||||
if goarch == "386" {
|
||||
spec.CFlags = []string{"-m32"}
|
||||
@@ -235,12 +238,50 @@ func defaultTarget(goos, goarch, triple string) (*TargetSpec, error) {
|
||||
return &spec, nil
|
||||
}
|
||||
|
||||
// Return the source directory of this package, or "." when it cannot be
|
||||
// recovered.
|
||||
// Return the TINYGOROOT, or exit with an error.
|
||||
func sourceDir() string {
|
||||
// Use $TINYGOROOT as root, if available.
|
||||
root := os.Getenv("TINYGOROOT")
|
||||
if root != "" {
|
||||
if !isSourceDir(root) {
|
||||
fmt.Fprintln(os.Stderr, "error: $TINYGOROOT was not set to the correct root")
|
||||
os.Exit(1)
|
||||
}
|
||||
return root
|
||||
}
|
||||
|
||||
// Find root from executable path.
|
||||
path, err := os.Executable()
|
||||
if err != nil {
|
||||
// Very unlikely. Bail out if it happens.
|
||||
panic("could not get executable path: " + err.Error())
|
||||
}
|
||||
root = filepath.Dir(filepath.Dir(path))
|
||||
if isSourceDir(root) {
|
||||
return root
|
||||
}
|
||||
|
||||
// Fallback: use the original directory from where it was built
|
||||
// https://stackoverflow.com/a/32163888/559350
|
||||
_, path, _, _ := runtime.Caller(0)
|
||||
return filepath.Dir(path)
|
||||
_, path, _, _ = runtime.Caller(0)
|
||||
root = filepath.Dir(path)
|
||||
if isSourceDir(root) {
|
||||
return root
|
||||
}
|
||||
|
||||
fmt.Fprintln(os.Stderr, "error: could not autodetect root directory, set the TINYGOROOT environment variable to override")
|
||||
os.Exit(1)
|
||||
panic("unreachable")
|
||||
}
|
||||
|
||||
// isSourceDir returns true if the directory looks like a TinyGo source directory.
|
||||
func isSourceDir(root string) bool {
|
||||
_, err := os.Stat(filepath.Join(root, "src/runtime/internal/sys/zversion.go"))
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
_, err = os.Stat(filepath.Join(root, "src/device/arm/arm.go"))
|
||||
return err == nil
|
||||
}
|
||||
|
||||
func getGopath() string {
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"inherits": ["cortex-m"],
|
||||
"llvm-target": "armv6m-none-eabi",
|
||||
"build-tags": ["atsamd21g18", "sam"],
|
||||
"cflags": [
|
||||
"--target=armv6m-none-eabi",
|
||||
"-Qunused-arguments"
|
||||
],
|
||||
"ldflags": [
|
||||
"-T", "targets/atsamd21g18.ld"
|
||||
],
|
||||
"extra-files": [
|
||||
"src/device/sam/atsamd21g18a.s"
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
|
||||
MEMORY
|
||||
{
|
||||
FLASH_TEXT (rw) : ORIGIN = 0x00000000+0x2000, LENGTH = 0x00040000-0x2000 /* First 8KB used by bootloader */
|
||||
RAM (xrw) : ORIGIN = 0x20000000, LENGTH = 0x00008000
|
||||
}
|
||||
|
||||
_stack_size = 2K;
|
||||
|
||||
INCLUDE "targets/arm.ld"
|
||||
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"inherits": ["atsamd21g18"],
|
||||
"build-tags": ["sam", "atsamd21g18a", "itsybitsy_m0"],
|
||||
"flash": "bossac -d -i -e -w -v -R --offset=0x2000 {hex}"
|
||||
}
|
||||
@@ -4,6 +4,7 @@
|
||||
"build-tags": ["nrf52", "nrf"],
|
||||
"cflags": [
|
||||
"--target=armv7em-none-eabi",
|
||||
"-mfloat-abi=soft",
|
||||
"-Qunused-arguments",
|
||||
"-DNRF52832_XXAA",
|
||||
"-Ilib/CMSIS/CMSIS/Include"
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
"build-tags": ["nrf52840", "nrf"],
|
||||
"cflags": [
|
||||
"--target=armv7em-none-eabi",
|
||||
"-mfloat-abi=soft",
|
||||
"-Qunused-arguments",
|
||||
"-DNRF52840_XXAA",
|
||||
"-Ilib/CMSIS/CMSIS/Include"
|
||||
|
||||
+3
-2
@@ -1,14 +1,15 @@
|
||||
{
|
||||
"llvm-target": "wasm32-unknown-unknown-wasm",
|
||||
"build-tags": ["js", "wasm"],
|
||||
"goos": "js",
|
||||
"goarch": "wasm",
|
||||
"compiler": "clang-7",
|
||||
"linker": "ld.lld-7",
|
||||
"linker": "wasm-ld-7",
|
||||
"cflags": [
|
||||
"--target=wasm32",
|
||||
"-Oz"
|
||||
],
|
||||
"ldflags": [
|
||||
"-flavor", "wasm",
|
||||
"-allow-undefined"
|
||||
],
|
||||
"emulator": ["cwa"]
|
||||
|
||||
+12
-1
@@ -213,6 +213,17 @@
|
||||
}
|
||||
},
|
||||
|
||||
// func ticks() float64
|
||||
"runtime.ticks": () => {
|
||||
return timeOrigin + performance.now();
|
||||
},
|
||||
|
||||
// func sleepTicks(timeout float64)
|
||||
"runtime.sleepTicks": (timeout) => {
|
||||
// Do not sleep, only reactivate scheduler after the given timeout.
|
||||
setTimeout(this._inst.exports.go_scheduler, timeout);
|
||||
},
|
||||
|
||||
// func stringVal(value string) ref
|
||||
"syscall/js.stringVal": (ret_ptr, value_ptr, value_len) => {
|
||||
const s = loadString(value_ptr, value_len);
|
||||
@@ -322,7 +333,7 @@
|
||||
true,
|
||||
false,
|
||||
global,
|
||||
this._inst.exports.mem,
|
||||
this._inst.exports.memory,
|
||||
this,
|
||||
];
|
||||
this._refs = new Map();
|
||||
|
||||
Vendored
+17
@@ -47,6 +47,20 @@ func main() {
|
||||
println(s2 == Struct2{"foo", 0.0, 7})
|
||||
println(s2 == Struct2{"foo", 1.0, 5})
|
||||
println(s2 == Struct2{"foo", 1.0, 7})
|
||||
|
||||
println("complex numbers")
|
||||
println(c64 == 3+2i)
|
||||
println(c64 == 4+2i)
|
||||
println(c64 == 3+3i)
|
||||
println(c64 != 3+2i)
|
||||
println(c64 != 4+2i)
|
||||
println(c64 != 3+3i)
|
||||
println(c128 == 3+2i)
|
||||
println(c128 == 4+2i)
|
||||
println(c128 == 3+3i)
|
||||
println(c128 != 3+2i)
|
||||
println(c128 != 4+2i)
|
||||
println(c128 != 3+3i)
|
||||
}
|
||||
|
||||
var x = true
|
||||
@@ -58,6 +72,9 @@ var s2 = Struct2{"foo", 0.0, 5}
|
||||
|
||||
var a1 = [2]int{1, 2}
|
||||
|
||||
var c64 = 3 + 2i
|
||||
var c128 = 4 + 3i
|
||||
|
||||
type Int int
|
||||
|
||||
type Struct1 struct {
|
||||
|
||||
Vendored
+13
@@ -41,3 +41,16 @@ true
|
||||
false
|
||||
true
|
||||
false
|
||||
complex numbers
|
||||
true
|
||||
false
|
||||
false
|
||||
false
|
||||
true
|
||||
true
|
||||
false
|
||||
false
|
||||
false
|
||||
true
|
||||
true
|
||||
true
|
||||
|
||||
Vendored
+10
@@ -1,5 +1,7 @@
|
||||
#include "main.h"
|
||||
|
||||
int global = 3;
|
||||
|
||||
int fortytwo() {
|
||||
return 42;
|
||||
}
|
||||
@@ -7,3 +9,11 @@ int fortytwo() {
|
||||
int add(int a, int b) {
|
||||
return a + b;
|
||||
}
|
||||
|
||||
int doCallback(int a, int b, binop_t callback) {
|
||||
return callback(a, b);
|
||||
}
|
||||
|
||||
void store(int value, int *ptr) {
|
||||
*ptr = value;
|
||||
}
|
||||
|
||||
Vendored
+17
@@ -3,6 +3,7 @@ package main
|
||||
/*
|
||||
int fortytwo(void);
|
||||
#include "main.h"
|
||||
int mul(int, int);
|
||||
*/
|
||||
import "C"
|
||||
|
||||
@@ -16,4 +17,20 @@ func main() {
|
||||
println("myint size:", int(unsafe.Sizeof(x)))
|
||||
var y C.longlong = -(1 << 40)
|
||||
println("longlong:", y)
|
||||
println("global:", C.global)
|
||||
var ptr C.intPointer
|
||||
var n C.int = 15
|
||||
ptr = C.intPointer(&n)
|
||||
println("15:", *ptr)
|
||||
C.store(25, &n)
|
||||
println("25:", *ptr)
|
||||
cb := C.binop_t(C.add)
|
||||
println("callback 1:", C.doCallback(20, 30, cb))
|
||||
cb = C.binop_t(C.mul)
|
||||
println("callback 2:", C.doCallback(20, 30, cb))
|
||||
}
|
||||
|
||||
//export mul
|
||||
func mul(a, b C.int) C.int {
|
||||
return a * b
|
||||
}
|
||||
|
||||
Vendored
+9
@@ -1,2 +1,11 @@
|
||||
typedef short myint;
|
||||
int add(int a, int b);
|
||||
typedef int (*binop_t) (int, int);
|
||||
int doCallback(int a, int b, binop_t cb);
|
||||
typedef int * intPointer;
|
||||
extern int global;
|
||||
void store(int value, int *ptr);
|
||||
|
||||
// test duplicate definitions
|
||||
int add(int a, int b);
|
||||
extern int global;
|
||||
|
||||
Vendored
+5
@@ -3,3 +3,8 @@ add: 8
|
||||
myint: 3 5
|
||||
myint size: 2
|
||||
longlong: -1099511627776
|
||||
global: 3
|
||||
15: 15
|
||||
25: 25
|
||||
callback 1: 50
|
||||
callback 2: 600
|
||||
|
||||
Vendored
+95
@@ -0,0 +1,95 @@
|
||||
package main
|
||||
|
||||
import "time"
|
||||
|
||||
func main() {
|
||||
ch := make(chan int)
|
||||
println("len, cap of channel:", len(ch), cap(ch))
|
||||
go sender(ch)
|
||||
|
||||
n, ok := <-ch
|
||||
println("recv from open channel:", n, ok)
|
||||
|
||||
for n := range ch {
|
||||
if n == 6 {
|
||||
time.Sleep(time.Microsecond)
|
||||
}
|
||||
println("received num:", n)
|
||||
}
|
||||
|
||||
n, ok = <-ch
|
||||
println("recv from closed channel:", n, ok)
|
||||
|
||||
// Test multi-sender.
|
||||
ch = make(chan int)
|
||||
go fastsender(ch)
|
||||
go fastsender(ch)
|
||||
go fastsender(ch)
|
||||
slowreceiver(ch)
|
||||
|
||||
// Test multi-receiver.
|
||||
ch = make(chan int)
|
||||
go fastreceiver(ch)
|
||||
go fastreceiver(ch)
|
||||
go fastreceiver(ch)
|
||||
slowsender(ch)
|
||||
|
||||
// Test iterator style channel.
|
||||
ch = make(chan int)
|
||||
go iterator(ch, 100)
|
||||
sum := 0
|
||||
for i := range ch {
|
||||
sum += i
|
||||
}
|
||||
println("sum(100):", sum)
|
||||
|
||||
// Allow goroutines to exit.
|
||||
time.Sleep(time.Microsecond)
|
||||
}
|
||||
|
||||
func sender(ch chan int) {
|
||||
for i := 1; i <= 8; i++ {
|
||||
if i == 4 {
|
||||
time.Sleep(time.Microsecond)
|
||||
println("slept")
|
||||
}
|
||||
ch <- i
|
||||
}
|
||||
close(ch)
|
||||
}
|
||||
|
||||
func fastsender(ch chan int) {
|
||||
ch <- 10
|
||||
ch <- 11
|
||||
}
|
||||
|
||||
func slowreceiver(ch chan int) {
|
||||
for i := 0; i < 6; i++ {
|
||||
n := <-ch
|
||||
println("got n:", n)
|
||||
time.Sleep(time.Microsecond)
|
||||
}
|
||||
}
|
||||
|
||||
func slowsender(ch chan int) {
|
||||
for n := 0; n < 6; n++ {
|
||||
time.Sleep(time.Microsecond)
|
||||
ch <- 12 + n
|
||||
}
|
||||
}
|
||||
|
||||
func fastreceiver(ch chan int) {
|
||||
sum := 0
|
||||
for i := 0; i < 2; i++ {
|
||||
n := <-ch
|
||||
sum += n
|
||||
}
|
||||
println("sum:", sum)
|
||||
}
|
||||
|
||||
func iterator(ch chan int, top int) {
|
||||
for i := 0; i < top; i++ {
|
||||
ch <- i
|
||||
}
|
||||
close(ch)
|
||||
}
|
||||
Vendored
+21
@@ -0,0 +1,21 @@
|
||||
len, cap of channel: 0 0
|
||||
recv from open channel: 1 true
|
||||
received num: 2
|
||||
received num: 3
|
||||
slept
|
||||
received num: 4
|
||||
received num: 5
|
||||
received num: 6
|
||||
received num: 7
|
||||
received num: 8
|
||||
recv from closed channel: 0 false
|
||||
got n: 10
|
||||
got n: 11
|
||||
got n: 10
|
||||
got n: 11
|
||||
got n: 10
|
||||
got n: 11
|
||||
sum: 25
|
||||
sum: 29
|
||||
sum: 33
|
||||
sum(100): 4950
|
||||
Vendored
+22
@@ -9,6 +9,18 @@ func main() {
|
||||
println("main 2")
|
||||
time.Sleep(2 * time.Millisecond)
|
||||
println("main 3")
|
||||
|
||||
// Await a blocking call. This must create a new coroutine.
|
||||
println("wait:")
|
||||
wait()
|
||||
println("end waiting")
|
||||
|
||||
// Run a non-blocking call in a goroutine. This should be turned into a
|
||||
// regular call, so should be equivalent to calling nowait() without 'go'
|
||||
// prefix.
|
||||
go nowait()
|
||||
time.Sleep(time.Millisecond)
|
||||
println("done with non-blocking goroutine")
|
||||
}
|
||||
|
||||
func sub() {
|
||||
@@ -16,3 +28,13 @@ func sub() {
|
||||
time.Sleep(2 * time.Millisecond)
|
||||
println("sub 2")
|
||||
}
|
||||
|
||||
func wait() {
|
||||
println(" wait start")
|
||||
time.Sleep(time.Millisecond)
|
||||
println(" wait end")
|
||||
}
|
||||
|
||||
func nowait() {
|
||||
println("non-blocking goroutine")
|
||||
}
|
||||
|
||||
Vendored
+6
@@ -3,3 +3,9 @@ sub 1
|
||||
main 2
|
||||
sub 2
|
||||
main 3
|
||||
wait:
|
||||
wait start
|
||||
wait end
|
||||
end waiting
|
||||
non-blocking goroutine
|
||||
done with non-blocking goroutine
|
||||
|
||||
Vendored
+16
@@ -16,6 +16,14 @@ var testmap2 = map[string]int{
|
||||
"twelve": 12,
|
||||
}
|
||||
|
||||
type ArrayKey [4]byte
|
||||
|
||||
var testMapArrayKey = map[ArrayKey]int{
|
||||
ArrayKey([4]byte{1, 2, 3, 4}): 1234,
|
||||
ArrayKey([4]byte{4, 3, 2, 1}): 4321,
|
||||
}
|
||||
var testmapIntInt = map[int]int{1: 1, 2: 4, 3: 9}
|
||||
|
||||
func main() {
|
||||
m := map[string]int{"answer": 42, "foo": 3}
|
||||
readMap(m, "answer")
|
||||
@@ -31,6 +39,14 @@ func main() {
|
||||
var nilmap map[string]int
|
||||
println(m == nil, m != nil, len(m))
|
||||
println(nilmap == nil, nilmap != nil, len(nilmap))
|
||||
println(testmapIntInt[2])
|
||||
testmapIntInt[2] = 42
|
||||
println(testmapIntInt[2])
|
||||
|
||||
arrKey := ArrayKey([4]byte{4, 3, 2, 1})
|
||||
println(testMapArrayKey[arrKey])
|
||||
testMapArrayKey[arrKey] = 5555
|
||||
println(testMapArrayKey[arrKey])
|
||||
}
|
||||
|
||||
func readMap(m map[string]int, key string) {
|
||||
|
||||
Vendored
+4
@@ -50,3 +50,7 @@ lookup with comma-ok: eight 8 true
|
||||
lookup with comma-ok: nokey 0 false
|
||||
false true 2
|
||||
true false 0
|
||||
4
|
||||
42
|
||||
4321
|
||||
5555
|
||||
|
||||
Vendored
+46
@@ -0,0 +1,46 @@
|
||||
package main
|
||||
|
||||
import "math"
|
||||
|
||||
func main() {
|
||||
for _, n := range []float64{0.3, 1.5, 2.6, -1.1, -3.1, -3.8} {
|
||||
println("n:", n)
|
||||
println(" asin: ", math.Asin(n))
|
||||
println(" asinh: ", math.Asinh(n))
|
||||
println(" acos: ", math.Acos(n))
|
||||
println(" acosh: ", math.Acosh(n))
|
||||
println(" atan: ", math.Atan(n))
|
||||
println(" atanh: ", math.Atanh(n))
|
||||
println(" atan2: ", math.Atan2(n, 0.2))
|
||||
println(" cbrt: ", math.Cbrt(n))
|
||||
println(" ceil: ", math.Ceil(n))
|
||||
println(" cos: ", math.Cos(n))
|
||||
println(" cosh: ", math.Cosh(n))
|
||||
println(" erf: ", math.Erf(n))
|
||||
println(" erfc: ", math.Erfc(n))
|
||||
println(" exp: ", math.Exp(n))
|
||||
println(" expm1: ", math.Expm1(n))
|
||||
println(" exp2: ", math.Exp2(n))
|
||||
println(" floor: ", math.Floor(n))
|
||||
f, e := math.Frexp(n)
|
||||
println(" frexp: ", f, e)
|
||||
println(" hypot: ", math.Hypot(n, n*2))
|
||||
println(" ldexp: ", math.Ldexp(n, 2))
|
||||
println(" log: ", math.Log(n))
|
||||
println(" log1p: ", math.Log1p(n))
|
||||
println(" log10: ", math.Log10(n))
|
||||
println(" log2: ", math.Log2(n))
|
||||
println(" max: ", math.Max(n, n+1))
|
||||
println(" min: ", math.Min(n, n+1))
|
||||
println(" mod: ", math.Mod(n, n+1))
|
||||
i, f := math.Modf(n)
|
||||
println(" modf: ", i, f)
|
||||
println(" pow: ", math.Pow(n, n))
|
||||
println(" remainder:", math.Remainder(n, n+0.2))
|
||||
println(" sin: ", math.Sin(n))
|
||||
println(" sinh: ", math.Sinh(n))
|
||||
println(" tan: ", math.Tan(n))
|
||||
println(" tanh: ", math.Tanh(n))
|
||||
println(" trunc: ", math.Trunc(n))
|
||||
}
|
||||
}
|
||||
Vendored
+216
@@ -0,0 +1,216 @@
|
||||
n: +3.000000e-001
|
||||
asin: +3.046927e-001
|
||||
asinh: +2.956730e-001
|
||||
acos: +1.266104e+000
|
||||
acosh: NaN
|
||||
atan: +2.914568e-001
|
||||
atanh: +3.095196e-001
|
||||
atan2: +9.827937e-001
|
||||
cbrt: +6.694330e-001
|
||||
ceil: +1.000000e+000
|
||||
cos: +9.553365e-001
|
||||
cosh: +1.045339e+000
|
||||
erf: +3.286268e-001
|
||||
erfc: +6.713732e-001
|
||||
exp: +1.349859e+000
|
||||
expm1: +3.498588e-001
|
||||
exp2: +1.231144e+000
|
||||
floor: +0.000000e+000
|
||||
frexp: +6.000000e-001 -1
|
||||
hypot: +6.708204e-001
|
||||
ldexp: +1.200000e+000
|
||||
log: -1.203973e+000
|
||||
log1p: +2.623643e-001
|
||||
log10: -5.228787e-001
|
||||
log2: -1.736966e+000
|
||||
max: +1.300000e+000
|
||||
min: +3.000000e-001
|
||||
mod: +3.000000e-001
|
||||
modf: +0.000000e+000 +3.000000e-001
|
||||
pow: +6.968453e-001
|
||||
remainder: -2.000000e-001
|
||||
sin: +2.955202e-001
|
||||
sinh: +3.045203e-001
|
||||
tan: +3.093362e-001
|
||||
tanh: +2.913126e-001
|
||||
trunc: +0.000000e+000
|
||||
n: +1.500000e+000
|
||||
asin: NaN
|
||||
asinh: +1.194763e+000
|
||||
acos: NaN
|
||||
acosh: +9.624237e-001
|
||||
atan: +9.827937e-001
|
||||
atanh: NaN
|
||||
atan2: +1.438245e+000
|
||||
cbrt: +1.144714e+000
|
||||
ceil: +2.000000e+000
|
||||
cos: +7.073720e-002
|
||||
cosh: +2.352410e+000
|
||||
erf: +9.661051e-001
|
||||
erfc: +3.389485e-002
|
||||
exp: +4.481689e+000
|
||||
expm1: +3.481689e+000
|
||||
exp2: +2.828427e+000
|
||||
floor: +1.000000e+000
|
||||
frexp: +7.500000e-001 1
|
||||
hypot: +3.354102e+000
|
||||
ldexp: +6.000000e+000
|
||||
log: +4.054651e-001
|
||||
log1p: +9.162907e-001
|
||||
log10: +1.760913e-001
|
||||
log2: +5.849625e-001
|
||||
max: +2.500000e+000
|
||||
min: +1.500000e+000
|
||||
mod: +1.500000e+000
|
||||
modf: +1.000000e+000 +5.000000e-001
|
||||
pow: +1.837117e+000
|
||||
remainder: -2.000000e-001
|
||||
sin: +9.974950e-001
|
||||
sinh: +2.129279e+000
|
||||
tan: +1.410142e+001
|
||||
tanh: +9.051483e-001
|
||||
trunc: +1.000000e+000
|
||||
n: +2.600000e+000
|
||||
asin: NaN
|
||||
asinh: +1.683743e+000
|
||||
acos: NaN
|
||||
acosh: +1.609438e+000
|
||||
atan: +1.203622e+000
|
||||
atanh: NaN
|
||||
atan2: +1.494024e+000
|
||||
cbrt: +1.375069e+000
|
||||
ceil: +3.000000e+000
|
||||
cos: -8.568888e-001
|
||||
cosh: +6.769006e+000
|
||||
erf: +9.997640e-001
|
||||
erfc: +2.360344e-004
|
||||
exp: +1.346374e+001
|
||||
expm1: +1.246374e+001
|
||||
exp2: +6.062866e+000
|
||||
floor: +2.000000e+000
|
||||
frexp: +6.500000e-001 2
|
||||
hypot: +5.813777e+000
|
||||
ldexp: +1.040000e+001
|
||||
log: +9.555114e-001
|
||||
log1p: +1.280934e+000
|
||||
log10: +4.149733e-001
|
||||
log2: +1.378512e+000
|
||||
max: +3.600000e+000
|
||||
min: +2.600000e+000
|
||||
mod: +2.600000e+000
|
||||
modf: +2.000000e+000 +6.000000e-001
|
||||
pow: +1.199308e+001
|
||||
remainder: -2.000000e-001
|
||||
sin: +5.155014e-001
|
||||
sinh: +6.694732e+000
|
||||
tan: -6.015966e-001
|
||||
tanh: +9.890274e-001
|
||||
trunc: +2.000000e+000
|
||||
n: -1.100000e+000
|
||||
asin: NaN
|
||||
asinh: -9.503469e-001
|
||||
acos: NaN
|
||||
acosh: NaN
|
||||
atan: -8.329813e-001
|
||||
atanh: NaN
|
||||
atan2: -1.390943e+000
|
||||
cbrt: -1.032280e+000
|
||||
ceil: -1.000000e+000
|
||||
cos: +4.535961e-001
|
||||
cosh: +1.668519e+000
|
||||
erf: -8.802051e-001
|
||||
erfc: +1.880205e+000
|
||||
exp: +3.328711e-001
|
||||
expm1: -6.671289e-001
|
||||
exp2: +4.665165e-001
|
||||
floor: -2.000000e+000
|
||||
frexp: -5.500000e-001 1
|
||||
hypot: +2.459675e+000
|
||||
ldexp: -4.400000e+000
|
||||
log: NaN
|
||||
log1p: NaN
|
||||
log10: NaN
|
||||
log2: NaN
|
||||
max: -1.000000e-001
|
||||
min: -1.100000e+000
|
||||
mod: -1.000000e-001
|
||||
modf: -1.000000e+000 -1.000000e-001
|
||||
pow: NaN
|
||||
remainder: -2.000000e-001
|
||||
sin: -8.912074e-001
|
||||
sinh: -1.335647e+000
|
||||
tan: -1.964760e+000
|
||||
tanh: -8.004990e-001
|
||||
trunc: -1.000000e+000
|
||||
n: -3.100000e+000
|
||||
asin: NaN
|
||||
asinh: -1.849604e+000
|
||||
acos: NaN
|
||||
acosh: NaN
|
||||
atan: -1.258754e+000
|
||||
atanh: NaN
|
||||
atan2: -1.506369e+000
|
||||
cbrt: -1.458100e+000
|
||||
ceil: -3.000000e+000
|
||||
cos: -9.991352e-001
|
||||
cosh: +1.112150e+001
|
||||
erf: -9.999884e-001
|
||||
erfc: +1.999988e+000
|
||||
exp: +4.504920e-002
|
||||
expm1: -9.549508e-001
|
||||
exp2: +1.166291e-001
|
||||
floor: -4.000000e+000
|
||||
frexp: -7.750000e-001 2
|
||||
hypot: +6.931811e+000
|
||||
ldexp: -1.240000e+001
|
||||
log: NaN
|
||||
log1p: NaN
|
||||
log10: NaN
|
||||
log2: NaN
|
||||
max: -2.100000e+000
|
||||
min: -3.100000e+000
|
||||
mod: -1.000000e+000
|
||||
modf: -3.000000e+000 -1.000000e-001
|
||||
pow: NaN
|
||||
remainder: -2.000000e-001
|
||||
sin: -4.158066e-002
|
||||
sinh: -1.107645e+001
|
||||
tan: +4.161665e-002
|
||||
tanh: -9.959494e-001
|
||||
trunc: -3.000000e+000
|
||||
n: -3.800000e+000
|
||||
asin: NaN
|
||||
asinh: -2.045028e+000
|
||||
acos: NaN
|
||||
acosh: NaN
|
||||
atan: -1.313473e+000
|
||||
atanh: NaN
|
||||
atan2: -1.518213e+000
|
||||
cbrt: -1.560491e+000
|
||||
ceil: -3.000000e+000
|
||||
cos: -7.909677e-001
|
||||
cosh: +2.236178e+001
|
||||
erf: -9.999999e-001
|
||||
erfc: +2.000000e+000
|
||||
exp: +2.237077e-002
|
||||
expm1: -9.776292e-001
|
||||
exp2: +7.179365e-002
|
||||
floor: -4.000000e+000
|
||||
frexp: -9.500000e-001 2
|
||||
hypot: +8.497058e+000
|
||||
ldexp: -1.520000e+001
|
||||
log: NaN
|
||||
log1p: NaN
|
||||
log10: NaN
|
||||
log2: NaN
|
||||
max: -2.800000e+000
|
||||
min: -3.800000e+000
|
||||
mod: -1.000000e+000
|
||||
modf: -3.000000e+000 -8.000000e-001
|
||||
pow: NaN
|
||||
remainder: -2.000000e-001
|
||||
sin: +6.118579e-001
|
||||
sinh: -2.233941e+001
|
||||
tan: -7.735561e-001
|
||||
tanh: -9.989996e-001
|
||||
trunc: -3.000000e+000
|
||||
Vendored
+11
@@ -0,0 +1,11 @@
|
||||
package main
|
||||
|
||||
func testRangeString() {
|
||||
for i, c := range "abcü¢€𐍈°x" {
|
||||
println(i, c)
|
||||
}
|
||||
}
|
||||
|
||||
func main() {
|
||||
testRangeString()
|
||||
}
|
||||
Vendored
+9
@@ -0,0 +1,9 @@
|
||||
0 97
|
||||
1 98
|
||||
2 99
|
||||
3 252
|
||||
5 162
|
||||
7 8364
|
||||
10 66376
|
||||
14 176
|
||||
16 120
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user