mirror of
https://github.com/tinygo-org/tinygo.git
synced 2026-07-27 23:28:42 +00:00
Compare commits
57 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| fa5df4f524 | |||
| 09db7ead50 | |||
| 21a4c14e86 | |||
| b716cf1afd | |||
| 586023b45d | |||
| a00a51e70e | |||
| 745b5dfb81 | |||
| 9c50d47b82 | |||
| 6c63a0d6e7 | |||
| 2a0a7722f9 | |||
| 1460877c28 | |||
| 0739775719 | |||
| 8d3b5a58d1 | |||
| 0d2a3ce532 | |||
| d2b3a5486c | |||
| 536086988c | |||
| 684543b7f1 | |||
| bd8e47af80 | |||
| 5b34713d41 | |||
| f1aea13c51 | |||
| 7bcabe53ca | |||
| 7de3d4be2b | |||
| 02ecab833f | |||
| 1322f404a6 | |||
| 315cd4059f | |||
| 078dd9ff52 | |||
| e5029c63d1 | |||
| dcffbc49c4 | |||
| 6a2a587dff | |||
| 3a76a49ddf | |||
| 81a1114ee5 | |||
| 30e192e7e8 | |||
| a965882a34 | |||
| f967c6919a | |||
| 2e926789f5 | |||
| 38c3d0852e | |||
| 85f2ef40f8 | |||
| ae4ead8690 | |||
| ebebdd5651 | |||
| 1778d92858 | |||
| 86f8778748 | |||
| 2523772b5d | |||
| 31d57fd3d1 | |||
| d653088cbe | |||
| cd8471acae | |||
| b64fc5484a | |||
| 38f8cf7bee | |||
| f484dddbc2 | |||
| a2d0f79be3 | |||
| 792274e86f | |||
| 06aa88abfb | |||
| ad7297a539 | |||
| 4d82f42d61 | |||
| 9c41011e17 | |||
| 5569cd1b6b | |||
| d6c2d6e301 | |||
| a466dd8f2b |
+200
-16
@@ -18,14 +18,12 @@ commands:
|
||||
wget -O - https://apt.llvm.org/llvm-snapshot.gpg.key|sudo apt-key add -
|
||||
sudo apt-get update
|
||||
sudo apt-get install \
|
||||
llvm \
|
||||
python3 \
|
||||
llvm<<parameters.llvm>>-dev \
|
||||
clang<<parameters.llvm>> \
|
||||
libclang<<parameters.llvm>>-dev \
|
||||
lld<<parameters.llvm>> \
|
||||
gcc-arm-linux-gnueabihf \
|
||||
binutils-arm-none-eabi \
|
||||
libc6-dev-armel-cross \
|
||||
gcc-aarch64-linux-gnu \
|
||||
libc6-dev-arm64-cross \
|
||||
@@ -50,36 +48,39 @@ commands:
|
||||
curl https://raw.githubusercontent.com/golang/dep/master/install.sh | sh
|
||||
dep ensure --vendor-only
|
||||
smoketest:
|
||||
steps:
|
||||
- smoketest-no-avr
|
||||
- run: tinygo build -size short -o test.elf -target=arduino examples/blinky1
|
||||
- run: tinygo build -size short -o test.elf -target=digispark examples/blinky1
|
||||
smoketest-no-avr:
|
||||
steps:
|
||||
- run: tinygo build -size short -o test.elf -target=pca10040 examples/blinky1
|
||||
- run: tinygo build -size short -o test.elf -target=pca10040 examples/blinky2
|
||||
- run: tinygo build -size short -o blinky2 examples/blinky2
|
||||
- run: tinygo build -o blinky2 examples/blinky2 # TODO: re-enable -size flag with MachO support
|
||||
- run: tinygo build -size short -o test.elf -target=pca10040 examples/test
|
||||
- run: tinygo build -size short -o test.elf -target=microbit examples/echo
|
||||
- run: tinygo build -size short -o test.elf -target=nrf52840-mdk examples/blinky1
|
||||
- run: tinygo build -size short -o test.elf -target=pca10031 examples/blinky1
|
||||
- run: tinygo build -size short -o test.elf -target=bluepill examples/blinky1
|
||||
- run: tinygo build -size short -o test.elf -target=arduino examples/blinky1
|
||||
- run: tinygo build -size short -o test.elf -target=digispark examples/blinky1
|
||||
- run: tinygo build -size short -o test.elf -target=reelboard examples/blinky1
|
||||
- run: tinygo build -size short -o test.elf -target=reelboard examples/blinky2
|
||||
- run: tinygo build -size short -o test.elf -target=pca10056 examples/blinky1
|
||||
- run: tinygo build -size short -o test.elf -target=pca10056 examples/blinky2
|
||||
- run: tinygo build -size short -o test.elf -target=itsybitsy-m0 examples/blinky1
|
||||
- run: tinygo build -size short -o test.elf -target=circuitplay-express examples/blinky1
|
||||
|
||||
|
||||
jobs:
|
||||
test-llvm7-go111:
|
||||
docker:
|
||||
- image: circleci/golang:1.11
|
||||
|
||||
working_directory: /go/src/github.com/tinygo-org/tinygo
|
||||
- run: tinygo build -size short -o test.elf -target=stm32f4disco examples/blinky1
|
||||
- run: tinygo build -size short -o test.elf -target=stm32f4disco examples/blinky2
|
||||
- run: tinygo build -o wasm.wasm -target=wasm examples/wasm/export
|
||||
- run: tinygo build -o wasm.wasm -target=wasm examples/wasm/main
|
||||
test-linux:
|
||||
parameters:
|
||||
llvm:
|
||||
type: string
|
||||
steps:
|
||||
- checkout
|
||||
- submodules
|
||||
- apt-dependencies:
|
||||
llvm: "-7"
|
||||
llvm: <<parameters.llvm>>
|
||||
- install-node
|
||||
- restore_cache:
|
||||
keys:
|
||||
@@ -87,7 +88,7 @@ jobs:
|
||||
- go-cache-{{ checksum "Gopkg.lock" }}
|
||||
- dep
|
||||
- run: go install .
|
||||
- run: make test
|
||||
- run: go test -v
|
||||
- run: make gen-device -j4
|
||||
- smoketest
|
||||
- save_cache:
|
||||
@@ -95,8 +96,191 @@ jobs:
|
||||
paths:
|
||||
- ~/.cache/go-build
|
||||
- ~/.cache/tinygo
|
||||
- run: make fmt-check
|
||||
build-linux:
|
||||
steps:
|
||||
- checkout
|
||||
- submodules
|
||||
- run:
|
||||
name: "Install apt dependencies"
|
||||
command: |
|
||||
sudo apt-get install \
|
||||
libtinfo-dev \
|
||||
python3 \
|
||||
gcc-arm-linux-gnueabihf \
|
||||
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-node
|
||||
- restore_cache:
|
||||
keys:
|
||||
- go-cache-{{ checksum "Gopkg.lock" }}-{{ .Environment.CIRCLE_PREVIOUS_BUILD_NUM }}
|
||||
- go-cache-{{ checksum "Gopkg.lock" }}
|
||||
- restore_cache:
|
||||
keys:
|
||||
- llvm-source-8-v2
|
||||
- run:
|
||||
name: "Fetch LLVM source"
|
||||
command: make llvm-source
|
||||
- save_cache:
|
||||
key: llvm-source-8-v2
|
||||
paths:
|
||||
- llvm
|
||||
- restore_cache:
|
||||
keys:
|
||||
- llvm-build-8-v2
|
||||
- run:
|
||||
name: "Build LLVM"
|
||||
command: |
|
||||
if [ ! -f llvm-build/lib/liblldELF.a ]
|
||||
then
|
||||
# install dependencies
|
||||
sudo apt-get install cmake clang ninja-build
|
||||
# make build faster
|
||||
export CC=clang
|
||||
export CXX=clang++
|
||||
# hack ninja to use less jobs
|
||||
echo -e '#!/bin/sh\n/usr/bin/ninja -j3 "$@"' > /go/bin/ninja
|
||||
chmod +x /go/bin/ninja
|
||||
# build!
|
||||
make llvm-build
|
||||
fi
|
||||
- save_cache:
|
||||
key: llvm-build-8-v2
|
||||
paths:
|
||||
llvm-build
|
||||
- run:
|
||||
name: "Create LLVM symlinks"
|
||||
command: |
|
||||
ln -s $PWD/llvm-build/bin/clang-8 /go/bin/clang-8
|
||||
ln -s $PWD/llvm-build/bin/ld.lld /go/bin/ld.lld-8
|
||||
ln -s $PWD/llvm-build/bin/wasm-ld /go/bin/wasm-ld-8
|
||||
- dep
|
||||
- run:
|
||||
name: "Test TinyGo"
|
||||
command: make test
|
||||
- run:
|
||||
name: "Build TinyGo release"
|
||||
command: |
|
||||
make release -j3
|
||||
cp -p build/release.tar.gz /tmp/tinygo.linux-amd64.tar.gz
|
||||
- store_artifacts:
|
||||
path: /tmp/tinygo.linux-amd64.tar.gz
|
||||
- save_cache:
|
||||
key: go-cache-{{ checksum "Gopkg.lock" }}-{{ .Environment.CIRCLE_BUILD_NUM }}
|
||||
paths:
|
||||
- ~/.cache/go-build
|
||||
- ~/.cache/tinygo
|
||||
- run:
|
||||
name: "Extract release tarball"
|
||||
command: |
|
||||
mkdir -p ~/lib
|
||||
tar -C ~/lib -xf /tmp/tinygo.linux-amd64.tar.gz
|
||||
ln -s ~/lib/tinygo/bin/tinygo /go/bin/tinygo
|
||||
tinygo version
|
||||
- smoketest
|
||||
build-macos:
|
||||
steps:
|
||||
- checkout
|
||||
- submodules
|
||||
- run:
|
||||
name: "Install dependencies"
|
||||
command: |
|
||||
HOMEBREW_NO_AUTO_UPDATE=1 brew install go dep qemu
|
||||
- restore_cache:
|
||||
keys:
|
||||
- llvm-source-8-macos-v2
|
||||
- run:
|
||||
name: "Fetch LLVM source"
|
||||
command: make llvm-source
|
||||
- save_cache:
|
||||
key: llvm-source-8-macos-v2
|
||||
paths:
|
||||
- llvm
|
||||
- restore_cache:
|
||||
keys:
|
||||
- llvm-build-8-macos-v2
|
||||
- run:
|
||||
name: "Build LLVM"
|
||||
command: |
|
||||
if [ ! -f llvm-build/lib/liblldELF.a ]
|
||||
then
|
||||
# install dependencies
|
||||
HOMEBREW_NO_AUTO_UPDATE=1 brew install cmake ninja
|
||||
# build!
|
||||
make llvm-build
|
||||
fi
|
||||
- save_cache:
|
||||
key: llvm-build-8-macos-v2
|
||||
paths:
|
||||
llvm-build
|
||||
- run:
|
||||
name: "Create LLVM symlinks"
|
||||
command: |
|
||||
ln -s $PWD/llvm-build/bin/clang-8 /usr/local/bin/clang-8
|
||||
- run:
|
||||
name: "Install Go dependencies"
|
||||
command: dep ensure --vendor-only
|
||||
- run:
|
||||
name: "Test TinyGo"
|
||||
command: make test
|
||||
- run:
|
||||
name: "Build TinyGo release"
|
||||
command: |
|
||||
make release -j3
|
||||
cp -p build/release.tar.gz /tmp/tinygo.darwin-amd64.tar.gz
|
||||
- store_artifacts:
|
||||
path: /tmp/tinygo.darwin-amd64.tar.gz
|
||||
- run:
|
||||
name: "Extract release tarball"
|
||||
command: |
|
||||
mkdir -p ~/lib
|
||||
tar -C /usr/local/opt -xf /tmp/tinygo.darwin-amd64.tar.gz
|
||||
ln -s /usr/local/opt/tinygo/bin/tinygo /usr/local/bin/tinygo
|
||||
tinygo version
|
||||
- smoketest-no-avr
|
||||
|
||||
|
||||
jobs:
|
||||
test-llvm8-go111:
|
||||
docker:
|
||||
- image: circleci/golang:1.11
|
||||
working_directory: /go/src/github.com/tinygo-org/tinygo
|
||||
steps:
|
||||
- test-linux:
|
||||
llvm: "-8"
|
||||
test-llvm8-go112:
|
||||
docker:
|
||||
- image: circleci/golang:1.12
|
||||
working_directory: /go/src/github.com/tinygo-org/tinygo
|
||||
steps:
|
||||
- test-linux:
|
||||
llvm: "-8"
|
||||
build-linux:
|
||||
docker:
|
||||
- image: circleci/golang:1.12
|
||||
working_directory: /go/src/github.com/tinygo-org/tinygo
|
||||
steps:
|
||||
- build-linux
|
||||
build-macos:
|
||||
macos:
|
||||
xcode: "10.1.0"
|
||||
working_directory: ~/go/src/github.com/tinygo-org/tinygo
|
||||
steps:
|
||||
- build-macos
|
||||
|
||||
|
||||
|
||||
|
||||
workflows:
|
||||
test-all:
|
||||
jobs:
|
||||
- test-llvm7-go111
|
||||
- test-llvm8-go111
|
||||
- test-llvm8-go112
|
||||
- build-linux
|
||||
- build-macos
|
||||
|
||||
+1
-1
@@ -13,4 +13,4 @@
|
||||
[submodule "lib/compiler-rt"]
|
||||
path = lib/compiler-rt
|
||||
url = https://github.com/llvm-mirror/compiler-rt.git
|
||||
branch = release_70
|
||||
branch = release_80
|
||||
|
||||
-40
@@ -1,40 +0,0 @@
|
||||
language: go
|
||||
|
||||
matrix:
|
||||
include:
|
||||
- os: osx
|
||||
go: "1.11"
|
||||
env: PATH="/usr/local/opt/llvm/bin:$PATH"
|
||||
before_install:
|
||||
- mkdir -p /Users/travis/gopath/bin
|
||||
|
||||
addons:
|
||||
homebrew:
|
||||
update: true
|
||||
taps: ArmMbed/homebrew-formulae
|
||||
packages:
|
||||
- llvm@7
|
||||
- qemu
|
||||
- arm-none-eabi-gcc
|
||||
|
||||
install:
|
||||
- curl https://raw.githubusercontent.com/golang/dep/master/install.sh | sh
|
||||
- dep ensure --vendor-only
|
||||
|
||||
script:
|
||||
- go install github.com/tinygo-org/tinygo
|
||||
- go test -v .
|
||||
- make gen-device
|
||||
- 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 -o blinky2 examples/blinky2 # TODO: re-enable -size flag with MachO support
|
||||
- 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.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
|
||||
+26
-60
@@ -2,9 +2,9 @@
|
||||
|
||||
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.
|
||||
these can be linked: dynamically and statically. An install with `go install` is
|
||||
dynamic linking because it is fast and works almost out of the box on
|
||||
Debian-based systems with the right packages 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
|
||||
@@ -18,81 +18,49 @@ 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
|
||||
* git
|
||||
* CMake
|
||||
* [Ninja](https://ninja-build.org/) or make (preferably Ninja)
|
||||
* [Ninja](https://ninja-build.org/)
|
||||
|
||||
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:
|
||||
The first step is to download the TinyGo sources. Then, inside the directory,
|
||||
perform these steps:
|
||||
|
||||
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
|
||||
dep ensure -vendor-only # download Go dependencies
|
||||
make llvm-source # download LLVM
|
||||
|
||||
Note that Clang and LLD must be placed inside the tools subdirectory of LLVM to
|
||||
be automatically built with the rest of the system.
|
||||
You can also store LLVM outside of the TinyGo root directory by setting the
|
||||
`LLVM_BUILDDIR`, `CLANG_SRC` and `LLD_SRC` make variables, but that is not
|
||||
covered by this guide.
|
||||
|
||||
## 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.
|
||||
Before starting the build, 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:
|
||||
The Makefile includes a default configuration that is good for most users. It
|
||||
builds a release version of LLVM (optimized, no asserts) and includes all
|
||||
targets supported by TinyGo:
|
||||
|
||||
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
|
||||
make llvm-build
|
||||
|
||||
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.
|
||||
The last step of course is to build TinyGo itself. This can again be done with
|
||||
make:
|
||||
|
||||
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
|
||||
make
|
||||
|
||||
## Verify TinyGo
|
||||
|
||||
@@ -109,14 +77,12 @@ 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:
|
||||
Now that we have a working static build, it's time to make a release tarball:
|
||||
|
||||
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
|
||||
make release
|
||||
|
||||
The release tarball is stored in build/release.tar.gz, and can be extracted with
|
||||
the following command:
|
||||
the following command (for example in ~/lib):
|
||||
|
||||
tar -xvf path/to/release.tar.gz
|
||||
|
||||
|
||||
@@ -1,3 +1,44 @@
|
||||
0.5.0
|
||||
---
|
||||
- **compiler driver**
|
||||
- use `wasm-ld` instead of `wasm-ld-8` on macOS
|
||||
- drop dependency on `llvm-ar`
|
||||
- fix linker script includes when running outside `TINYGOROOT`
|
||||
- **compiler**
|
||||
- switch to LLVM 8
|
||||
- add support for the Go 1.12 standard library (Go 1.11 is still supported)
|
||||
- work around lack of escape analysis due to nil checks
|
||||
- implement casting named structs and pointers to them
|
||||
- fix int casting to use the source signedness
|
||||
- fix some bugs around `make([]T, …)` with uncommon index types
|
||||
- some other optimizations
|
||||
- support interface asserts in interp for "math/rand" support
|
||||
- resolve all func value targets at compile time (wasm-only at the moment)
|
||||
- **cgo**
|
||||
- improve diagnostics
|
||||
- implement C `struct`, `union`, and arrays
|
||||
- fix CGo-related crash in libclang
|
||||
- implement `C.struct_` types
|
||||
- **targets**
|
||||
- all baremetal: pretend to be linux/arm instead of js/wasm
|
||||
- `avr`: improve `uintptr` support
|
||||
- `cortexm`: implement memmove intrinsic generated by LLVM
|
||||
- `cortexm`: use the lld linker instead of `arm-none-eabi-ld`
|
||||
- `darwin`: use custom syscall package that links to libSystem.dylib
|
||||
- `microbit`: add blink example
|
||||
- `samd21`: support I2C1
|
||||
- `samd21`: machine/atsamd21: correct pad/pin handling when using both UART
|
||||
and USBCDC interfaces at same time
|
||||
- `stm32f4discovery`: add support for this board
|
||||
- `wasm`: support async func values
|
||||
- `wasm`: improve documentation and add extra example
|
||||
|
||||
0.4.1
|
||||
---
|
||||
- **compiler**
|
||||
- fix `objcopy` replacement to include the .data section in the firmware image
|
||||
- use `llvm-ar-7` on Linux to fix the Docker image
|
||||
|
||||
0.4.0
|
||||
---
|
||||
- **compiler**
|
||||
|
||||
+7
-7
@@ -1,10 +1,10 @@
|
||||
# TinyGo base stage just installs LLVM 7 and the TinyGo compiler itself.
|
||||
# TinyGo base stage just installs LLVM 8 and the TinyGo compiler itself.
|
||||
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 && \
|
||||
echo "deb http://apt.llvm.org/stretch/ llvm-toolchain-stretch-8 main" >> /etc/apt/sources.list && \
|
||||
apt-get update && \
|
||||
apt-get install -y llvm-7-dev libclang-7-dev git
|
||||
apt-get install -y llvm-8-dev libclang-8-dev git
|
||||
|
||||
RUN wget -O- https://raw.githubusercontent.com/golang/dep/master/install.sh | sh
|
||||
|
||||
@@ -25,9 +25,9 @@ COPY --from=tinygo-base /go/src/github.com/tinygo-org/tinygo/src /go/src/github.
|
||||
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 && \
|
||||
echo "deb http://apt.llvm.org/stretch/ llvm-toolchain-stretch-8 main" >> /etc/apt/sources.list && \
|
||||
apt-get update && \
|
||||
apt-get install -y libllvm7 lld-7
|
||||
apt-get install -y libllvm8 lld-8
|
||||
|
||||
# tinygo-avr stage installs the needed dependencies to compile TinyGo programs for AVR microcontrollers.
|
||||
FROM tinygo-base AS tinygo-avr
|
||||
@@ -59,7 +59,7 @@ COPY --from=tinygo-base /go/src/github.com/tinygo-org/tinygo/lib /go/src/github.
|
||||
|
||||
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 && \
|
||||
apt-get install -y apt-utils python3 make clang-8 && \
|
||||
make gen-device-nrf && make gen-device-stm32 && \
|
||||
apt-get remove -y python3 make && \
|
||||
apt-get autoremove -y && \
|
||||
@@ -74,7 +74,7 @@ COPY --from=tinygo-base /go/src/github.com/tinygo-org/tinygo/lib /go/src/github.
|
||||
|
||||
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 && \
|
||||
apt-get install -y apt-utils python3 make clang-8 binutils-avr gcc-avr avr-libc && \
|
||||
make gen-device && \
|
||||
apt-get remove -y python3 make && \
|
||||
apt-get autoremove -y && \
|
||||
|
||||
Generated
+13
-4
@@ -1,6 +1,14 @@
|
||||
# This file is autogenerated, do not edit; changes may be undone by the next 'dep ensure'.
|
||||
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
digest = "1:06519a2ec1d59040eaccec40206f9d0b59dc662db2a032f974d6d6b9a2bcb839"
|
||||
name = "github.com/blakesmith/ar"
|
||||
packages = ["."]
|
||||
pruneopts = "UT"
|
||||
revision = "8bd4349a67f2533b078dbc524689d15dba0f4659"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
digest = "1:00b45e06c7843541372fc17d982242bd6adfc2fc382b6f2e9ef9ce53d87a50b9"
|
||||
@@ -19,20 +27,21 @@
|
||||
"go/types/typeutil",
|
||||
]
|
||||
pruneopts = "UT"
|
||||
revision = "3744606dbb67b99c60d3f11cb10bd3f9e6dad472"
|
||||
revision = "8dcc6e70cdefe9a82236b6e195e4f4e2108fcb9f"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
digest = "1:a6a25fd8906c74978f1ed811bc9fd3422da8093be863b458874b02a782b6ae3e"
|
||||
branch = "llvm8"
|
||||
digest = "1:bf5539bdf6b3cc3ec1e45926db05d81180da11ce722fa1edcce3f0b4e1967da5"
|
||||
name = "tinygo.org/x/go-llvm"
|
||||
packages = ["."]
|
||||
pruneopts = "UT"
|
||||
revision = "d5f730401f5069618b275a5241c6417eb0c38a65"
|
||||
revision = "7707ae5d1261a8929edea7336c8087ca8b520d8d"
|
||||
|
||||
[solve-meta]
|
||||
analyzer-name = "dep"
|
||||
analyzer-version = 1
|
||||
input-imports = [
|
||||
"github.com/blakesmith/ar",
|
||||
"github.com/marcinbor85/gohex",
|
||||
"golang.org/x/tools/go/ast/astutil",
|
||||
"golang.org/x/tools/go/ssa",
|
||||
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
[[constraint]]
|
||||
branch = "master"
|
||||
branch = "llvm8"
|
||||
name = "tinygo.org/x/go-llvm"
|
||||
|
||||
[[constraint]]
|
||||
|
||||
@@ -3,48 +3,24 @@
|
||||
all: tinygo
|
||||
tinygo: build/tinygo
|
||||
|
||||
.PHONY: all tinygo static run-test run-blinky run-blinky2 clean fmt gen-device gen-device-nrf gen-device-avr
|
||||
.PHONY: all tinygo build/tinygo test llvm-build llvm-source clean fmt gen-device gen-device-nrf gen-device-avr
|
||||
|
||||
TARGET ?= unix
|
||||
|
||||
ifeq ($(TARGET),unix)
|
||||
# Regular *nix system.
|
||||
|
||||
else ifeq ($(TARGET),pca10040)
|
||||
# PCA10040: nRF52832 development board
|
||||
OBJCOPY = arm-none-eabi-objcopy
|
||||
TGOFLAGS += -target $(TARGET)
|
||||
|
||||
else ifeq ($(TARGET),microbit)
|
||||
# BBC micro:bit
|
||||
OBJCOPY = arm-none-eabi-objcopy
|
||||
TGOFLAGS += -target $(TARGET)
|
||||
|
||||
else ifeq ($(TARGET),reelboard)
|
||||
# reel board
|
||||
OBJCOPY = arm-none-eabi-objcopy
|
||||
TGOFLAGS += -target $(TARGET)
|
||||
|
||||
else ifeq ($(TARGET),bluepill)
|
||||
# "blue pill" development board
|
||||
# See: https://wiki.stm32duino.com/index.php?title=Blue_Pill
|
||||
OBJCOPY = arm-none-eabi-objcopy
|
||||
TGOFLAGS += -target $(TARGET)
|
||||
|
||||
else ifeq ($(TARGET),arduino)
|
||||
OBJCOPY = avr-objcopy
|
||||
TGOFLAGS += -target $(TARGET)
|
||||
|
||||
else
|
||||
$(error Unknown target)
|
||||
|
||||
endif
|
||||
# Default build and source directories, as created by `make llvm-build`.
|
||||
LLVM_BUILDDIR ?= llvm-build
|
||||
CLANG_SRC ?= llvm/tools/clang
|
||||
LLD_SRC ?= llvm/tools/lld
|
||||
|
||||
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++
|
||||
UNAME_S := $(shell uname -s)
|
||||
ifeq ($(UNAME_S),Linux)
|
||||
START_GROUP = -Wl,--start-group
|
||||
END_GROUP = -Wl,--end-group
|
||||
endif
|
||||
|
||||
LLD_LIBS = -Wl,--start-group -llldCOFF -llldCommon -llldCore -llldDriver -llldELF -llldMachO -llldMinGW -llldReaderWriter -llldWasm -llldYAML -Wl,--end-group
|
||||
CLANG_LIBS = $(START_GROUP) $(abspath $(LLVM_BUILDDIR))/lib/libclang.a -lclangAnalysis -lclangARCMigrate -lclangAST -lclangASTMatchers -lclangBasic -lclangCodeGen -lclangCrossTU -lclangDriver -lclangDynamicASTMatchers -lclangEdit -lclangFormat -lclangFrontend -lclangFrontendTool -lclangHandleCXX -lclangHandleLLVM -lclangIndex -lclangLex -lclangParse -lclangRewrite -lclangRewriteFrontend -lclangSema -lclangSerialization -lclangStaticAnalyzerCheckers -lclangStaticAnalyzerCore -lclangStaticAnalyzerFrontend -lclangTooling -lclangToolingASTDiff -lclangToolingCore -lclangToolingInclusions -lclangToolingRefactor $(END_GROUP) -lstdc++
|
||||
|
||||
LLD_LIBS = $(START_GROUP) -llldCOFF -llldCommon -llldCore -llldDriver -llldELF -llldMachO -llldMinGW -llldReaderWriter -llldWasm -llldYAML $(END_GROUP)
|
||||
|
||||
|
||||
# For static linking.
|
||||
@@ -53,40 +29,14 @@ 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
|
||||
./build/test
|
||||
|
||||
run-blinky: run-blinky2
|
||||
run-blinky2: build/blinky2
|
||||
./build/blinky2
|
||||
|
||||
ifeq ($(TARGET),pca10040)
|
||||
flash-%: build/%.hex
|
||||
nrfjprog -f nrf52 --sectorerase --program $< --reset
|
||||
else ifeq ($(TARGET),microbit)
|
||||
flash-%: build/%.hex
|
||||
openocd -f interface/cmsis-dap.cfg -f target/nrf51.cfg -c 'program $< reset exit'
|
||||
else ifeq ($(TARGET),reelboard)
|
||||
flash-%: build/%.hex
|
||||
openocd -f interface/cmsis-dap.cfg -f target/nrf51.cfg -c 'program $< reset exit'
|
||||
else ifeq ($(TARGET),arduino)
|
||||
flash-%: build/%.hex
|
||||
avrdude -c arduino -p atmega328p -P /dev/ttyACM0 -U flash:w:$<
|
||||
else ifeq ($(TARGET),bluepill)
|
||||
flash-%: build/%.hex
|
||||
openocd -f interface/stlink-v2.cfg -f target/stm32f1x.cfg -c 'program $< reset exit'
|
||||
endif
|
||||
|
||||
clean:
|
||||
@rm -rf build
|
||||
|
||||
FMT_PATHS = ./*.go compiler interp ir loader src/device/arm src/examples src/machine src/os src/reflect src/runtime src/sync src/syscall
|
||||
fmt:
|
||||
@go fmt . ./compiler ./interp ./loader ./ir ./src/device/arm ./src/examples/* ./src/machine ./src/os ./src/runtime ./src/sync
|
||||
@go fmt ./testdata/*.go
|
||||
|
||||
test:
|
||||
@go test -v .
|
||||
@gofmt -l -w $(FMT_PATHS)
|
||||
fmt-check:
|
||||
@unformatted=$$(gofmt -l $(FMT_PATHS)); [ -z "$$unformatted" ] && exit 0; echo "Unformatted:"; for fn in $$unformatted; do echo " $$fn"; done; exit 1
|
||||
|
||||
gen-device: gen-device-avr gen-device-nrf gen-device-sam gen-device-stm32
|
||||
|
||||
@@ -107,15 +57,34 @@ gen-device-stm32:
|
||||
./tools/gen-device-svd.py lib/cmsis-svd/data/STMicro/ src/device/stm32/ --source=https://github.com/posborne/cmsis-svd/tree/master/data/STMicro
|
||||
go fmt ./src/device/stm32
|
||||
|
||||
|
||||
# Get LLVM sources.
|
||||
llvm/README.txt:
|
||||
git clone -b release_80 https://github.com/llvm-mirror/llvm.git llvm
|
||||
llvm/tools/clang/README.txt:
|
||||
git clone -b release_80 https://github.com/llvm-mirror/clang.git llvm/tools/clang
|
||||
llvm/tools/lld/README.md:
|
||||
git clone -b release_80 https://github.com/llvm-mirror/lld.git llvm/tools/lld
|
||||
llvm-source: llvm/README.txt llvm/tools/clang/README.txt llvm/tools/lld/README.md
|
||||
|
||||
# Configure LLVM.
|
||||
llvm-build/build.ninja: llvm-source
|
||||
mkdir -p llvm-build; cd llvm-build; cmake -G Ninja ../llvm "-DLLVM_TARGETS_TO_BUILD=X86;ARM;AArch64;WebAssembly" "-DLLVM_EXPERIMENTAL_TARGETS_TO_BUILD=AVR" -DCMAKE_BUILD_TYPE=Release -DLLVM_ENABLE_ASSERTIONS=OFF -DLIBCLANG_BUILD_STATIC=ON
|
||||
|
||||
# Build LLVM.
|
||||
llvm-build: llvm-build/build.ninja
|
||||
cd llvm-build; ninja
|
||||
|
||||
|
||||
# Build the Go compiler.
|
||||
build/tinygo:
|
||||
@mkdir -p build
|
||||
go build -o build/tinygo .
|
||||
|
||||
static:
|
||||
@if [ ! -f llvm-build/bin/llvm-config ]; then echo "Fetch and build LLVM first by running:\n make llvm-source\n make llvm-build"; exit 1; fi
|
||||
CGO_CPPFLAGS="$(CGO_CPPFLAGS)" CGO_CXXFLAGS="$(CGO_CXXFLAGS)" CGO_LDFLAGS="$(CGO_LDFLAGS)" go build -o build/tinygo -tags byollvm .
|
||||
|
||||
release: static gen-device
|
||||
test:
|
||||
CGO_CPPFLAGS="$(CGO_CPPFLAGS)" CGO_CXXFLAGS="$(CGO_CXXFLAGS)" CGO_LDFLAGS="$(CGO_LDFLAGS)" go test -v -tags byollvm .
|
||||
|
||||
release: build/tinygo 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
|
||||
@@ -136,15 +105,3 @@ release: static gen-device
|
||||
./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/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/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
|
||||
$(OBJCOPY) -O ihex $^ $@
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
# TinyGo - Go compiler for small places
|
||||
|
||||
[](https://travis-ci.com/tinygo-org/tinygo)
|
||||
[](https://circleci.com/gh/tinygo-org/tinygo/tree/dev)
|
||||
|
||||
TinyGo is a Go compiler intended for use in small places such as microcontrollers, WebAssembly (WASM), and command-line tools.
|
||||
|
||||
+42
-6
@@ -1,11 +1,16 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/blakesmith/ar"
|
||||
)
|
||||
|
||||
// These are the GENERIC_SOURCES according to CMakeList.txt.
|
||||
@@ -246,14 +251,45 @@ func compileBuiltins(target string, callback func(path string) error) error {
|
||||
}
|
||||
|
||||
// Put all builtins in an archive to link as a static library.
|
||||
// Note: this does not create a symbol index, but ld.lld doesn't seem to
|
||||
// care.
|
||||
arpath := filepath.Join(dir, "librt.a")
|
||||
cmd := exec.Command(commands["ar"], append([]string{"cr", arpath}, objs...)...)
|
||||
cmd.Stdout = os.Stdout
|
||||
cmd.Stderr = os.Stderr
|
||||
cmd.Dir = dir
|
||||
err = cmd.Run()
|
||||
arfile, err := os.Create(arpath)
|
||||
if err != nil {
|
||||
return &commandError{"failed to make static library", arpath, err}
|
||||
return err
|
||||
}
|
||||
defer arfile.Close()
|
||||
arwriter := ar.NewWriter(arfile)
|
||||
err = arwriter.WriteGlobalHeader()
|
||||
if err != nil {
|
||||
return &os.PathError{"write ar header", arpath, err}
|
||||
}
|
||||
for _, objpath := range objs {
|
||||
name := filepath.Base(objpath)
|
||||
objfile, err := os.Open(objpath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer objfile.Close()
|
||||
st, err := objfile.Stat()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
arwriter.WriteHeader(&ar.Header{
|
||||
Name: name,
|
||||
ModTime: time.Unix(0, 0),
|
||||
Uid: 0,
|
||||
Gid: 0,
|
||||
Mode: 0644,
|
||||
Size: st.Size(),
|
||||
})
|
||||
n, err := io.Copy(arwriter, objfile)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if n != st.Size() {
|
||||
return errors.New("file modified during ar creation: " + arpath)
|
||||
}
|
||||
}
|
||||
|
||||
// Give the caller the resulting file. The callback must copy the file,
|
||||
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
// +build !darwin
|
||||
|
||||
package main
|
||||
|
||||
// commands used by the compilation process might have different file names on Linux than those used on macOS.
|
||||
var commands = map[string]string{
|
||||
"clang": "clang-8",
|
||||
"ld.lld": "ld.lld-8",
|
||||
"wasm-ld": "wasm-ld-8",
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
// +build darwin
|
||||
|
||||
package main
|
||||
|
||||
// commands used by the compilation process might have different file names on macOS than those used on Linux.
|
||||
var commands = map[string]string{
|
||||
"clang": "clang-8",
|
||||
"ld.lld": "ld.lld",
|
||||
"wasm-ld": "wasm-ld",
|
||||
}
|
||||
+22
-2
@@ -51,6 +51,11 @@ func (c *Compiler) emitLookupBoundsCheck(frame *Frame, arrayLen, index llvm.Valu
|
||||
|
||||
// emitSliceBoundsCheck emits a bounds check before a slicing operation to make
|
||||
// sure it is within bounds.
|
||||
//
|
||||
// This function is both used for slicing a slice (low and high have their
|
||||
// normal meaning) and for creating a new slice, where 'capacity' means the
|
||||
// biggest possible slice capacity, 'low' means len and 'high' means cap. The
|
||||
// logic is the same in both cases.
|
||||
func (c *Compiler) emitSliceBoundsCheck(frame *Frame, capacity, low, high llvm.Value, lowType, highType *types.Basic) {
|
||||
if frame.fn.IsNoBounds() {
|
||||
// The //go:nobounds pragma was added to the function to avoid bounds
|
||||
@@ -115,8 +120,23 @@ func (c *Compiler) emitNilCheck(frame *Frame, ptr llvm.Value, blockPrefix string
|
||||
frame.blockExits[frame.currentBlock] = nextBlock // adjust outgoing block for phi nodes
|
||||
|
||||
// Compare against nil.
|
||||
nilptr := llvm.ConstPointerNull(ptr.Type())
|
||||
isnil := c.builder.CreateICmp(llvm.IntEQ, ptr, nilptr, "")
|
||||
var isnil llvm.Value
|
||||
if ptr.Type().PointerAddressSpace() == 0 {
|
||||
// Do the nil check using the isnil builtin, which marks the parameter
|
||||
// as nocapture.
|
||||
// The reason it has to go through a builtin, is that a regular icmp
|
||||
// instruction may capture the pointer in LLVM semantics, see
|
||||
// https://reviews.llvm.org/D60047 for details. Pointer capturing
|
||||
// unfortunately breaks escape analysis, so we use this trick to let the
|
||||
// functionattr pass know that this pointer doesn't really escape.
|
||||
ptr = c.builder.CreateBitCast(ptr, c.i8ptrType, "")
|
||||
isnil = c.createRuntimeCall("isnil", []llvm.Value{ptr}, "")
|
||||
} else {
|
||||
// Do the nil check using a regular icmp. This can happen with function
|
||||
// pointers on AVR, which don't benefit from escape analysis anyway.
|
||||
nilptr := llvm.ConstPointerNull(ptr.Type())
|
||||
isnil = c.builder.CreateICmp(llvm.IntEQ, ptr, nilptr, "")
|
||||
}
|
||||
c.builder.CreateCondBr(isnil, faultBlock, nextBlock)
|
||||
|
||||
// Fail: this is a nil pointer, exit with a panic.
|
||||
|
||||
+270
-336
@@ -9,7 +9,6 @@ import (
|
||||
"go/types"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"runtime"
|
||||
"strconv"
|
||||
"strings"
|
||||
@@ -162,11 +161,11 @@ func (c *Compiler) selectGC() string {
|
||||
func (c *Compiler) Compile(mainPath string) error {
|
||||
// Prefix the GOPATH with the system GOROOT, as GOROOT is already set to
|
||||
// the TinyGo root.
|
||||
gopath := c.GOPATH
|
||||
if gopath == "" {
|
||||
gopath = runtime.GOROOT()
|
||||
overlayGopath := c.GOPATH
|
||||
if overlayGopath == "" {
|
||||
overlayGopath = runtime.GOROOT()
|
||||
} else {
|
||||
gopath = runtime.GOROOT() + string(filepath.ListSeparator) + gopath
|
||||
overlayGopath = runtime.GOROOT() + string(filepath.ListSeparator) + overlayGopath
|
||||
}
|
||||
|
||||
wd, err := os.Getwd()
|
||||
@@ -177,13 +176,40 @@ func (c *Compiler) Compile(mainPath string) error {
|
||||
Build: &build.Context{
|
||||
GOARCH: c.GOARCH,
|
||||
GOOS: c.GOOS,
|
||||
GOROOT: c.RootDir,
|
||||
GOPATH: gopath,
|
||||
GOROOT: runtime.GOROOT(),
|
||||
GOPATH: c.GOPATH,
|
||||
CgoEnabled: true,
|
||||
UseAllFiles: false,
|
||||
Compiler: "gc", // must be one of the recognized compilers
|
||||
BuildTags: append([]string{"tinygo", "gc." + c.selectGC()}, c.BuildTags...),
|
||||
},
|
||||
OverlayBuild: &build.Context{
|
||||
GOARCH: c.GOARCH,
|
||||
GOOS: c.GOOS,
|
||||
GOROOT: c.RootDir,
|
||||
GOPATH: overlayGopath,
|
||||
CgoEnabled: true,
|
||||
UseAllFiles: false,
|
||||
Compiler: "gc", // must be one of the recognized compilers
|
||||
BuildTags: append([]string{"tinygo", "gc." + c.selectGC()}, c.BuildTags...),
|
||||
},
|
||||
ShouldOverlay: func(path string) bool {
|
||||
switch path {
|
||||
case "machine", "os", "reflect", "runtime", "sync":
|
||||
return true
|
||||
default:
|
||||
if strings.HasPrefix(path, "device/") || strings.HasPrefix(path, "examples/") {
|
||||
return true
|
||||
} else if path == "syscall" {
|
||||
for _, tag := range c.BuildTags {
|
||||
if tag == "avr" || tag == "cortexm" || tag == "darwin" {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
},
|
||||
TypeChecker: types.Config{
|
||||
Sizes: &StdSizes{
|
||||
IntSize: int64(c.targetData.TypeAllocSize(c.intType)),
|
||||
@@ -344,12 +370,32 @@ func (c *Compiler) Compile(mainPath string) error {
|
||||
c.mod.NamedFunction("runtime.activateTask").SetLinkage(llvm.ExternalLinkage)
|
||||
c.mod.NamedFunction("runtime.scheduler").SetLinkage(llvm.ExternalLinkage)
|
||||
|
||||
// Load some attributes
|
||||
getAttr := func(attrName string) llvm.Attribute {
|
||||
attrKind := llvm.AttributeKindID(attrName)
|
||||
return c.ctx.CreateEnumAttribute(attrKind, 0)
|
||||
}
|
||||
nocapture := getAttr("nocapture")
|
||||
writeonly := getAttr("writeonly")
|
||||
readonly := getAttr("readonly")
|
||||
|
||||
// Tell the optimizer that runtime.alloc is an allocator, meaning that it
|
||||
// returns values that are never null and never alias to an existing value.
|
||||
for _, name := range []string{"noalias", "nonnull"} {
|
||||
attrKind := llvm.AttributeKindID(name)
|
||||
attr := c.ctx.CreateEnumAttribute(attrKind, 0)
|
||||
c.mod.NamedFunction("runtime.alloc").AddAttributeAtIndex(0, attr)
|
||||
for _, attrName := range []string{"noalias", "nonnull"} {
|
||||
c.mod.NamedFunction("runtime.alloc").AddAttributeAtIndex(0, getAttr(attrName))
|
||||
}
|
||||
|
||||
// See emitNilCheck in asserts.go.
|
||||
c.mod.NamedFunction("runtime.isnil").AddAttributeAtIndex(1, nocapture)
|
||||
|
||||
// Memory copy operations do not capture pointers, even though some weird
|
||||
// pointer arithmetic is happening in the Go implementation.
|
||||
for _, fnName := range []string{"runtime.memcpy", "runtime.memmove"} {
|
||||
fn := c.mod.NamedFunction(fnName)
|
||||
fn.AddAttributeAtIndex(1, nocapture)
|
||||
fn.AddAttributeAtIndex(1, writeonly)
|
||||
fn.AddAttributeAtIndex(2, nocapture)
|
||||
fn.AddAttributeAtIndex(2, readonly)
|
||||
}
|
||||
|
||||
// see: https://reviews.llvm.org/D18355
|
||||
@@ -427,58 +473,8 @@ func (c *Compiler) getLLVMType(goType types.Type) (llvm.Type, error) {
|
||||
return llvm.Type{}, err
|
||||
}
|
||||
return llvm.PointerType(ptrTo, 0), nil
|
||||
case *types.Signature: // function pointer
|
||||
// return value
|
||||
var err error
|
||||
var returnType llvm.Type
|
||||
if typ.Results().Len() == 0 {
|
||||
returnType = c.ctx.VoidType()
|
||||
} else if typ.Results().Len() == 1 {
|
||||
returnType, err = c.getLLVMType(typ.Results().At(0).Type())
|
||||
if err != nil {
|
||||
return llvm.Type{}, err
|
||||
}
|
||||
} else {
|
||||
// Multiple return values. Put them together in a struct.
|
||||
members := make([]llvm.Type, typ.Results().Len())
|
||||
for i := 0; i < typ.Results().Len(); i++ {
|
||||
returnType, err := c.getLLVMType(typ.Results().At(i).Type())
|
||||
if err != nil {
|
||||
return llvm.Type{}, err
|
||||
}
|
||||
members[i] = returnType
|
||||
}
|
||||
returnType = c.ctx.StructType(members, false)
|
||||
}
|
||||
// param values
|
||||
var paramTypes []llvm.Type
|
||||
if typ.Recv() != nil {
|
||||
recv, err := c.getLLVMType(typ.Recv().Type())
|
||||
if err != nil {
|
||||
return llvm.Type{}, err
|
||||
}
|
||||
if recv.StructName() == "runtime._interface" {
|
||||
// This is a call on an interface, not a concrete type.
|
||||
// The receiver is not an interface, but a i8* type.
|
||||
recv = c.i8ptrType
|
||||
}
|
||||
paramTypes = append(paramTypes, c.expandFormalParamType(recv)...)
|
||||
}
|
||||
params := typ.Params()
|
||||
for i := 0; i < params.Len(); i++ {
|
||||
subType, err := c.getLLVMType(params.At(i).Type())
|
||||
if err != nil {
|
||||
return llvm.Type{}, err
|
||||
}
|
||||
paramTypes = append(paramTypes, c.expandFormalParamType(subType)...)
|
||||
}
|
||||
// make a closure type (with a function pointer type inside):
|
||||
// {context, funcptr}
|
||||
paramTypes = append(paramTypes, c.i8ptrType) // context
|
||||
paramTypes = append(paramTypes, c.i8ptrType) // parent coroutine
|
||||
ptr := llvm.PointerType(llvm.FunctionType(returnType, paramTypes, false), c.funcPtrAddrSpace)
|
||||
ptr = c.ctx.StructType([]llvm.Type{c.i8ptrType, ptr}, false)
|
||||
return ptr, nil
|
||||
case *types.Signature: // function value
|
||||
return c.getFuncType(typ)
|
||||
case *types.Slice:
|
||||
elemType, err := c.getLLVMType(typ.Elem())
|
||||
if err != nil {
|
||||
@@ -499,6 +495,43 @@ func (c *Compiler) getLLVMType(goType types.Type) (llvm.Type, error) {
|
||||
}
|
||||
members[i] = member
|
||||
}
|
||||
if len(members) > 2 && typ.Field(0).Name() == "C union" {
|
||||
// Not a normal struct but a C union emitted by cgo.
|
||||
// Such a field name cannot be entered in regular Go code, this must
|
||||
// be manually inserted in the AST so this is safe.
|
||||
maxAlign := 0
|
||||
maxSize := uint64(0)
|
||||
mainType := members[0]
|
||||
for _, member := range members {
|
||||
align := c.targetData.ABITypeAlignment(member)
|
||||
size := c.targetData.TypeAllocSize(member)
|
||||
if align > maxAlign {
|
||||
maxAlign = align
|
||||
mainType = member
|
||||
} else if align == maxAlign && size > maxSize {
|
||||
maxAlign = align
|
||||
maxSize = size
|
||||
mainType = member
|
||||
} else if size > maxSize {
|
||||
maxSize = size
|
||||
}
|
||||
}
|
||||
members = []llvm.Type{mainType}
|
||||
mainTypeSize := c.targetData.TypeAllocSize(mainType)
|
||||
if mainTypeSize < maxSize {
|
||||
members = append(members, llvm.ArrayType(c.ctx.Int8Type(), int(maxSize-mainTypeSize)))
|
||||
}
|
||||
}
|
||||
return c.ctx.StructType(members, false), nil
|
||||
case *types.Tuple:
|
||||
members := make([]llvm.Type, typ.Len())
|
||||
for i := 0; i < typ.Len(); i++ {
|
||||
member, err := c.getLLVMType(typ.At(i).Type())
|
||||
if err != nil {
|
||||
return llvm.Type{}, err
|
||||
}
|
||||
members[i] = member
|
||||
}
|
||||
return c.ctx.StructType(members, false), nil
|
||||
default:
|
||||
return llvm.Type{}, errors.New("todo: unknown type: " + goType.String())
|
||||
@@ -1053,16 +1086,21 @@ func (c *Compiler) parseBuiltin(frame *Frame, args []ssa.Value, callName string,
|
||||
if err != nil {
|
||||
return llvm.Value{}, err
|
||||
}
|
||||
var llvmCap llvm.Value
|
||||
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
|
||||
llvmCap = llvm.ConstInt(c.intType, 0, false)
|
||||
case *types.Slice:
|
||||
return c.builder.CreateExtractValue(value, 2, "cap"), nil
|
||||
llvmCap = c.builder.CreateExtractValue(value, 2, "cap")
|
||||
default:
|
||||
return llvm.Value{}, c.makeError(pos, "todo: cap: unknown type")
|
||||
}
|
||||
if c.targetData.TypeAllocSize(llvmCap.Type()) < c.targetData.TypeAllocSize(c.intType) {
|
||||
llvmCap = c.builder.CreateZExt(llvmCap, c.intType, "len.int")
|
||||
}
|
||||
return llvmCap, nil
|
||||
case "close":
|
||||
return llvm.Value{}, c.emitChanClose(frame, args[0])
|
||||
case "complex":
|
||||
@@ -1253,120 +1291,15 @@ func (c *Compiler) parseCall(frame *Frame, instr *ssa.CallCommon) (llvm.Value, e
|
||||
|
||||
// Try to call the function directly for trivially static calls.
|
||||
if fn := instr.StaticCallee(); fn != nil {
|
||||
if fn.RelString(nil) == "device/arm.Asm" || fn.RelString(nil) == "device/avr.Asm" {
|
||||
// Magic function: insert inline assembly instead of calling it.
|
||||
fnType := llvm.FunctionType(c.ctx.VoidType(), []llvm.Type{}, false)
|
||||
asm := constant.StringVal(instr.Args[0].(*ssa.Const).Value)
|
||||
target := llvm.InlineAsm(fnType, asm, "", true, false, 0)
|
||||
return c.builder.CreateCall(target, nil, ""), nil
|
||||
}
|
||||
|
||||
if fn.RelString(nil) == "device/arm.ReadRegister" {
|
||||
// Magic function: return the given register.
|
||||
fnType := llvm.FunctionType(c.uintptrType, []llvm.Type{}, false)
|
||||
regname := constant.StringVal(instr.Args[0].(*ssa.Const).Value)
|
||||
target := llvm.InlineAsm(fnType, "mov $0, "+regname, "=r", false, false, 0)
|
||||
return c.builder.CreateCall(target, nil, ""), nil
|
||||
}
|
||||
|
||||
if strings.HasPrefix(fn.RelString(nil), "device/arm.SVCall") {
|
||||
// Magic function: inline this call as a SVC instruction.
|
||||
num, _ := constant.Uint64Val(instr.Args[0].(*ssa.Const).Value)
|
||||
args := []llvm.Value{}
|
||||
argTypes := []llvm.Type{}
|
||||
asm := "svc #" + strconv.FormatUint(num, 10)
|
||||
constraints := "={r0}"
|
||||
for i, arg := range instr.Args[1:] {
|
||||
arg = arg.(*ssa.MakeInterface).X
|
||||
if i == 0 {
|
||||
constraints += ",0"
|
||||
} else {
|
||||
constraints += ",{r" + strconv.Itoa(i) + "}"
|
||||
}
|
||||
llvmValue, err := c.parseExpr(frame, arg)
|
||||
if err != nil {
|
||||
return llvm.Value{}, err
|
||||
}
|
||||
args = append(args, llvmValue)
|
||||
argTypes = append(argTypes, llvmValue.Type())
|
||||
}
|
||||
// Implement the ARM calling convention by marking r1-r3 as
|
||||
// clobbered. r0 is used as an output register so doesn't have to be
|
||||
// marked as clobbered.
|
||||
constraints += ",~{r1},~{r2},~{r3}"
|
||||
fnType := llvm.FunctionType(c.uintptrType, argTypes, false)
|
||||
target := llvm.InlineAsm(fnType, asm, constraints, true, false, 0)
|
||||
return c.builder.CreateCall(target, args, ""), nil
|
||||
}
|
||||
|
||||
if fn.RelString(nil) == "device/arm.AsmFull" || fn.RelString(nil) == "device/avr.AsmFull" {
|
||||
asmString := constant.StringVal(instr.Args[0].(*ssa.Const).Value)
|
||||
registers := map[string]llvm.Value{}
|
||||
registerMap := instr.Args[1].(*ssa.MakeMap)
|
||||
for _, r := range *registerMap.Referrers() {
|
||||
switch r := r.(type) {
|
||||
case *ssa.DebugRef:
|
||||
// ignore
|
||||
case *ssa.MapUpdate:
|
||||
if r.Block() != registerMap.Block() {
|
||||
return llvm.Value{}, c.makeError(instr.Pos(), "register value map must be created in the same basic block")
|
||||
}
|
||||
key := constant.StringVal(r.Key.(*ssa.Const).Value)
|
||||
//println("value:", r.Value.(*ssa.MakeInterface).X.String())
|
||||
value, err := c.parseExpr(frame, r.Value.(*ssa.MakeInterface).X)
|
||||
if err != nil {
|
||||
return llvm.Value{}, err
|
||||
}
|
||||
registers[key] = value
|
||||
case *ssa.Call:
|
||||
if r.Common() == instr {
|
||||
break
|
||||
}
|
||||
default:
|
||||
return llvm.Value{}, c.makeError(instr.Pos(), "don't know how to handle argument to inline assembly: "+r.String())
|
||||
}
|
||||
}
|
||||
// TODO: handle dollar signs in asm string
|
||||
registerNumbers := map[string]int{}
|
||||
var err error
|
||||
argTypes := []llvm.Type{}
|
||||
args := []llvm.Value{}
|
||||
constraints := []string{}
|
||||
asmString = regexp.MustCompile("\\{[a-zA-Z]+\\}").ReplaceAllStringFunc(asmString, func(s string) string {
|
||||
// TODO: skip strings like {r4} etc. that look like ARM push/pop
|
||||
// instructions.
|
||||
name := s[1 : len(s)-1]
|
||||
if _, ok := registers[name]; !ok {
|
||||
if err == nil {
|
||||
err = c.makeError(instr.Pos(), "unknown register name: "+name)
|
||||
}
|
||||
return s
|
||||
}
|
||||
if _, ok := registerNumbers[name]; !ok {
|
||||
registerNumbers[name] = len(registerNumbers)
|
||||
argTypes = append(argTypes, registers[name].Type())
|
||||
args = append(args, registers[name])
|
||||
switch registers[name].Type().TypeKind() {
|
||||
case llvm.IntegerTypeKind:
|
||||
constraints = append(constraints, "r")
|
||||
case llvm.PointerTypeKind:
|
||||
constraints = append(constraints, "*m")
|
||||
default:
|
||||
err = c.makeError(instr.Pos(), "unknown type in inline assembly for value: "+name)
|
||||
return s
|
||||
}
|
||||
}
|
||||
return fmt.Sprintf("${%v}", registerNumbers[name])
|
||||
})
|
||||
if err != nil {
|
||||
return llvm.Value{}, err
|
||||
}
|
||||
fnType := llvm.FunctionType(c.ctx.VoidType(), argTypes, false)
|
||||
target := llvm.InlineAsm(fnType, asmString, strings.Join(constraints, ","), true, false, 0)
|
||||
return c.builder.CreateCall(target, args, ""), nil
|
||||
}
|
||||
|
||||
switch fn.RelString(nil) {
|
||||
case "device/arm.ReadRegister":
|
||||
return c.emitReadRegister(instr.Args)
|
||||
case "device/arm.Asm", "device/avr.Asm":
|
||||
return c.emitAsm(instr.Args)
|
||||
case "device/arm.AsmFull", "device/avr.AsmFull":
|
||||
return c.emitAsmFull(frame, instr)
|
||||
case "device/arm.SVCall0", "device/arm.SVCall1", "device/arm.SVCall2", "device/arm.SVCall3", "device/arm.SVCall4":
|
||||
return c.emitSVCall(frame, instr.Args)
|
||||
case "syscall.Syscall", "syscall.Syscall6", "syscall.Syscall9":
|
||||
return c.emitSyscall(frame, instr)
|
||||
}
|
||||
@@ -1376,20 +1309,20 @@ func (c *Compiler) parseCall(frame *Frame, instr *ssa.CallCommon) (llvm.Value, e
|
||||
return llvm.Value{}, c.makeError(instr.Pos(), "undefined function: "+targetFunc.LinkName())
|
||||
}
|
||||
var context llvm.Value
|
||||
// This function call is to a (potential) closure, not a regular
|
||||
// function. See whether it is a closure and if so, call it as such.
|
||||
// Else, supply a dummy nil pointer as the last parameter.
|
||||
if targetFunc.IsExported() {
|
||||
// don't pass a context parameter
|
||||
} else if mkClosure, ok := instr.Value.(*ssa.MakeClosure); ok {
|
||||
// closure is {context, function pointer}
|
||||
closure, err := c.parseExpr(frame, mkClosure)
|
||||
switch value := instr.Value.(type) {
|
||||
case *ssa.Function:
|
||||
// Regular function call. No context is necessary.
|
||||
context = llvm.Undef(c.i8ptrType)
|
||||
case *ssa.MakeClosure:
|
||||
// A call on a func value, but the callee is trivial to find. For
|
||||
// example: immediately applied functions.
|
||||
funcValue, err := c.parseExpr(frame, value)
|
||||
if err != nil {
|
||||
return llvm.Value{}, err
|
||||
}
|
||||
context = c.builder.CreateExtractValue(closure, 0, "")
|
||||
} else {
|
||||
context = llvm.Undef(c.i8ptrType)
|
||||
context = c.extractFuncContext(funcValue)
|
||||
default:
|
||||
panic("StaticCallee returned an unexpected value")
|
||||
}
|
||||
return c.parseFunctionCall(frame, instr.Args, targetFunc.LLVMFn, context, targetFunc.IsExported())
|
||||
}
|
||||
@@ -1403,13 +1336,14 @@ func (c *Compiler) parseCall(frame *Frame, instr *ssa.CallCommon) (llvm.Value, e
|
||||
if err != nil {
|
||||
return llvm.Value{}, err
|
||||
}
|
||||
// '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, "")
|
||||
c.emitNilCheck(frame, value, "fpcall")
|
||||
return c.parseFunctionCall(frame, instr.Args, value, context, false)
|
||||
// This is a func value, which cannot be called directly. We have to
|
||||
// extract the function pointer and context first from the func value.
|
||||
funcPtr, context, err := c.decodeFuncValue(value, instr.Value.Type().(*types.Signature))
|
||||
if err != nil {
|
||||
return llvm.Value{}, err
|
||||
}
|
||||
c.emitNilCheck(frame, funcPtr, "fpcall")
|
||||
return c.parseFunctionCall(frame, instr.Args, funcPtr, context, false)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1475,21 +1409,41 @@ func (c *Compiler) parseExpr(frame *Frame, expr ssa.Value) (llvm.Value, error) {
|
||||
// https://research.swtch.com/interfaces
|
||||
return c.parseExpr(frame, expr.X)
|
||||
case *ssa.ChangeType:
|
||||
// This instruction changes the type, but the underlying value remains
|
||||
// the same. This is often a no-op, but sometimes we have to change the
|
||||
// LLVM type as well.
|
||||
x, err := c.parseExpr(frame, expr.X)
|
||||
if err != nil {
|
||||
return llvm.Value{}, err
|
||||
}
|
||||
// The only case when we need to bitcast is when casting between named
|
||||
// struct types, as those are actually different in LLVM. Let's just
|
||||
// bitcast all struct types for ease of use.
|
||||
if _, ok := expr.Type().Underlying().(*types.Struct); ok {
|
||||
llvmType, err := c.getLLVMType(expr.X.Type())
|
||||
if err != nil {
|
||||
return llvm.Value{}, err
|
||||
}
|
||||
return c.builder.CreateBitCast(x, llvmType, "changetype"), nil
|
||||
llvmType, err := c.getLLVMType(expr.Type())
|
||||
if err != nil {
|
||||
return llvm.Value{}, err
|
||||
}
|
||||
if x.Type() == llvmType {
|
||||
// Different Go type but same LLVM type (for example, named int).
|
||||
// This is the common case.
|
||||
return x, nil
|
||||
}
|
||||
// Figure out what kind of type we need to cast.
|
||||
switch llvmType.TypeKind() {
|
||||
case llvm.StructTypeKind:
|
||||
// Unfortunately, we can't just bitcast structs. We have to
|
||||
// actually create a new struct of the correct type and insert the
|
||||
// values from the previous struct in there.
|
||||
value := llvm.Undef(llvmType)
|
||||
for i := 0; i < llvmType.StructElementTypesCount(); i++ {
|
||||
field := c.builder.CreateExtractValue(x, i, "changetype.field")
|
||||
value = c.builder.CreateInsertValue(value, field, i, "changetype.struct")
|
||||
}
|
||||
return value, nil
|
||||
case llvm.PointerTypeKind:
|
||||
// This can happen with pointers to structs. This case is easy:
|
||||
// simply bitcast the pointer to the destination type.
|
||||
return c.builder.CreateBitCast(x, llvmType, "changetype.pointer"), nil
|
||||
default:
|
||||
return llvm.Value{}, errors.New("todo: unknown ChangeType type: " + expr.X.Type().String())
|
||||
}
|
||||
return x, nil
|
||||
case *ssa.Const:
|
||||
return c.parseConst(frame.fn.LinkName(), expr)
|
||||
case *ssa.Convert:
|
||||
@@ -1510,6 +1464,19 @@ func (c *Compiler) parseExpr(frame *Frame, expr ssa.Value) (llvm.Value, error) {
|
||||
if err != nil {
|
||||
return llvm.Value{}, err
|
||||
}
|
||||
if s := expr.X.Type().Underlying().(*types.Struct); s.NumFields() > 2 && s.Field(0).Name() == "C union" {
|
||||
// Extract a field from a CGo union.
|
||||
// This could be done directly, but as this is a very infrequent
|
||||
// operation it's much easier to bitcast it through an alloca.
|
||||
resultType, err := c.getLLVMType(expr.Type())
|
||||
if err != nil {
|
||||
return llvm.Value{}, err
|
||||
}
|
||||
alloca := c.builder.CreateAlloca(value.Type(), "")
|
||||
c.builder.CreateStore(value, alloca)
|
||||
bitcast := c.builder.CreateBitCast(alloca, llvm.PointerType(resultType, 0), "")
|
||||
return c.builder.CreateLoad(bitcast, ""), nil
|
||||
}
|
||||
result := c.builder.CreateExtractValue(value, expr.Field, "")
|
||||
return result, nil
|
||||
case *ssa.FieldAddr:
|
||||
@@ -1517,27 +1484,34 @@ func (c *Compiler) parseExpr(frame *Frame, expr ssa.Value) (llvm.Value, error) {
|
||||
if err != nil {
|
||||
return llvm.Value{}, err
|
||||
}
|
||||
indices := []llvm.Value{
|
||||
llvm.ConstInt(c.ctx.Int32Type(), 0, false),
|
||||
llvm.ConstInt(c.ctx.Int32Type(), uint64(expr.Field), false),
|
||||
}
|
||||
// Check for nil pointer before calculating the address, from the spec:
|
||||
// > For an operand x of type T, the address operation &x generates a
|
||||
// > pointer of type *T to x. [...] If the evaluation of x would cause a
|
||||
// > run-time panic, then the evaluation of &x does too.
|
||||
c.emitNilCheck(frame, val, "gep")
|
||||
return c.builder.CreateGEP(val, indices, ""), nil
|
||||
if s := expr.X.Type().(*types.Pointer).Elem().Underlying().(*types.Struct); s.NumFields() > 2 && s.Field(0).Name() == "C union" {
|
||||
// This is not a regular struct but actually an union.
|
||||
// That simplifies things, as we can just bitcast the pointer to the
|
||||
// right type.
|
||||
ptrType, err := c.getLLVMType(expr.Type())
|
||||
if err != nil {
|
||||
return llvm.Value{}, nil
|
||||
}
|
||||
return c.builder.CreateBitCast(val, ptrType, ""), nil
|
||||
} else {
|
||||
// Do a GEP on the pointer to get the field address.
|
||||
indices := []llvm.Value{
|
||||
llvm.ConstInt(c.ctx.Int32Type(), 0, false),
|
||||
llvm.ConstInt(c.ctx.Int32Type(), uint64(expr.Field), false),
|
||||
}
|
||||
return c.builder.CreateGEP(val, indices, ""), nil
|
||||
}
|
||||
case *ssa.Function:
|
||||
fn := c.ir.GetFunction(expr)
|
||||
if fn.IsExported() {
|
||||
return llvm.Value{}, c.makeError(expr.Pos(), "cannot use an exported function as value")
|
||||
}
|
||||
// Create closure for function pointer.
|
||||
// Closure is: {context, function pointer}
|
||||
return c.ctx.ConstStruct([]llvm.Value{
|
||||
llvm.Undef(c.i8ptrType),
|
||||
fn.LLVMFn,
|
||||
}, false), nil
|
||||
return c.createFuncValue(fn.LLVMFn, llvm.Undef(c.i8ptrType), fn.Signature)
|
||||
case *ssa.Global:
|
||||
value := c.ir.GetGlobal(expr).LLVMGlobal
|
||||
if value.IsNil() {
|
||||
@@ -1653,8 +1627,6 @@ func (c *Compiler) parseExpr(frame *Frame, expr ssa.Value) (llvm.Value, error) {
|
||||
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)
|
||||
@@ -1705,37 +1677,7 @@ func (c *Compiler) parseExpr(frame *Frame, expr ssa.Value) (llvm.Value, error) {
|
||||
}
|
||||
|
||||
// Bounds checking.
|
||||
if !frame.fn.IsNoBounds() {
|
||||
checkFunc := "sliceBoundsCheckMake"
|
||||
capacityType := c.uintptrType
|
||||
capacityTypeWidth := capacityType.IntTypeWidth()
|
||||
if sliceLen.Type().IntTypeWidth() > capacityTypeWidth || sliceCap.Type().IntTypeWidth() > capacityTypeWidth {
|
||||
// System that is less than 64bit, meaning that the slice make
|
||||
// params are bigger than uintptr.
|
||||
checkFunc = "sliceBoundsCheckMake64"
|
||||
capacityType = c.ctx.Int64Type()
|
||||
capacityTypeWidth = capacityType.IntTypeWidth()
|
||||
}
|
||||
if sliceLen.Type().IntTypeWidth() < capacityTypeWidth {
|
||||
if expr.Len.Type().(*types.Basic).Info()&types.IsUnsigned != 0 {
|
||||
sliceLen = c.builder.CreateZExt(sliceLen, capacityType, "")
|
||||
} else {
|
||||
sliceLen = c.builder.CreateSExt(sliceLen, capacityType, "")
|
||||
}
|
||||
}
|
||||
if sliceCap.Type().IntTypeWidth() < capacityTypeWidth {
|
||||
if expr.Cap.Type().(*types.Basic).Info()&types.IsUnsigned != 0 {
|
||||
sliceCap = c.builder.CreateZExt(sliceCap, capacityType, "")
|
||||
} else {
|
||||
sliceCap = c.builder.CreateSExt(sliceCap, capacityType, "")
|
||||
}
|
||||
}
|
||||
maxSliceSize := maxSize
|
||||
if elemSize != 0 { // avoid divide by zero
|
||||
maxSliceSize = llvm.ConstSDiv(maxSize, llvm.ConstInt(c.uintptrType, elemSize, false))
|
||||
}
|
||||
c.createRuntimeCall(checkFunc, []llvm.Value{sliceLen, sliceCap, maxSliceSize}, "")
|
||||
}
|
||||
c.emitSliceBoundsCheck(frame, maxSize, sliceLen, sliceCap, expr.Len.Type().(*types.Basic), expr.Cap.Type().(*types.Basic))
|
||||
|
||||
// Allocate the backing array.
|
||||
// TODO: escape analysis
|
||||
@@ -1747,9 +1689,15 @@ func (c *Compiler) parseExpr(frame *Frame, expr ssa.Value) (llvm.Value, error) {
|
||||
slicePtr := c.createRuntimeCall("alloc", []llvm.Value{sliceSize}, "makeslice.buf")
|
||||
slicePtr = c.builder.CreateBitCast(slicePtr, llvm.PointerType(llvmElemType, 0), "makeslice.array")
|
||||
|
||||
if c.targetData.TypeAllocSize(sliceLen.Type()) > c.targetData.TypeAllocSize(c.uintptrType) {
|
||||
sliceLen = c.builder.CreateTrunc(sliceLen, c.uintptrType, "")
|
||||
sliceCap = c.builder.CreateTrunc(sliceCap, c.uintptrType, "")
|
||||
// Extend or truncate if necessary. This is safe as we've already done
|
||||
// the bounds check.
|
||||
sliceLen, err = c.parseConvert(expr.Len.Type(), types.Typ[types.Uintptr], sliceLen, expr.Pos())
|
||||
if err != nil {
|
||||
return llvm.Value{}, err
|
||||
}
|
||||
sliceCap, err = c.parseConvert(expr.Cap.Type(), types.Typ[types.Uintptr], sliceCap, expr.Pos())
|
||||
if err != nil {
|
||||
return llvm.Value{}, err
|
||||
}
|
||||
|
||||
// Create the slice.
|
||||
@@ -1821,6 +1769,29 @@ func (c *Compiler) parseExpr(frame *Frame, expr ssa.Value) (llvm.Value, error) {
|
||||
}
|
||||
c.builder.CreateStore(zero, it)
|
||||
return it, nil
|
||||
case *ssa.Select:
|
||||
if len(expr.States) == 0 {
|
||||
// Shortcuts for some simple selects.
|
||||
llvmType, err := c.getLLVMType(expr.Type())
|
||||
if err != nil {
|
||||
return llvm.Value{}, err
|
||||
}
|
||||
if expr.Blocking {
|
||||
// Blocks forever:
|
||||
// select {}
|
||||
c.createRuntimeCall("deadlockStub", nil, "")
|
||||
return llvm.Undef(llvmType), nil
|
||||
} else {
|
||||
// No-op:
|
||||
// select {
|
||||
// default:
|
||||
// }
|
||||
retval := llvm.Undef(llvmType)
|
||||
retval = c.builder.CreateInsertValue(retval, llvm.ConstInt(c.intType, 0xffffffffffffffff, true), 0, "")
|
||||
return retval, nil // {-1, false}
|
||||
}
|
||||
}
|
||||
return llvm.Value{}, c.makeError(expr.Pos(), "unimplemented: "+expr.String())
|
||||
case *ssa.Slice:
|
||||
if expr.Max != nil {
|
||||
return llvm.Value{}, c.makeError(expr.Pos(), "todo: full slice expressions (with max): "+expr.Type().String())
|
||||
@@ -1847,8 +1818,8 @@ func (c *Compiler) parseExpr(frame *Frame, expr ssa.Value) (llvm.Value, error) {
|
||||
}
|
||||
}
|
||||
} else {
|
||||
lowType = types.Typ[types.Int]
|
||||
low = llvm.ConstInt(c.intType, 0, false)
|
||||
lowType = types.Typ[types.Uintptr]
|
||||
low = llvm.ConstInt(c.uintptrType, 0, false)
|
||||
}
|
||||
|
||||
if expr.High != nil {
|
||||
@@ -1883,6 +1854,8 @@ func (c *Compiler) parseExpr(frame *Frame, expr ssa.Value) (llvm.Value, error) {
|
||||
|
||||
c.emitSliceBoundsCheck(frame, llvmLen, low, high, lowType, highType)
|
||||
|
||||
// Truncate ints bigger than uintptr. This is after the bounds
|
||||
// check so it's safe.
|
||||
if c.targetData.TypeAllocSize(high.Type()) > c.targetData.TypeAllocSize(c.uintptrType) {
|
||||
high = c.builder.CreateTrunc(high, c.uintptrType, "")
|
||||
}
|
||||
@@ -1915,6 +1888,8 @@ func (c *Compiler) parseExpr(frame *Frame, expr ssa.Value) (llvm.Value, error) {
|
||||
|
||||
c.emitSliceBoundsCheck(frame, oldCap, low, high, lowType, highType)
|
||||
|
||||
// Truncate ints bigger than uintptr. This is after the bounds
|
||||
// check so it's safe.
|
||||
if c.targetData.TypeAllocSize(low.Type()) > c.targetData.TypeAllocSize(c.uintptrType) {
|
||||
low = c.builder.CreateTrunc(low, c.uintptrType, "")
|
||||
}
|
||||
@@ -1948,6 +1923,15 @@ func (c *Compiler) parseExpr(frame *Frame, expr ssa.Value) (llvm.Value, error) {
|
||||
|
||||
c.emitSliceBoundsCheck(frame, oldLen, low, high, lowType, highType)
|
||||
|
||||
// Truncate ints bigger than uintptr. This is after the bounds
|
||||
// check so it's safe.
|
||||
if c.targetData.TypeAllocSize(low.Type()) > c.targetData.TypeAllocSize(c.uintptrType) {
|
||||
low = c.builder.CreateTrunc(low, c.uintptrType, "")
|
||||
}
|
||||
if c.targetData.TypeAllocSize(high.Type()) > c.targetData.TypeAllocSize(c.uintptrType) {
|
||||
high = c.builder.CreateTrunc(high, c.uintptrType, "")
|
||||
}
|
||||
|
||||
newPtr := c.builder.CreateGEP(oldPtr, []llvm.Value{low}, "")
|
||||
newLen := c.builder.CreateSub(high, low, "")
|
||||
str, err := c.getZeroValue(c.mod.GetTypeByName("runtime._string"))
|
||||
@@ -2154,12 +2138,13 @@ func (c *Compiler) parseBinOp(op token.Token, typ types.Type, x, y llvm.Value, p
|
||||
return llvm.Value{}, c.makeError(pos, "todo: unknown basic type in binop: "+typ.String())
|
||||
}
|
||||
case *types.Signature:
|
||||
// Extract function pointers from the function values (closures).
|
||||
// Get raw scalars from the function value and compare those.
|
||||
// Function values may be implemented in multiple ways, but they all
|
||||
// have some way of getting a scalar value identifying the function.
|
||||
// This is safe: function pointers are generally not comparable
|
||||
// against each other, only against nil. So one or both has to be
|
||||
// nil, so we can ignore the closure context.
|
||||
x = c.builder.CreateExtractValue(x, 1, "")
|
||||
y = c.builder.CreateExtractValue(y, 1, "")
|
||||
// against each other, only against nil. So one of these has to be nil.
|
||||
x = c.extractFuncScalar(x)
|
||||
y = c.extractFuncScalar(y)
|
||||
switch op {
|
||||
case token.EQL: // ==
|
||||
return c.builder.CreateICmp(llvm.IntEQ, x, y, ""), nil
|
||||
@@ -2410,6 +2395,27 @@ func (c *Compiler) parseConvert(typeFrom, typeTo types.Type, value llvm.Value, p
|
||||
if isPtrFrom && !isPtrTo {
|
||||
return c.builder.CreatePtrToInt(value, llvmTypeTo, ""), nil
|
||||
} else if !isPtrFrom && isPtrTo {
|
||||
if !value.IsABinaryOperator().IsNil() && value.InstructionOpcode() == llvm.Add {
|
||||
// This is probably a pattern like the following:
|
||||
// unsafe.Pointer(uintptr(ptr) + index)
|
||||
// Used in functions like memmove etc. for lack of pointer
|
||||
// arithmetic. Convert it to real pointer arithmatic here.
|
||||
ptr := value.Operand(0)
|
||||
index := value.Operand(1)
|
||||
if !index.IsAPtrToIntInst().IsNil() {
|
||||
// Swap if necessary, if ptr and index are reversed.
|
||||
ptr, index = index, ptr
|
||||
}
|
||||
if !ptr.IsAPtrToIntInst().IsNil() {
|
||||
origptr := ptr.Operand(0)
|
||||
if origptr.Type() == c.i8ptrType {
|
||||
// This pointer can be calculated from the original
|
||||
// ptrtoint instruction with a GEP. The leftover inttoptr
|
||||
// instruction is trivial to optimize away.
|
||||
return c.builder.CreateGEP(origptr, []llvm.Value{index}, ""), nil
|
||||
}
|
||||
}
|
||||
}
|
||||
return c.builder.CreateIntToPtr(value, llvmTypeTo, ""), nil
|
||||
}
|
||||
|
||||
@@ -2456,7 +2462,7 @@ func (c *Compiler) parseConvert(typeFrom, typeTo types.Type, value llvm.Value, p
|
||||
// Conversion between two integers.
|
||||
if sizeFrom > sizeTo {
|
||||
return c.builder.CreateTrunc(value, llvmTypeTo, ""), nil
|
||||
} else if typeTo.Info()&types.IsUnsigned != 0 { // if unsigned
|
||||
} else if typeFrom.Info()&types.IsUnsigned != 0 { // if unsigned
|
||||
return c.builder.CreateZExt(value, llvmTypeTo, ""), nil
|
||||
} else { // if signed
|
||||
return c.builder.CreateSExt(value, llvmTypeTo, ""), nil
|
||||
@@ -2536,81 +2542,6 @@ func (c *Compiler) parseConvert(typeFrom, typeTo types.Type, value llvm.Value, p
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Compiler) parseMakeClosure(frame *Frame, expr *ssa.MakeClosure) (llvm.Value, error) {
|
||||
if len(expr.Bindings) == 0 {
|
||||
panic("unexpected: MakeClosure without bound variables")
|
||||
}
|
||||
f := c.ir.GetFunction(expr.Fn.(*ssa.Function))
|
||||
|
||||
// Collect all bound variables.
|
||||
boundVars := make([]llvm.Value, 0, len(expr.Bindings))
|
||||
boundVarTypes := make([]llvm.Type, 0, len(expr.Bindings))
|
||||
for _, binding := range expr.Bindings {
|
||||
// The context stores the bound variables.
|
||||
llvmBoundVar, err := c.parseExpr(frame, binding)
|
||||
if err != nil {
|
||||
return llvm.Value{}, err
|
||||
}
|
||||
boundVars = append(boundVars, llvmBoundVar)
|
||||
boundVarTypes = append(boundVarTypes, llvmBoundVar.Type())
|
||||
}
|
||||
contextType := c.ctx.StructType(boundVarTypes, false)
|
||||
|
||||
// Allocate memory for the context.
|
||||
contextAlloc := llvm.Value{}
|
||||
contextHeapAlloc := llvm.Value{}
|
||||
if c.targetData.TypeAllocSize(contextType) <= c.targetData.TypeAllocSize(c.i8ptrType) {
|
||||
// Context fits in a pointer - e.g. when it is a pointer. Store it
|
||||
// directly in the stack after a convert.
|
||||
// Because contextType is a struct and we have to cast it to a *i8,
|
||||
// store it in an alloca first for bitcasting (store+bitcast+load).
|
||||
contextAlloc = c.builder.CreateAlloca(contextType, "")
|
||||
} else {
|
||||
// Context is bigger than a pointer, so allocate it on the heap.
|
||||
size := c.targetData.TypeAllocSize(contextType)
|
||||
sizeValue := llvm.ConstInt(c.uintptrType, size, false)
|
||||
contextHeapAlloc = c.createRuntimeCall("alloc", []llvm.Value{sizeValue}, "")
|
||||
contextAlloc = c.builder.CreateBitCast(contextHeapAlloc, llvm.PointerType(contextType, 0), "")
|
||||
}
|
||||
|
||||
// Store all bound variables in the alloca or heap pointer.
|
||||
for i, boundVar := range boundVars {
|
||||
indices := []llvm.Value{
|
||||
llvm.ConstInt(c.ctx.Int32Type(), 0, false),
|
||||
llvm.ConstInt(c.ctx.Int32Type(), uint64(i), false),
|
||||
}
|
||||
gep := c.builder.CreateInBoundsGEP(contextAlloc, indices, "")
|
||||
c.builder.CreateStore(boundVar, gep)
|
||||
}
|
||||
|
||||
context := llvm.Value{}
|
||||
if c.targetData.TypeAllocSize(contextType) <= c.targetData.TypeAllocSize(c.i8ptrType) {
|
||||
// Load value (as *i8) from the alloca.
|
||||
contextAlloc = c.builder.CreateBitCast(contextAlloc, llvm.PointerType(c.i8ptrType, 0), "")
|
||||
context = c.builder.CreateLoad(contextAlloc, "")
|
||||
} else {
|
||||
// Get the original heap allocation pointer, which already is an
|
||||
// *i8.
|
||||
context = contextHeapAlloc
|
||||
}
|
||||
|
||||
// Get the function signature type, which is a closure type.
|
||||
// A closure is a tuple of {context, function pointer}.
|
||||
typ, err := c.getLLVMType(f.Signature)
|
||||
if err != nil {
|
||||
return llvm.Value{}, err
|
||||
}
|
||||
|
||||
// Create the closure, which is a struct: {context, function pointer}.
|
||||
closure, err := c.getZeroValue(typ)
|
||||
if err != nil {
|
||||
return llvm.Value{}, err
|
||||
}
|
||||
closure = c.builder.CreateInsertValue(closure, f.LLVMFn, 1, "")
|
||||
closure = c.builder.CreateInsertValue(closure, context, 0, "")
|
||||
return closure, nil
|
||||
}
|
||||
|
||||
func (c *Compiler) parseUnOp(frame *Frame, unop *ssa.UnOp) (llvm.Value, error) {
|
||||
x, err := c.parseExpr(frame, unop.X)
|
||||
if err != nil {
|
||||
@@ -2645,6 +2576,9 @@ func (c *Compiler) parseUnOp(frame *Frame, unop *ssa.UnOp) (llvm.Value, error) {
|
||||
global := c.ir.GetGlobal(unop.X.(*ssa.Global))
|
||||
name := global.LinkName()[:len(global.LinkName())-len("$funcaddr")]
|
||||
fn := c.mod.NamedFunction(name)
|
||||
if fn.IsNil() {
|
||||
return llvm.Value{}, c.makeError(unop.Pos(), "cgo function not found: "+name)
|
||||
}
|
||||
return c.builder.CreateBitCast(fn, c.i8ptrType, ""), nil
|
||||
} else {
|
||||
c.emitNilCheck(frame, x, "deref")
|
||||
|
||||
@@ -0,0 +1,269 @@
|
||||
package compiler
|
||||
|
||||
// This file lowers func values into their final form. This is necessary for
|
||||
// funcValueSwitch, which needs full program analysis.
|
||||
|
||||
import (
|
||||
"sort"
|
||||
"strconv"
|
||||
|
||||
"tinygo.org/x/go-llvm"
|
||||
)
|
||||
|
||||
// funcSignatureInfo keeps information about a single signature and its uses.
|
||||
type funcSignatureInfo struct {
|
||||
sig llvm.Value // *uint8 to identify the signature
|
||||
funcValueWithSignatures []llvm.Value // slice of runtime.funcValueWithSignature
|
||||
}
|
||||
|
||||
// funcWithUses keeps information about a single function used as func value and
|
||||
// the assigned function ID. More commonly used functions are assigned a lower
|
||||
// ID.
|
||||
type funcWithUses struct {
|
||||
funcPtr llvm.Value
|
||||
useCount int // how often this function is used in a func value
|
||||
id int // assigned ID
|
||||
}
|
||||
|
||||
// Slice to sort functions by their use counts, or else their name if they're
|
||||
// used equally often.
|
||||
type funcWithUsesList []*funcWithUses
|
||||
|
||||
func (l funcWithUsesList) Len() int { return len(l) }
|
||||
func (l funcWithUsesList) Less(i, j int) bool {
|
||||
if l[i].useCount != l[j].useCount {
|
||||
// return the reverse: we want the highest use counts sorted first
|
||||
return l[i].useCount > l[j].useCount
|
||||
}
|
||||
iName := l[i].funcPtr.Name()
|
||||
jName := l[j].funcPtr.Name()
|
||||
return iName < jName
|
||||
}
|
||||
func (l funcWithUsesList) Swap(i, j int) {
|
||||
l[i], l[j] = l[j], l[i]
|
||||
}
|
||||
|
||||
// LowerFuncValue lowers the runtime.funcValueWithSignature type and
|
||||
// runtime.getFuncPtr function to their final form.
|
||||
func (c *Compiler) LowerFuncValues() {
|
||||
if c.funcImplementation() != funcValueSwitch {
|
||||
return
|
||||
}
|
||||
|
||||
// Find all func values used in the program with their signatures.
|
||||
funcValueWithSignaturePtr := llvm.PointerType(c.mod.GetTypeByName("runtime.funcValueWithSignature"), 0)
|
||||
signatures := map[string]*funcSignatureInfo{}
|
||||
for global := c.mod.FirstGlobal(); !global.IsNil(); global = llvm.NextGlobal(global) {
|
||||
if global.Type() != funcValueWithSignaturePtr {
|
||||
continue
|
||||
}
|
||||
sig := llvm.ConstExtractValue(global.Initializer(), []uint32{1})
|
||||
name := sig.Name()
|
||||
if info, ok := signatures[name]; ok {
|
||||
info.funcValueWithSignatures = append(info.funcValueWithSignatures, global)
|
||||
} else {
|
||||
signatures[name] = &funcSignatureInfo{
|
||||
sig: sig,
|
||||
funcValueWithSignatures: []llvm.Value{global},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Sort the signatures, for deterministic execution.
|
||||
names := make([]string, 0, len(signatures))
|
||||
for name := range signatures {
|
||||
names = append(names, name)
|
||||
}
|
||||
sort.Strings(names)
|
||||
|
||||
for _, name := range names {
|
||||
info := signatures[name]
|
||||
functions := make(funcWithUsesList, len(info.funcValueWithSignatures))
|
||||
for i, use := range info.funcValueWithSignatures {
|
||||
var useCount int
|
||||
for _, use2 := range getUses(use) {
|
||||
useCount += len(getUses(use2))
|
||||
}
|
||||
functions[i] = &funcWithUses{
|
||||
funcPtr: llvm.ConstExtractValue(use.Initializer(), []uint32{0}).Operand(0),
|
||||
useCount: useCount,
|
||||
}
|
||||
}
|
||||
sort.Sort(functions)
|
||||
|
||||
for i, fn := range functions {
|
||||
fn.id = i + 1
|
||||
for _, ptrtoint := range getUses(fn.funcPtr) {
|
||||
if ptrtoint.IsAConstantExpr().IsNil() || ptrtoint.Opcode() != llvm.PtrToInt {
|
||||
continue
|
||||
}
|
||||
for _, funcValueWithSignatureConstant := range getUses(ptrtoint) {
|
||||
for _, funcValueWithSignatureGlobal := range getUses(funcValueWithSignatureConstant) {
|
||||
for _, use := range getUses(funcValueWithSignatureGlobal) {
|
||||
if ptrtoint.IsAConstantExpr().IsNil() || ptrtoint.Opcode() != llvm.PtrToInt {
|
||||
panic("expected const ptrtoint")
|
||||
}
|
||||
use.ReplaceAllUsesWith(llvm.ConstInt(c.uintptrType, uint64(fn.id), false))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for _, getFuncPtrCall := range getUses(info.sig) {
|
||||
if getFuncPtrCall.IsACallInst().IsNil() {
|
||||
continue
|
||||
}
|
||||
if getFuncPtrCall.CalledValue().Name() != "runtime.getFuncPtr" {
|
||||
panic("expected all call uses to be runtime.getFuncPtr")
|
||||
}
|
||||
funcID := getFuncPtrCall.Operand(1)
|
||||
switch len(functions) {
|
||||
case 0:
|
||||
// There are no functions used in a func value that implement
|
||||
// this signature. The only possible value is a nil value.
|
||||
for _, inttoptr := range getUses(getFuncPtrCall) {
|
||||
if inttoptr.IsAIntToPtrInst().IsNil() {
|
||||
panic("expected inttoptr")
|
||||
}
|
||||
nilptr := llvm.ConstPointerNull(inttoptr.Type())
|
||||
inttoptr.ReplaceAllUsesWith(nilptr)
|
||||
inttoptr.EraseFromParentAsInstruction()
|
||||
}
|
||||
getFuncPtrCall.EraseFromParentAsInstruction()
|
||||
case 1:
|
||||
// There is exactly one function with this signature that is
|
||||
// used in a func value. The func value itself can be either nil
|
||||
// or this one function.
|
||||
c.builder.SetInsertPointBefore(getFuncPtrCall)
|
||||
zero := llvm.ConstInt(c.uintptrType, 0, false)
|
||||
isnil := c.builder.CreateICmp(llvm.IntEQ, funcID, zero, "")
|
||||
funcPtrNil := llvm.ConstPointerNull(functions[0].funcPtr.Type())
|
||||
funcPtr := c.builder.CreateSelect(isnil, funcPtrNil, functions[0].funcPtr, "")
|
||||
for _, inttoptr := range getUses(getFuncPtrCall) {
|
||||
if inttoptr.IsAIntToPtrInst().IsNil() {
|
||||
panic("expected inttoptr")
|
||||
}
|
||||
inttoptr.ReplaceAllUsesWith(funcPtr)
|
||||
inttoptr.EraseFromParentAsInstruction()
|
||||
}
|
||||
getFuncPtrCall.EraseFromParentAsInstruction()
|
||||
default:
|
||||
// There are multiple functions used in a func value that
|
||||
// implement this signature.
|
||||
// What we'll do is transform the following:
|
||||
// rawPtr := runtime.getFuncPtr(fn)
|
||||
// if func.rawPtr == nil {
|
||||
// runtime.nilpanic()
|
||||
// }
|
||||
// result := func.rawPtr(...args, func.context)
|
||||
// into this:
|
||||
// if false {
|
||||
// runtime.nilpanic()
|
||||
// }
|
||||
// var result // Phi
|
||||
// switch fn.id {
|
||||
// case 0:
|
||||
// runtime.nilpanic()
|
||||
// case 1:
|
||||
// result = call first implementation...
|
||||
// case 2:
|
||||
// result = call second implementation...
|
||||
// default:
|
||||
// unreachable
|
||||
// }
|
||||
|
||||
// Remove some casts, checks, and the old call which we're going
|
||||
// to replace.
|
||||
var funcCall llvm.Value
|
||||
for _, inttoptr := range getUses(getFuncPtrCall) {
|
||||
if inttoptr.IsAIntToPtrInst().IsNil() {
|
||||
panic("expected inttoptr")
|
||||
}
|
||||
for _, ptrUse := range getUses(inttoptr) {
|
||||
if !ptrUse.IsABitCastInst().IsNil() {
|
||||
for _, bitcastUse := range getUses(ptrUse) {
|
||||
if bitcastUse.IsACallInst().IsNil() || bitcastUse.CalledValue().Name() != "runtime.isnil" {
|
||||
panic("expected a call to runtime.isnil")
|
||||
}
|
||||
bitcastUse.ReplaceAllUsesWith(llvm.ConstInt(c.ctx.Int1Type(), 0, false))
|
||||
bitcastUse.EraseFromParentAsInstruction()
|
||||
}
|
||||
ptrUse.EraseFromParentAsInstruction()
|
||||
} else if !ptrUse.IsACallInst().IsNil() && ptrUse.CalledValue() == inttoptr {
|
||||
if !funcCall.IsNil() {
|
||||
panic("multiple calls on a single runtime.getFuncPtr")
|
||||
}
|
||||
funcCall = ptrUse
|
||||
} else {
|
||||
panic("unexpected getFuncPtrCall")
|
||||
}
|
||||
}
|
||||
}
|
||||
if funcCall.IsNil() {
|
||||
panic("expected exactly one call use of a runtime.getFuncPtr")
|
||||
}
|
||||
|
||||
// The block that cannot be reached with correct funcValues (to
|
||||
// help the optimizer).
|
||||
c.builder.SetInsertPointBefore(funcCall)
|
||||
defaultBlock := llvm.AddBasicBlock(funcCall.InstructionParent().Parent(), "func.default")
|
||||
c.builder.SetInsertPointAtEnd(defaultBlock)
|
||||
c.builder.CreateUnreachable()
|
||||
|
||||
// Create the switch.
|
||||
c.builder.SetInsertPointBefore(funcCall)
|
||||
sw := c.builder.CreateSwitch(funcID, defaultBlock, len(functions)+1)
|
||||
|
||||
// Split right after the switch. We will need to insert a few
|
||||
// basic blocks in this gap.
|
||||
nextBlock := c.splitBasicBlock(sw, llvm.NextBasicBlock(sw.InstructionParent()), "func.next")
|
||||
|
||||
// The 0 case, which is actually a nil check.
|
||||
nilBlock := llvm.InsertBasicBlock(nextBlock, "func.nil")
|
||||
c.builder.SetInsertPointAtEnd(nilBlock)
|
||||
c.createRuntimeCall("nilpanic", nil, "")
|
||||
c.builder.CreateUnreachable()
|
||||
sw.AddCase(llvm.ConstInt(c.uintptrType, 0, false), nilBlock)
|
||||
|
||||
// Gather the list of parameters for every call we're going to
|
||||
// make.
|
||||
callParams := make([]llvm.Value, funcCall.OperandsCount()-1)
|
||||
for i := range callParams {
|
||||
callParams[i] = funcCall.Operand(i)
|
||||
}
|
||||
|
||||
// If the call produces a value, we need to get it using a PHI
|
||||
// node.
|
||||
phiBlocks := make([]llvm.BasicBlock, len(functions))
|
||||
phiValues := make([]llvm.Value, len(functions))
|
||||
for i, fn := range functions {
|
||||
// Insert a switch case.
|
||||
bb := llvm.InsertBasicBlock(nextBlock, "func.call"+strconv.Itoa(fn.id))
|
||||
c.builder.SetInsertPointAtEnd(bb)
|
||||
result := c.builder.CreateCall(fn.funcPtr, callParams, "")
|
||||
c.builder.CreateBr(nextBlock)
|
||||
sw.AddCase(llvm.ConstInt(c.uintptrType, uint64(fn.id), false), bb)
|
||||
phiBlocks[i] = bb
|
||||
phiValues[i] = result
|
||||
}
|
||||
// Create the PHI node so that the call result flows into the
|
||||
// next block (after the split). This is only necessary when the
|
||||
// call produced a value.
|
||||
if funcCall.Type().TypeKind() != llvm.VoidTypeKind {
|
||||
c.builder.SetInsertPointBefore(nextBlock.FirstInstruction())
|
||||
phi := c.builder.CreatePHI(funcCall.Type(), "")
|
||||
phi.AddIncoming(phiValues, phiBlocks)
|
||||
funcCall.ReplaceAllUsesWith(phi)
|
||||
}
|
||||
|
||||
// Finally, remove the old instructions.
|
||||
funcCall.EraseFromParentAsInstruction()
|
||||
for _, inttoptr := range getUses(getFuncPtrCall) {
|
||||
inttoptr.EraseFromParentAsInstruction()
|
||||
}
|
||||
getFuncPtrCall.EraseFromParentAsInstruction()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,265 @@
|
||||
package compiler
|
||||
|
||||
// This file implements function values and closures. It may need some lowering
|
||||
// in a later step, see func-lowering.go.
|
||||
|
||||
import (
|
||||
"go/types"
|
||||
|
||||
"golang.org/x/tools/go/ssa"
|
||||
"tinygo.org/x/go-llvm"
|
||||
)
|
||||
|
||||
type funcValueImplementation int
|
||||
|
||||
const (
|
||||
funcValueNone funcValueImplementation = iota
|
||||
|
||||
// A func value is implemented as a pair of pointers:
|
||||
// {context, function pointer}
|
||||
// where the context may be a pointer to a heap-allocated struct containing
|
||||
// the free variables, or it may be undef if the function being pointed to
|
||||
// doesn't need a context. The function pointer is a regular function
|
||||
// pointer.
|
||||
funcValueDoubleword
|
||||
|
||||
// As funcValueDoubleword, but with the function pointer replaced by a
|
||||
// unique ID per function signature. Function values are called by using a
|
||||
// switch statement and choosing which function to call.
|
||||
funcValueSwitch
|
||||
)
|
||||
|
||||
// funcImplementation picks an appropriate func value implementation for the
|
||||
// target.
|
||||
func (c *Compiler) funcImplementation() funcValueImplementation {
|
||||
if c.GOARCH == "wasm" {
|
||||
return funcValueSwitch
|
||||
} else {
|
||||
return funcValueDoubleword
|
||||
}
|
||||
}
|
||||
|
||||
// createFuncValue creates a function value from a raw function pointer with no
|
||||
// context.
|
||||
func (c *Compiler) createFuncValue(funcPtr, context llvm.Value, sig *types.Signature) (llvm.Value, error) {
|
||||
var funcValueScalar llvm.Value
|
||||
switch c.funcImplementation() {
|
||||
case funcValueDoubleword:
|
||||
// Closure is: {context, function pointer}
|
||||
funcValueScalar = funcPtr
|
||||
case funcValueSwitch:
|
||||
sigGlobal := c.getFuncSignature(sig)
|
||||
funcValueWithSignatureGlobalName := funcPtr.Name() + "$withSignature"
|
||||
funcValueWithSignatureGlobal := c.mod.NamedGlobal(funcValueWithSignatureGlobalName)
|
||||
if funcValueWithSignatureGlobal.IsNil() {
|
||||
funcValueWithSignatureType := c.mod.GetTypeByName("runtime.funcValueWithSignature")
|
||||
funcValueWithSignature := llvm.ConstNamedStruct(funcValueWithSignatureType, []llvm.Value{
|
||||
llvm.ConstPtrToInt(funcPtr, c.uintptrType),
|
||||
sigGlobal,
|
||||
})
|
||||
funcValueWithSignatureGlobal = llvm.AddGlobal(c.mod, funcValueWithSignatureType, funcValueWithSignatureGlobalName)
|
||||
funcValueWithSignatureGlobal.SetInitializer(funcValueWithSignature)
|
||||
funcValueWithSignatureGlobal.SetGlobalConstant(true)
|
||||
funcValueWithSignatureGlobal.SetLinkage(llvm.InternalLinkage)
|
||||
}
|
||||
funcValueScalar = llvm.ConstPtrToInt(funcValueWithSignatureGlobal, c.uintptrType)
|
||||
default:
|
||||
panic("unimplemented func value variant")
|
||||
}
|
||||
funcValueType, err := c.getFuncType(sig)
|
||||
if err != nil {
|
||||
return llvm.Value{}, err
|
||||
}
|
||||
funcValue := llvm.Undef(funcValueType)
|
||||
funcValue = c.builder.CreateInsertValue(funcValue, context, 0, "")
|
||||
funcValue = c.builder.CreateInsertValue(funcValue, funcValueScalar, 1, "")
|
||||
return funcValue, nil
|
||||
}
|
||||
|
||||
// getFuncSignature returns a global for identification of a particular function
|
||||
// signature. It is used in runtime.funcValueWithSignature and in calls to
|
||||
// getFuncPtr.
|
||||
func (c *Compiler) getFuncSignature(sig *types.Signature) llvm.Value {
|
||||
typeCodeName := getTypeCodeName(sig)
|
||||
sigGlobalName := "reflect/types.type:" + typeCodeName
|
||||
sigGlobal := c.mod.NamedGlobal(sigGlobalName)
|
||||
if sigGlobal.IsNil() {
|
||||
sigGlobal = llvm.AddGlobal(c.mod, c.ctx.Int8Type(), sigGlobalName)
|
||||
sigGlobal.SetInitializer(llvm.Undef(c.ctx.Int8Type()))
|
||||
sigGlobal.SetGlobalConstant(true)
|
||||
sigGlobal.SetLinkage(llvm.InternalLinkage)
|
||||
}
|
||||
return sigGlobal
|
||||
}
|
||||
|
||||
// extractFuncScalar returns some scalar that can be used in comparisons. It is
|
||||
// a cheap operation.
|
||||
func (c *Compiler) extractFuncScalar(funcValue llvm.Value) llvm.Value {
|
||||
return c.builder.CreateExtractValue(funcValue, 1, "")
|
||||
}
|
||||
|
||||
// extractFuncContext extracts the context pointer from this function value. It
|
||||
// is a cheap operation.
|
||||
func (c *Compiler) extractFuncContext(funcValue llvm.Value) llvm.Value {
|
||||
return c.builder.CreateExtractValue(funcValue, 0, "")
|
||||
}
|
||||
|
||||
// decodeFuncValue extracts the context and the function pointer from this func
|
||||
// value. This may be an expensive operation.
|
||||
func (c *Compiler) decodeFuncValue(funcValue llvm.Value, sig *types.Signature) (funcPtr, context llvm.Value, err error) {
|
||||
context = c.builder.CreateExtractValue(funcValue, 0, "")
|
||||
switch c.funcImplementation() {
|
||||
case funcValueDoubleword:
|
||||
funcPtr = c.builder.CreateExtractValue(funcValue, 1, "")
|
||||
case funcValueSwitch:
|
||||
llvmSig, err := c.getRawFuncType(sig)
|
||||
if err != nil {
|
||||
return llvm.Value{}, llvm.Value{}, err
|
||||
}
|
||||
sigGlobal := c.getFuncSignature(sig)
|
||||
funcPtr = c.createRuntimeCall("getFuncPtr", []llvm.Value{funcValue, sigGlobal}, "")
|
||||
funcPtr = c.builder.CreateIntToPtr(funcPtr, llvmSig, "")
|
||||
default:
|
||||
panic("unimplemented func value variant")
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// getFuncType returns the type of a func value given a signature.
|
||||
func (c *Compiler) getFuncType(typ *types.Signature) (llvm.Type, error) {
|
||||
switch c.funcImplementation() {
|
||||
case funcValueDoubleword:
|
||||
rawPtr, err := c.getRawFuncType(typ)
|
||||
if err != nil {
|
||||
return llvm.Type{}, err
|
||||
}
|
||||
return c.ctx.StructType([]llvm.Type{c.i8ptrType, rawPtr}, false), nil
|
||||
case funcValueSwitch:
|
||||
return c.mod.GetTypeByName("runtime.funcValue"), nil
|
||||
default:
|
||||
panic("unimplemented func value variant")
|
||||
}
|
||||
}
|
||||
|
||||
// getRawFuncType returns a LLVM function pointer type for a given signature.
|
||||
func (c *Compiler) getRawFuncType(typ *types.Signature) (llvm.Type, error) {
|
||||
// Get the return type.
|
||||
var err error
|
||||
var returnType llvm.Type
|
||||
switch typ.Results().Len() {
|
||||
case 0:
|
||||
// No return values.
|
||||
returnType = c.ctx.VoidType()
|
||||
case 1:
|
||||
// Just one return value.
|
||||
returnType, err = c.getLLVMType(typ.Results().At(0).Type())
|
||||
if err != nil {
|
||||
return llvm.Type{}, err
|
||||
}
|
||||
default:
|
||||
// Multiple return values. Put them together in a struct.
|
||||
// This appears to be the common way to handle multiple return values in
|
||||
// LLVM.
|
||||
members := make([]llvm.Type, typ.Results().Len())
|
||||
for i := 0; i < typ.Results().Len(); i++ {
|
||||
returnType, err := c.getLLVMType(typ.Results().At(i).Type())
|
||||
if err != nil {
|
||||
return llvm.Type{}, err
|
||||
}
|
||||
members[i] = returnType
|
||||
}
|
||||
returnType = c.ctx.StructType(members, false)
|
||||
}
|
||||
|
||||
// Get the parameter types.
|
||||
var paramTypes []llvm.Type
|
||||
if typ.Recv() != nil {
|
||||
recv, err := c.getLLVMType(typ.Recv().Type())
|
||||
if err != nil {
|
||||
return llvm.Type{}, err
|
||||
}
|
||||
if recv.StructName() == "runtime._interface" {
|
||||
// This is a call on an interface, not a concrete type.
|
||||
// The receiver is not an interface, but a i8* type.
|
||||
recv = c.i8ptrType
|
||||
}
|
||||
paramTypes = append(paramTypes, c.expandFormalParamType(recv)...)
|
||||
}
|
||||
for i := 0; i < typ.Params().Len(); i++ {
|
||||
subType, err := c.getLLVMType(typ.Params().At(i).Type())
|
||||
if err != nil {
|
||||
return llvm.Type{}, err
|
||||
}
|
||||
paramTypes = append(paramTypes, c.expandFormalParamType(subType)...)
|
||||
}
|
||||
// All functions take these parameters at the end.
|
||||
paramTypes = append(paramTypes, c.i8ptrType) // context
|
||||
paramTypes = append(paramTypes, c.i8ptrType) // parent coroutine
|
||||
|
||||
// Make a func type out of the signature.
|
||||
return llvm.PointerType(llvm.FunctionType(returnType, paramTypes, false), c.funcPtrAddrSpace), nil
|
||||
}
|
||||
|
||||
// parseMakeClosure makes a function value (with context) from the given
|
||||
// closure expression.
|
||||
func (c *Compiler) parseMakeClosure(frame *Frame, expr *ssa.MakeClosure) (llvm.Value, error) {
|
||||
if len(expr.Bindings) == 0 {
|
||||
panic("unexpected: MakeClosure without bound variables")
|
||||
}
|
||||
f := c.ir.GetFunction(expr.Fn.(*ssa.Function))
|
||||
|
||||
// Collect all bound variables.
|
||||
boundVars := make([]llvm.Value, 0, len(expr.Bindings))
|
||||
boundVarTypes := make([]llvm.Type, 0, len(expr.Bindings))
|
||||
for _, binding := range expr.Bindings {
|
||||
// The context stores the bound variables.
|
||||
llvmBoundVar, err := c.parseExpr(frame, binding)
|
||||
if err != nil {
|
||||
return llvm.Value{}, err
|
||||
}
|
||||
boundVars = append(boundVars, llvmBoundVar)
|
||||
boundVarTypes = append(boundVarTypes, llvmBoundVar.Type())
|
||||
}
|
||||
contextType := c.ctx.StructType(boundVarTypes, false)
|
||||
|
||||
// Allocate memory for the context.
|
||||
contextAlloc := llvm.Value{}
|
||||
contextHeapAlloc := llvm.Value{}
|
||||
if c.targetData.TypeAllocSize(contextType) <= c.targetData.TypeAllocSize(c.i8ptrType) {
|
||||
// Context fits in a pointer - e.g. when it is a pointer. Store it
|
||||
// directly in the stack after a convert.
|
||||
// Because contextType is a struct and we have to cast it to a *i8,
|
||||
// store it in an alloca first for bitcasting (store+bitcast+load).
|
||||
contextAlloc = c.builder.CreateAlloca(contextType, "")
|
||||
} else {
|
||||
// Context is bigger than a pointer, so allocate it on the heap.
|
||||
size := c.targetData.TypeAllocSize(contextType)
|
||||
sizeValue := llvm.ConstInt(c.uintptrType, size, false)
|
||||
contextHeapAlloc = c.createRuntimeCall("alloc", []llvm.Value{sizeValue}, "")
|
||||
contextAlloc = c.builder.CreateBitCast(contextHeapAlloc, llvm.PointerType(contextType, 0), "")
|
||||
}
|
||||
|
||||
// Store all bound variables in the alloca or heap pointer.
|
||||
for i, boundVar := range boundVars {
|
||||
indices := []llvm.Value{
|
||||
llvm.ConstInt(c.ctx.Int32Type(), 0, false),
|
||||
llvm.ConstInt(c.ctx.Int32Type(), uint64(i), false),
|
||||
}
|
||||
gep := c.builder.CreateInBoundsGEP(contextAlloc, indices, "")
|
||||
c.builder.CreateStore(boundVar, gep)
|
||||
}
|
||||
|
||||
context := llvm.Value{}
|
||||
if c.targetData.TypeAllocSize(contextType) <= c.targetData.TypeAllocSize(c.i8ptrType) {
|
||||
// Load value (as *i8) from the alloca.
|
||||
contextAlloc = c.builder.CreateBitCast(contextAlloc, llvm.PointerType(c.i8ptrType, 0), "")
|
||||
context = c.builder.CreateLoad(contextAlloc, "")
|
||||
} else {
|
||||
// Get the original heap allocation pointer, which already is an
|
||||
// *i8.
|
||||
context = contextHeapAlloc
|
||||
}
|
||||
|
||||
// Create the closure.
|
||||
return c.createFuncValue(f.LLVMFn, context, f.Signature)
|
||||
}
|
||||
@@ -170,6 +170,10 @@ func (c *Compiler) markAsyncFunctions() (needsScheduler bool, err error) {
|
||||
if !sleep.IsNil() {
|
||||
worklist = append(worklist, sleep)
|
||||
}
|
||||
deadlockStub := c.mod.NamedFunction("runtime.deadlockStub")
|
||||
if !deadlockStub.IsNil() {
|
||||
worklist = append(worklist, deadlockStub)
|
||||
}
|
||||
chanSendStub := c.mod.NamedFunction("runtime.chanSendStub")
|
||||
if !chanSendStub.IsNil() {
|
||||
worklist = append(worklist, chanSendStub)
|
||||
@@ -288,7 +292,7 @@ func (c *Compiler) markAsyncFunctions() (needsScheduler bool, err error) {
|
||||
|
||||
// Transform all async functions into coroutines.
|
||||
for _, f := range asyncList {
|
||||
if f == sleep || f == chanSendStub || f == chanRecvStub {
|
||||
if f == sleep || f == deadlockStub || f == chanSendStub || f == chanRecvStub {
|
||||
continue
|
||||
}
|
||||
|
||||
@@ -305,7 +309,7 @@ func (c *Compiler) markAsyncFunctions() (needsScheduler bool, err error) {
|
||||
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 {
|
||||
if _, ok := asyncFuncs[callee]; !ok || callee == sleep || callee == deadlockStub || callee == chanSendStub || callee == chanRecvStub {
|
||||
continue
|
||||
}
|
||||
asyncCalls = append(asyncCalls, inst)
|
||||
@@ -439,6 +443,26 @@ func (c *Compiler) markAsyncFunctions() (needsScheduler bool, err error) {
|
||||
sleepCall.EraseFromParentAsInstruction()
|
||||
}
|
||||
|
||||
// Transform calls to runtime.deadlockStub into coroutine suspends (without
|
||||
// resume).
|
||||
for _, deadlockCall := range getUses(deadlockStub) {
|
||||
// deadlockCall must be a call instruction.
|
||||
frame := asyncFuncs[deadlockCall.InstructionParent().Parent()]
|
||||
|
||||
// Exit coroutine.
|
||||
c.builder.SetInsertPointBefore(deadlockCall)
|
||||
continuePoint := c.builder.CreateCall(coroSuspendFunc, []llvm.Value{
|
||||
llvm.ConstNull(c.ctx.TokenType()),
|
||||
llvm.ConstInt(c.ctx.Int1Type(), 1, false), // final suspend
|
||||
}, "")
|
||||
c.splitBasicBlock(deadlockCall, llvm.NextBasicBlock(c.builder.GetInsertBlock()), "task.wakeup.dead")
|
||||
c.builder.SetInsertPointBefore(deadlockCall)
|
||||
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)
|
||||
deadlockCall.EraseFromParentAsInstruction()
|
||||
}
|
||||
|
||||
// Transform calls to runtime.chanSendStub into channel send operations.
|
||||
for _, sendOp := range getUses(chanSendStub) {
|
||||
// sendOp must be a call instruction.
|
||||
|
||||
@@ -0,0 +1,162 @@
|
||||
package compiler
|
||||
|
||||
// This file implements inline asm support by calling special functions.
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"go/constant"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"golang.org/x/tools/go/ssa"
|
||||
"tinygo.org/x/go-llvm"
|
||||
)
|
||||
|
||||
// This is a compiler builtin, which reads the given register by name:
|
||||
//
|
||||
// func ReadRegister(name string) uintptr
|
||||
//
|
||||
// The register name must be a constant, for example "sp".
|
||||
func (c *Compiler) emitReadRegister(args []ssa.Value) (llvm.Value, error) {
|
||||
fnType := llvm.FunctionType(c.uintptrType, []llvm.Type{}, false)
|
||||
regname := constant.StringVal(args[0].(*ssa.Const).Value)
|
||||
target := llvm.InlineAsm(fnType, "mov $0, "+regname, "=r", false, false, 0)
|
||||
return c.builder.CreateCall(target, nil, ""), nil
|
||||
}
|
||||
|
||||
// This is a compiler builtin, which emits a piece of inline assembly with no
|
||||
// operands or return values. It is useful for trivial instructions, like wfi in
|
||||
// ARM or sleep in AVR.
|
||||
//
|
||||
// func Asm(asm string)
|
||||
//
|
||||
// The provided assembly must be a constant.
|
||||
func (c *Compiler) emitAsm(args []ssa.Value) (llvm.Value, error) {
|
||||
// Magic function: insert inline assembly instead of calling it.
|
||||
fnType := llvm.FunctionType(c.ctx.VoidType(), []llvm.Type{}, false)
|
||||
asm := constant.StringVal(args[0].(*ssa.Const).Value)
|
||||
target := llvm.InlineAsm(fnType, asm, "", true, false, 0)
|
||||
return c.builder.CreateCall(target, nil, ""), nil
|
||||
}
|
||||
|
||||
// This is a compiler builtin, which allows assembly to be called in a flexible
|
||||
// way.
|
||||
//
|
||||
// func AsmFull(asm string, regs map[string]interface{})
|
||||
//
|
||||
// The asm parameter must be a constant string. The regs parameter must be
|
||||
// provided immediately. For example:
|
||||
//
|
||||
// arm.AsmFull(
|
||||
// "str {value}, {result}",
|
||||
// map[string]interface{}{
|
||||
// "value": 1
|
||||
// "result": &dest,
|
||||
// })
|
||||
func (c *Compiler) emitAsmFull(frame *Frame, instr *ssa.CallCommon) (llvm.Value, error) {
|
||||
asmString := constant.StringVal(instr.Args[0].(*ssa.Const).Value)
|
||||
registers := map[string]llvm.Value{}
|
||||
registerMap := instr.Args[1].(*ssa.MakeMap)
|
||||
for _, r := range *registerMap.Referrers() {
|
||||
switch r := r.(type) {
|
||||
case *ssa.DebugRef:
|
||||
// ignore
|
||||
case *ssa.MapUpdate:
|
||||
if r.Block() != registerMap.Block() {
|
||||
return llvm.Value{}, c.makeError(instr.Pos(), "register value map must be created in the same basic block")
|
||||
}
|
||||
key := constant.StringVal(r.Key.(*ssa.Const).Value)
|
||||
//println("value:", r.Value.(*ssa.MakeInterface).X.String())
|
||||
value, err := c.parseExpr(frame, r.Value.(*ssa.MakeInterface).X)
|
||||
if err != nil {
|
||||
return llvm.Value{}, err
|
||||
}
|
||||
registers[key] = value
|
||||
case *ssa.Call:
|
||||
if r.Common() == instr {
|
||||
break
|
||||
}
|
||||
default:
|
||||
return llvm.Value{}, c.makeError(instr.Pos(), "don't know how to handle argument to inline assembly: "+r.String())
|
||||
}
|
||||
}
|
||||
// TODO: handle dollar signs in asm string
|
||||
registerNumbers := map[string]int{}
|
||||
var err error
|
||||
argTypes := []llvm.Type{}
|
||||
args := []llvm.Value{}
|
||||
constraints := []string{}
|
||||
asmString = regexp.MustCompile("\\{[a-zA-Z]+\\}").ReplaceAllStringFunc(asmString, func(s string) string {
|
||||
// TODO: skip strings like {r4} etc. that look like ARM push/pop
|
||||
// instructions.
|
||||
name := s[1 : len(s)-1]
|
||||
if _, ok := registers[name]; !ok {
|
||||
if err == nil {
|
||||
err = c.makeError(instr.Pos(), "unknown register name: "+name)
|
||||
}
|
||||
return s
|
||||
}
|
||||
if _, ok := registerNumbers[name]; !ok {
|
||||
registerNumbers[name] = len(registerNumbers)
|
||||
argTypes = append(argTypes, registers[name].Type())
|
||||
args = append(args, registers[name])
|
||||
switch registers[name].Type().TypeKind() {
|
||||
case llvm.IntegerTypeKind:
|
||||
constraints = append(constraints, "r")
|
||||
case llvm.PointerTypeKind:
|
||||
constraints = append(constraints, "*m")
|
||||
default:
|
||||
err = c.makeError(instr.Pos(), "unknown type in inline assembly for value: "+name)
|
||||
return s
|
||||
}
|
||||
}
|
||||
return fmt.Sprintf("${%v}", registerNumbers[name])
|
||||
})
|
||||
if err != nil {
|
||||
return llvm.Value{}, err
|
||||
}
|
||||
fnType := llvm.FunctionType(c.ctx.VoidType(), argTypes, false)
|
||||
target := llvm.InlineAsm(fnType, asmString, strings.Join(constraints, ","), true, false, 0)
|
||||
return c.builder.CreateCall(target, args, ""), nil
|
||||
}
|
||||
|
||||
// This is a compiler builtin which emits an inline SVCall instruction. It can
|
||||
// be one of:
|
||||
//
|
||||
// func SVCall0(num uintptr) uintptr
|
||||
// func SVCall1(num uintptr, a1 interface{}) uintptr
|
||||
// func SVCall2(num uintptr, a1, a2 interface{}) uintptr
|
||||
// func SVCall3(num uintptr, a1, a2, a3 interface{}) uintptr
|
||||
// func SVCall4(num uintptr, a1, a2, a3, a4 interface{}) uintptr
|
||||
//
|
||||
// The num parameter must be a constant. All other parameters may be any scalar
|
||||
// value supported by LLVM inline assembly.
|
||||
func (c *Compiler) emitSVCall(frame *Frame, args []ssa.Value) (llvm.Value, error) {
|
||||
num, _ := constant.Uint64Val(args[0].(*ssa.Const).Value)
|
||||
llvmArgs := []llvm.Value{}
|
||||
argTypes := []llvm.Type{}
|
||||
asm := "svc #" + strconv.FormatUint(num, 10)
|
||||
constraints := "={r0}"
|
||||
for i, arg := range args[1:] {
|
||||
arg = arg.(*ssa.MakeInterface).X
|
||||
if i == 0 {
|
||||
constraints += ",0"
|
||||
} else {
|
||||
constraints += ",{r" + strconv.Itoa(i) + "}"
|
||||
}
|
||||
llvmValue, err := c.parseExpr(frame, arg)
|
||||
if err != nil {
|
||||
return llvm.Value{}, err
|
||||
}
|
||||
llvmArgs = append(llvmArgs, llvmValue)
|
||||
argTypes = append(argTypes, llvmValue.Type())
|
||||
}
|
||||
// Implement the ARM calling convention by marking r1-r3 as
|
||||
// clobbered. r0 is used as an output register so doesn't have to be
|
||||
// marked as clobbered.
|
||||
constraints += ",~{r1},~{r2},~{r3}"
|
||||
fnType := llvm.FunctionType(c.uintptrType, argTypes, false)
|
||||
target := llvm.InlineAsm(fnType, asm, constraints, true, false, 0)
|
||||
return c.builder.CreateCall(target, llvmArgs, ""), nil
|
||||
}
|
||||
@@ -4,7 +4,6 @@ package compiler
|
||||
// form, optimizing them in the process.
|
||||
//
|
||||
// During SSA construction, the following pseudo-calls are created:
|
||||
// runtime.makeInterface(typecode, methodSet)
|
||||
// runtime.typeAssert(typecode, assertedType)
|
||||
// runtime.interfaceImplements(typecode, interfaceMethodSet)
|
||||
// runtime.interfaceMethod(typecode, interfaceMethodSet, signature)
|
||||
@@ -14,16 +13,13 @@ package compiler
|
||||
//
|
||||
// This pass lowers the above functions to their final form:
|
||||
//
|
||||
// makeInterface:
|
||||
// Replaced with a constant typecode.
|
||||
//
|
||||
// typeAssert:
|
||||
// Replaced with an icmp instruction so it can be directly used in a type
|
||||
// switch. This is very easy to optimize for LLVM: it will often translate a
|
||||
// type switch into a regular switch statement.
|
||||
// When this type assert is not possible (the type is never used in an
|
||||
// interface with makeInterface), this call is replaced with a constant
|
||||
// false to optimize the type assert away completely.
|
||||
// interface), this call is replaced with a constant false to optimize the
|
||||
// type assert away completely.
|
||||
//
|
||||
// interfaceImplements:
|
||||
// This call is translated into a call that checks whether the underlying
|
||||
@@ -166,25 +162,36 @@ func (c *Compiler) LowerInterfaces() {
|
||||
|
||||
// run runs the pass itself.
|
||||
func (p *lowerInterfacesPass) run() {
|
||||
// Count per type how often it is put in an interface. Also, collect all
|
||||
// methods this type has (if it is named).
|
||||
makeInterface := p.mod.NamedFunction("runtime.makeInterface")
|
||||
makeInterfaceUses := getUses(makeInterface)
|
||||
for _, use := range makeInterfaceUses {
|
||||
typecode := use.Operand(0)
|
||||
name := typecode.Name()
|
||||
if t, ok := p.types[name]; !ok {
|
||||
// This is the first time this type has been seen, add it to the
|
||||
// list of types.
|
||||
t = p.addType(typecode)
|
||||
p.addTypeMethods(t, use.Operand(1))
|
||||
} else {
|
||||
p.addTypeMethods(t, use.Operand(1))
|
||||
}
|
||||
// Collect all type codes.
|
||||
typecodeIDPtr := llvm.PointerType(p.mod.GetTypeByName("runtime.typecodeID"), 0)
|
||||
typeInInterfacePtr := llvm.PointerType(p.mod.GetTypeByName("runtime.typeInInterface"), 0)
|
||||
var typesInInterfaces []llvm.Value
|
||||
for global := p.mod.FirstGlobal(); !global.IsNil(); global = llvm.NextGlobal(global) {
|
||||
switch global.Type() {
|
||||
case typecodeIDPtr:
|
||||
// Retrieve Go type information based on an opaque global variable.
|
||||
// Only the name of the global is relevant, the object itself is
|
||||
// discarded afterwards.
|
||||
name := global.Name()
|
||||
t := &typeInfo{
|
||||
name: name,
|
||||
typecode: global,
|
||||
}
|
||||
p.types[name] = t
|
||||
case typeInInterfacePtr:
|
||||
// Count per type how often it is put in an interface. Also, collect
|
||||
// all methods this type has (if it is named).
|
||||
typesInInterfaces = append(typesInInterfaces, global)
|
||||
initializer := global.Initializer()
|
||||
typecode := llvm.ConstExtractValue(initializer, []uint32{0})
|
||||
methodSet := llvm.ConstExtractValue(initializer, []uint32{1})
|
||||
t := p.types[typecode.Name()]
|
||||
p.addTypeMethods(t, methodSet)
|
||||
|
||||
// Count the number of MakeInterface instructions, for sorting the
|
||||
// typecodes later.
|
||||
p.types[name].countMakeInterfaces++
|
||||
// Count the number of MakeInterface instructions, for sorting the
|
||||
// typecodes later.
|
||||
t.countMakeInterfaces += len(getUses(global))
|
||||
}
|
||||
}
|
||||
|
||||
// Count per type how often it is type asserted on (e.g. in a switch
|
||||
@@ -194,9 +201,6 @@ func (p *lowerInterfacesPass) run() {
|
||||
for _, use := range typeAssertUses {
|
||||
typecode := use.Operand(1)
|
||||
name := typecode.Name()
|
||||
if _, ok := p.types[name]; !ok {
|
||||
p.addType(typecode)
|
||||
}
|
||||
p.types[name].countTypeAsserts++
|
||||
}
|
||||
|
||||
@@ -286,16 +290,6 @@ func (p *lowerInterfacesPass) run() {
|
||||
typecode := use.Operand(0)
|
||||
signature := p.signatures[use.Operand(2).Name()]
|
||||
|
||||
// If the interface was created in the same function, we can insert a
|
||||
// direct call. This may not happen often but it is an easy
|
||||
// optimization so let's do it anyway.
|
||||
if !typecode.IsACallInst().IsNil() && typecode.CalledValue() == makeInterface {
|
||||
name := typecode.Operand(0).Name()
|
||||
typ := p.types[name]
|
||||
p.replaceInvokeWithCall(use, typ, signature)
|
||||
continue
|
||||
}
|
||||
|
||||
methodSet := use.Operand(1).Operand(0) // global variable
|
||||
itf := p.interfaces[methodSet.Name()]
|
||||
if len(itf.types) == 0 {
|
||||
@@ -356,20 +350,6 @@ func (p *lowerInterfacesPass) run() {
|
||||
// types, if possible.
|
||||
for _, use := range interfaceImplementsUses {
|
||||
actualType := use.Operand(0)
|
||||
if !actualType.IsACallInst().IsNil() && actualType.CalledValue() == makeInterface {
|
||||
// Type assert is in the same function that creates the interface
|
||||
// value. This means the underlying type is already known so match
|
||||
// on that.
|
||||
// This may not happen often but it is an easy optimization.
|
||||
name := actualType.Operand(0).Name()
|
||||
typ := p.types[name]
|
||||
p.builder.SetInsertPointBefore(use)
|
||||
assertedType := p.builder.CreatePtrToInt(typ.typecode, p.uintptrType, "typeassert.typecode")
|
||||
commaOk := p.builder.CreateICmp(llvm.IntEQ, assertedType, actualType, "typeassert.ok")
|
||||
use.ReplaceAllUsesWith(commaOk)
|
||||
use.EraseFromParentAsInstruction()
|
||||
continue
|
||||
}
|
||||
|
||||
methodSet := use.Operand(1).Operand(0) // global variable
|
||||
itf := p.interfaces[methodSet.Name()]
|
||||
@@ -416,12 +396,14 @@ func (p *lowerInterfacesPass) run() {
|
||||
// Assign a type code for each type.
|
||||
p.assignTypeCodes(typeSlice)
|
||||
|
||||
// Replace each call to runtime.makeInterface with the constant type code.
|
||||
for _, use := range makeInterfaceUses {
|
||||
global := use.Operand(0)
|
||||
t := p.types[global.Name()]
|
||||
use.ReplaceAllUsesWith(llvm.ConstPtrToInt(t.typecode, p.uintptrType))
|
||||
use.EraseFromParentAsInstruction()
|
||||
// Replace each use of a runtime.typeInInterface with the constant type
|
||||
// code.
|
||||
for _, global := range typesInInterfaces {
|
||||
for _, use := range getUses(global) {
|
||||
t := p.types[llvm.ConstExtractValue(global.Initializer(), []uint32{0}).Name()]
|
||||
typecode := llvm.ConstInt(p.uintptrType, t.num, false)
|
||||
use.ReplaceAllUsesWith(typecode)
|
||||
}
|
||||
}
|
||||
|
||||
// Replace each type assert with an actual type comparison or (if the type
|
||||
@@ -458,12 +440,18 @@ func (p *lowerInterfacesPass) run() {
|
||||
// numbers.
|
||||
for _, typ := range p.types {
|
||||
for _, use := range getUses(typ.typecode) {
|
||||
if use.IsConstant() && use.Opcode() == llvm.PtrToInt {
|
||||
if !use.IsAConstantExpr().IsNil() && use.Opcode() == llvm.PtrToInt {
|
||||
use.ReplaceAllUsesWith(llvm.ConstInt(p.uintptrType, typ.num, false))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Remove stray runtime.typeInInterface globals. Required for the following
|
||||
// cleanup.
|
||||
for _, global := range typesInInterfaces {
|
||||
global.EraseFromParentAsGlobal()
|
||||
}
|
||||
|
||||
// Remove method sets of types. Unnecessary, but cleans up the IR for
|
||||
// inspection.
|
||||
for _, typ := range p.types {
|
||||
@@ -474,19 +462,6 @@ func (p *lowerInterfacesPass) run() {
|
||||
}
|
||||
}
|
||||
|
||||
// addType retrieves Go type information based on a i16 global variable.
|
||||
// Only the name of the i16 is relevant, the object itself is const-propagated
|
||||
// and discared afterwards.
|
||||
func (p *lowerInterfacesPass) addType(typecode llvm.Value) *typeInfo {
|
||||
name := typecode.Name()
|
||||
t := &typeInfo{
|
||||
name: name,
|
||||
typecode: typecode,
|
||||
}
|
||||
p.types[name] = t
|
||||
return t
|
||||
}
|
||||
|
||||
// addTypeMethods reads the method set of the given type info struct. It
|
||||
// retrieves the signatures and the references to the method functions
|
||||
// themselves for later type<->interface matching.
|
||||
|
||||
+15
-7
@@ -59,7 +59,15 @@ func (c *Compiler) parseMakeInterface(val llvm.Value, typ types.Type, pos token.
|
||||
if err != nil {
|
||||
return llvm.Value{}, nil
|
||||
}
|
||||
itfTypeCode := c.createRuntimeCall("makeInterface", []llvm.Value{itfTypeCodeGlobal, itfMethodSetGlobal}, "makeinterface.typecode")
|
||||
itfConcreteTypeGlobal := c.mod.NamedGlobal("typeInInterface:" + itfTypeCodeGlobal.Name())
|
||||
if itfConcreteTypeGlobal.IsNil() {
|
||||
typeInInterface := c.mod.GetTypeByName("runtime.typeInInterface")
|
||||
itfConcreteTypeGlobal = llvm.AddGlobal(c.mod, typeInInterface, "typeInInterface:"+itfTypeCodeGlobal.Name())
|
||||
itfConcreteTypeGlobal.SetInitializer(llvm.ConstNamedStruct(typeInInterface, []llvm.Value{itfTypeCodeGlobal, itfMethodSetGlobal}))
|
||||
itfConcreteTypeGlobal.SetGlobalConstant(true)
|
||||
itfConcreteTypeGlobal.SetLinkage(llvm.PrivateLinkage)
|
||||
}
|
||||
itfTypeCode := c.builder.CreatePtrToInt(itfConcreteTypeGlobal, c.uintptrType, "")
|
||||
itf := llvm.Undef(c.mod.GetTypeByName("runtime._interface"))
|
||||
itf = c.builder.CreateInsertValue(itf, itfTypeCode, 0, "")
|
||||
itf = c.builder.CreateInsertValue(itf, itfValue, 1, "")
|
||||
@@ -73,7 +81,7 @@ func (c *Compiler) getTypeCode(typ types.Type) llvm.Value {
|
||||
globalName := "type:" + getTypeCodeName(typ)
|
||||
global := c.mod.NamedGlobal(globalName)
|
||||
if global.IsNil() {
|
||||
global = llvm.AddGlobal(c.mod, c.ctx.Int8Type(), globalName)
|
||||
global = llvm.AddGlobal(c.mod, c.mod.GetTypeByName("runtime.typecodeID"), globalName)
|
||||
global.SetGlobalConstant(true)
|
||||
}
|
||||
return global
|
||||
@@ -162,6 +170,10 @@ func getTypeCodeName(t types.Type) string {
|
||||
return "slice:" + name + getTypeCodeName(t.Elem())
|
||||
case *types.Struct:
|
||||
elems := make([]string, t.NumFields())
|
||||
if t.NumFields() > 2 && t.Field(0).Name() == "C union" {
|
||||
// TODO: report this as a normal error instead of panicking.
|
||||
panic("cgo unions are not allowed in interfaces")
|
||||
}
|
||||
for i := 0; i < t.NumFields(); i++ {
|
||||
elems[i] = getTypeCodeName(t.Field(i).Type())
|
||||
}
|
||||
@@ -384,14 +396,10 @@ func (c *Compiler) getInvokeCall(frame *Frame, instr *ssa.CallCommon) (llvm.Valu
|
||||
return llvm.Value{}, nil, err
|
||||
}
|
||||
|
||||
llvmFnType, err := c.getLLVMType(instr.Method.Type())
|
||||
llvmFnType, err := c.getRawFuncType(instr.Method.Type().(*types.Signature))
|
||||
if err != nil {
|
||||
return llvm.Value{}, nil, err
|
||||
}
|
||||
// getLLVMType() has created a closure type for us, but we don't actually
|
||||
// want a closure type as an interface call can never be a closure call. So
|
||||
// extract the function pointer type from the closure.
|
||||
llvmFnType = llvmFnType.Subtypes()[1]
|
||||
|
||||
typecode := c.builder.CreateExtractValue(itf, 0, "invoke.typecode")
|
||||
values := []llvm.Value{
|
||||
|
||||
@@ -43,6 +43,7 @@ func (c *Compiler) Optimize(optLevel, sizeLevel int, inlinerThreshold uint) erro
|
||||
c.OptimizeStringToBytes()
|
||||
c.OptimizeAllocs()
|
||||
c.LowerInterfaces()
|
||||
c.LowerFuncValues()
|
||||
|
||||
// After interfaces are lowered, there are many more opportunities for
|
||||
// interprocedural optimizations. To get them to work, function
|
||||
@@ -53,6 +54,22 @@ func (c *Compiler) Optimize(optLevel, sizeLevel int, inlinerThreshold uint) erro
|
||||
c.OptimizeAllocs()
|
||||
c.OptimizeStringToBytes()
|
||||
|
||||
// Lower runtime.isnil calls to regular nil comparisons.
|
||||
isnil := c.mod.NamedFunction("runtime.isnil")
|
||||
if !isnil.IsNil() {
|
||||
for _, use := range getUses(isnil) {
|
||||
c.builder.SetInsertPointBefore(use)
|
||||
ptr := use.Operand(0)
|
||||
if !ptr.IsABitCastInst().IsNil() {
|
||||
ptr = ptr.Operand(0)
|
||||
}
|
||||
nilptr := llvm.ConstPointerNull(ptr.Type())
|
||||
icmp := c.builder.CreateICmp(llvm.IntEQ, ptr, nilptr, "")
|
||||
use.ReplaceAllUsesWith(icmp)
|
||||
use.EraseFromParentAsInstruction()
|
||||
}
|
||||
}
|
||||
|
||||
err := c.LowerGoroutines()
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -60,6 +77,7 @@ func (c *Compiler) Optimize(optLevel, sizeLevel int, inlinerThreshold uint) erro
|
||||
} else {
|
||||
// Must be run at any optimization level.
|
||||
c.LowerInterfaces()
|
||||
c.LowerFuncValues()
|
||||
err := c.LowerGoroutines()
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -281,6 +299,9 @@ func (c *Compiler) doesEscape(value llvm.Value) bool {
|
||||
if !c.hasFlag(use, value, "nocapture") {
|
||||
return true
|
||||
}
|
||||
} else if use.IsAICmpInst() != nilValue {
|
||||
// Comparing pointers don't let the pointer escape.
|
||||
// This is often a compiler-inserted nil check.
|
||||
} else {
|
||||
// Unknown instruction, might escape.
|
||||
return true
|
||||
|
||||
+36
-3
@@ -63,6 +63,12 @@ func (s *StdSizes) Alignof(T types.Type) int64 {
|
||||
|
||||
func (s *StdSizes) Offsetsof(fields []*types.Var) []int64 {
|
||||
offsets := make([]int64, len(fields))
|
||||
if len(fields) > 1 && fields[0].Name() == "C union" {
|
||||
// This struct contains the magic "C union" field which indicates that
|
||||
// this is actually a union from CGo.
|
||||
// All fields in the union start at 0 so return that.
|
||||
return offsets // all fields are still set to 0
|
||||
}
|
||||
var o int64
|
||||
for i, f := range fields {
|
||||
a := s.Alignof(f.Type())
|
||||
@@ -125,11 +131,38 @@ func (s *StdSizes) Sizeof(T types.Type) int64 {
|
||||
return 0
|
||||
}
|
||||
fields := make([]*types.Var, t.NumFields())
|
||||
maxAlign := int64(1)
|
||||
for i := range fields {
|
||||
fields[i] = t.Field(i)
|
||||
field := t.Field(i)
|
||||
fields[i] = field
|
||||
al := s.Alignof(field.Type())
|
||||
if al > maxAlign {
|
||||
maxAlign = al
|
||||
}
|
||||
}
|
||||
if fields[0].Name() == "C union" {
|
||||
// Magic field that indicates this is a CGo union and not a struct.
|
||||
// The size is the biggest element, aligned to the element with the
|
||||
// biggest alignment. This is not necessarily the same, for example
|
||||
// in the following union:
|
||||
// union { int32_t l; int16_t s[3] }
|
||||
maxSize := int64(0)
|
||||
for _, field := range fields[1:] {
|
||||
si := s.Sizeof(field.Type())
|
||||
if si > maxSize {
|
||||
maxSize = si
|
||||
}
|
||||
}
|
||||
return align(maxSize, maxAlign)
|
||||
} else {
|
||||
// This is a regular struct.
|
||||
// Pick the size that fits this struct and add some alignment. Some
|
||||
// structs have some extra padding at the end which should also be
|
||||
// taken care of:
|
||||
// struct { int32 n; byte b }
|
||||
offsets := s.Offsetsof(fields)
|
||||
return align(offsets[n-1]+s.Sizeof(fields[n-1].Type()), maxAlign)
|
||||
}
|
||||
offsets := s.Offsetsof(fields)
|
||||
return offsets[n-1] + s.Sizeof(fields[n-1].Type())
|
||||
case *types.Interface:
|
||||
return s.PtrSize * 2
|
||||
case *types.Pointer:
|
||||
|
||||
+41
-4
@@ -95,7 +95,7 @@ func (fr *frame) evalBasicBlock(bb, incoming llvm.BasicBlock, indent string) (re
|
||||
case !inst.IsALoadInst().IsNil():
|
||||
operand := fr.getLocal(inst.Operand(0)).(*LocalValue)
|
||||
var value llvm.Value
|
||||
if !operand.IsConstant() || inst.IsVolatile() || operand.Underlying.Opcode() == llvm.BitCast {
|
||||
if !operand.IsConstant() || inst.IsVolatile() || (!operand.Underlying.IsAConstantExpr().IsNil() && operand.Underlying.Opcode() == llvm.BitCast) {
|
||||
value = fr.builder.CreateLoad(operand.Value(), inst.Name())
|
||||
} else {
|
||||
value = operand.Load()
|
||||
@@ -307,9 +307,46 @@ func (fr *frame) evalBasicBlock(bb, incoming llvm.BasicBlock, indent string) (re
|
||||
ret = llvm.ConstInsertValue(ret, retLen, []uint32{1}) // len
|
||||
ret = llvm.ConstInsertValue(ret, retLen, []uint32{2}) // cap
|
||||
fr.locals[inst] = &LocalValue{fr.Eval, ret}
|
||||
case callee.Name() == "runtime.makeInterface":
|
||||
uintptrType := callee.Type().Context().IntType(fr.TargetData.PointerSize() * 8)
|
||||
fr.locals[inst] = &LocalValue{fr.Eval, llvm.ConstPtrToInt(inst.Operand(0), uintptrType)}
|
||||
case callee.Name() == "runtime.interfaceImplements":
|
||||
typecode := fr.getLocal(inst.Operand(0)).(*LocalValue).Underlying
|
||||
interfaceMethodSet := fr.getLocal(inst.Operand(1)).(*LocalValue).Underlying
|
||||
if typecode.IsAConstantExpr().IsNil() || typecode.Opcode() != llvm.PtrToInt {
|
||||
panic("interp: expected typecode to be a ptrtoint")
|
||||
}
|
||||
typecode = typecode.Operand(0)
|
||||
if interfaceMethodSet.IsAConstantExpr().IsNil() || interfaceMethodSet.Opcode() != llvm.GetElementPtr {
|
||||
panic("interp: expected method set in runtime.interfaceImplements to be a constant gep")
|
||||
}
|
||||
interfaceMethodSet = interfaceMethodSet.Operand(0).Initializer()
|
||||
methodSet := llvm.ConstExtractValue(typecode.Initializer(), []uint32{1})
|
||||
if methodSet.IsAConstantExpr().IsNil() || methodSet.Opcode() != llvm.GetElementPtr {
|
||||
panic("interp: expected method set to be a constant gep")
|
||||
}
|
||||
methodSet = methodSet.Operand(0).Initializer()
|
||||
|
||||
// Make a set of all the methods on the concrete type, for
|
||||
// easier checking in the next step.
|
||||
definedMethods := map[string]struct{}{}
|
||||
for i := 0; i < methodSet.Type().ArrayLength(); i++ {
|
||||
methodInfo := llvm.ConstExtractValue(methodSet, []uint32{uint32(i)})
|
||||
name := llvm.ConstExtractValue(methodInfo, []uint32{0}).Name()
|
||||
definedMethods[name] = struct{}{}
|
||||
}
|
||||
// Check whether all interface methods are also in the list
|
||||
// of defined methods calculated above.
|
||||
implements := uint64(1) // i1 true
|
||||
for i := 0; i < interfaceMethodSet.Type().ArrayLength(); i++ {
|
||||
name := llvm.ConstExtractValue(interfaceMethodSet, []uint32{uint32(i)}).Name()
|
||||
if _, ok := definedMethods[name]; !ok {
|
||||
// There is a method on the interface that is not
|
||||
// implemented by the type.
|
||||
implements = 0 // i1 false
|
||||
break
|
||||
}
|
||||
}
|
||||
fr.locals[inst] = &LocalValue{fr.Eval, llvm.ConstInt(fr.Mod.Context().Int1Type(), implements, false)}
|
||||
case callee.Name() == "runtime.nanotime":
|
||||
fr.locals[inst] = &LocalValue{fr.Eval, llvm.ConstInt(fr.Mod.Context().Int64Type(), 0, false)}
|
||||
case strings.HasPrefix(callee.Name(), "runtime.print") || callee.Name() == "runtime._panic":
|
||||
// This are all print instructions, which necessarily have side
|
||||
// effects but no results.
|
||||
|
||||
+5
-5
@@ -28,8 +28,13 @@ func (e *Eval) hasSideEffects(fn llvm.Value) *sideEffectResult {
|
||||
case "runtime.alloc":
|
||||
// Cannot be scanned but can be interpreted.
|
||||
return &sideEffectResult{severity: sideEffectNone}
|
||||
case "runtime.nanotime":
|
||||
// Fixed value at compile time.
|
||||
return &sideEffectResult{severity: sideEffectNone}
|
||||
case "runtime._panic":
|
||||
return &sideEffectResult{severity: sideEffectLimited}
|
||||
case "runtime.interfaceImplements":
|
||||
return &sideEffectResult{severity: sideEffectNone}
|
||||
}
|
||||
if e.sideEffectFuncs == nil {
|
||||
e.sideEffectFuncs = make(map[llvm.Value]*sideEffectResult)
|
||||
@@ -81,11 +86,6 @@ func (e *Eval) hasSideEffects(fn llvm.Value) *sideEffectResult {
|
||||
continue
|
||||
}
|
||||
if child.IsDeclaration() {
|
||||
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 e.hasLocalSideEffects(dirtyLocals, inst) {
|
||||
|
||||
+1
-1
Submodule lib/compiler-rt updated: a4cbb02bca...5bc79797e1
+1
-2
@@ -22,7 +22,7 @@ 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 {
|
||||
func Link(linker string, flags ...string) error {
|
||||
switch linker {
|
||||
case "ld.lld", commands["ld.lld"]:
|
||||
flags = append([]string{"tinygo:" + linker}, flags...)
|
||||
@@ -60,7 +60,6 @@ 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()
|
||||
}
|
||||
}
|
||||
|
||||
+1
-2
@@ -13,10 +13,9 @@ import (
|
||||
// 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 {
|
||||
func Link(linker string, flags ...string) error {
|
||||
cmd := exec.Command(linker, flags...)
|
||||
cmd.Stdout = os.Stdout
|
||||
cmd.Stderr = os.Stderr
|
||||
cmd.Dir = dir
|
||||
return cmd.Run()
|
||||
}
|
||||
|
||||
+59
-15
@@ -16,11 +16,13 @@ import (
|
||||
// fileInfo holds all Cgo-related information of a given *ast.File.
|
||||
type fileInfo struct {
|
||||
*ast.File
|
||||
filename string
|
||||
functions map[string]*functionInfo
|
||||
globals map[string]*globalInfo
|
||||
typedefs map[string]*typedefInfo
|
||||
importCPos token.Pos
|
||||
*Package
|
||||
filename string
|
||||
functions map[string]*functionInfo
|
||||
globals map[string]*globalInfo
|
||||
typedefs map[string]*typedefInfo
|
||||
elaboratedTypes map[string]ast.Expr
|
||||
importCPos token.Pos
|
||||
}
|
||||
|
||||
// functionInfo stores some information about a Cgo function found by libclang
|
||||
@@ -78,13 +80,15 @@ typedef unsigned long long _Cgo_ulonglong;
|
||||
|
||||
// processCgo extracts the `import "C"` statement from the AST, parses the
|
||||
// comment with libclang, and modifies the AST to use this information.
|
||||
func (p *Package) processCgo(filename string, f *ast.File, cflags []string) error {
|
||||
func (p *Package) processCgo(filename string, f *ast.File, cflags []string) []error {
|
||||
info := &fileInfo{
|
||||
File: f,
|
||||
filename: filename,
|
||||
functions: map[string]*functionInfo{},
|
||||
globals: map[string]*globalInfo{},
|
||||
typedefs: map[string]*typedefInfo{},
|
||||
File: f,
|
||||
Package: p,
|
||||
filename: filename,
|
||||
functions: map[string]*functionInfo{},
|
||||
globals: map[string]*globalInfo{},
|
||||
typedefs: map[string]*typedefInfo{},
|
||||
elaboratedTypes: map[string]ast.Expr{},
|
||||
}
|
||||
|
||||
// Find `import "C"` statements in the file.
|
||||
@@ -114,9 +118,10 @@ func (p *Package) processCgo(filename string, f *ast.File, cflags []string) erro
|
||||
// source location.
|
||||
info.importCPos = spec.Path.ValuePos
|
||||
|
||||
err = info.parseFragment(cgoComment+cgoTypes, cflags)
|
||||
if err != nil {
|
||||
return err
|
||||
pos := info.fset.PositionFor(genDecl.Doc.Pos(), true)
|
||||
errs := info.parseFragment(cgoComment+cgoTypes, cflags, pos.Filename, pos.Line)
|
||||
if errs != nil {
|
||||
return errs
|
||||
}
|
||||
|
||||
// Remove this import declaration.
|
||||
@@ -139,9 +144,12 @@ func (p *Package) processCgo(filename string, f *ast.File, cflags []string) erro
|
||||
// Forward C types to Go types (like C.uint32_t -> uint32).
|
||||
info.addTypeAliases()
|
||||
|
||||
// Add type declarations for C types, declared using typeef in C.
|
||||
// Add type declarations for C types, declared using typedef in C.
|
||||
info.addTypedefs()
|
||||
|
||||
// Add elaborated types for C structs and unions.
|
||||
info.addElaboratedTypes()
|
||||
|
||||
// Patch the AST to use the declared types and functions.
|
||||
f = astutil.Apply(f, info.walker, nil).(*ast.File)
|
||||
|
||||
@@ -373,6 +381,42 @@ func (info *fileInfo) addTypedefs() {
|
||||
info.Decls = append(info.Decls, gen)
|
||||
}
|
||||
|
||||
// addElaboratedTypes adds C elaborated types as aliases. These are the "struct
|
||||
// foo" or "union foo" types, often used in a typedef.
|
||||
//
|
||||
// See also:
|
||||
// https://en.cppreference.com/w/cpp/language/elaborated_type_specifier
|
||||
func (info *fileInfo) addElaboratedTypes() {
|
||||
gen := &ast.GenDecl{
|
||||
TokPos: info.importCPos,
|
||||
Tok: token.TYPE,
|
||||
}
|
||||
names := make([]string, 0, len(info.elaboratedTypes))
|
||||
for name := range info.elaboratedTypes {
|
||||
names = append(names, name)
|
||||
}
|
||||
sort.Strings(names)
|
||||
for _, name := range names {
|
||||
typ := info.elaboratedTypes[name]
|
||||
typeName := "C.struct_" + name
|
||||
obj := &ast.Object{
|
||||
Kind: ast.Typ,
|
||||
Name: typeName,
|
||||
}
|
||||
typeSpec := &ast.TypeSpec{
|
||||
Name: &ast.Ident{
|
||||
NamePos: info.importCPos,
|
||||
Name: typeName,
|
||||
Obj: obj,
|
||||
},
|
||||
Type: typ,
|
||||
}
|
||||
obj.Decl = typeSpec
|
||||
gen.Specs = append(gen.Specs, typeSpec)
|
||||
}
|
||||
info.Decls = append(info.Decls, gen)
|
||||
}
|
||||
|
||||
// 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
|
||||
|
||||
+231
-35
@@ -4,29 +4,69 @@ package loader
|
||||
// modification. It does not touch the AST itself.
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"go/ast"
|
||||
"go/scanner"
|
||||
"go/token"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
/*
|
||||
#include <clang-c/Index.h> // if this fails, install libclang-7-dev
|
||||
#include <clang-c/Index.h> // if this fails, install libclang-8-dev
|
||||
#include <stdlib.h>
|
||||
#include <stdint.h>
|
||||
|
||||
int tinygo_clang_visitor(CXCursor c, CXCursor parent, CXClientData client_data);
|
||||
// This struct should be ABI-compatible on all platforms (uintptr_t has the same
|
||||
// alignment etc. as void*) but does not include void* pointers that are not
|
||||
// always real pointers.
|
||||
// The Go garbage collector assumes that all non-nil pointer-typed integers are
|
||||
// actually pointers. This is not always true, as data[1] often contains 0x1,
|
||||
// which is clearly not a valid pointer. Usually the GC won't catch this issue,
|
||||
// but occasionally it will leading to a crash with a vague error message.
|
||||
typedef struct {
|
||||
enum CXCursorKind kind;
|
||||
int xdata;
|
||||
uintptr_t data[3];
|
||||
} GoCXCursor;
|
||||
|
||||
// Forwarding functions. They are implemented in libclang_stubs.c and forward to
|
||||
// the real functions without doing anything else, thus they are entirely
|
||||
// compatible with the versions without tinygo_ prefix. The only difference is
|
||||
// the CXCursor type, which has been replaced with GoCXCursor.
|
||||
GoCXCursor tinygo_clang_getTranslationUnitCursor(CXTranslationUnit tu);
|
||||
unsigned tinygo_clang_visitChildren(GoCXCursor parent, CXCursorVisitor visitor, CXClientData client_data);
|
||||
CXString tinygo_clang_getCursorSpelling(GoCXCursor c);
|
||||
enum CXCursorKind tinygo_clang_getCursorKind(GoCXCursor c);
|
||||
CXType tinygo_clang_getCursorType(GoCXCursor c);
|
||||
GoCXCursor tinygo_clang_getTypeDeclaration(CXType t);
|
||||
CXType tinygo_clang_getTypedefDeclUnderlyingType(GoCXCursor c);
|
||||
CXType tinygo_clang_getCursorResultType(GoCXCursor c);
|
||||
int tinygo_clang_Cursor_getNumArguments(GoCXCursor c);
|
||||
GoCXCursor tinygo_clang_Cursor_getArgument(GoCXCursor c, unsigned i);
|
||||
|
||||
int tinygo_clang_globals_visitor(GoCXCursor c, GoCXCursor parent, CXClientData client_data);
|
||||
int tinygo_clang_struct_visitor(GoCXCursor c, GoCXCursor parent, CXClientData client_data);
|
||||
*/
|
||||
import "C"
|
||||
|
||||
var globalFileInfo *fileInfo
|
||||
// refMap stores references to types, used for clang_visitChildren.
|
||||
var refMap RefMap
|
||||
|
||||
func (info *fileInfo) parseFragment(fragment string, cflags []string) error {
|
||||
index := C.clang_createIndex(0, 1)
|
||||
var diagnosticSeverity = [...]string{
|
||||
C.CXDiagnostic_Ignored: "ignored",
|
||||
C.CXDiagnostic_Note: "note",
|
||||
C.CXDiagnostic_Warning: "warning",
|
||||
C.CXDiagnostic_Error: "error",
|
||||
C.CXDiagnostic_Fatal: "fatal",
|
||||
}
|
||||
|
||||
func (info *fileInfo) parseFragment(fragment string, cflags []string, posFilename string, posLine int) []error {
|
||||
index := C.clang_createIndex(0, 0)
|
||||
defer C.clang_disposeIndex(index)
|
||||
|
||||
filenameC := C.CString("cgo-fake.c")
|
||||
filenameC := C.CString(posFilename + "!cgo.c")
|
||||
defer C.free(unsafe.Pointer(filenameC))
|
||||
|
||||
fragmentC := C.CString(fragment)
|
||||
@@ -61,43 +101,80 @@ func (info *fileInfo) parseFragment(fragment string, cflags []string) error {
|
||||
}
|
||||
defer C.clang_disposeTranslationUnit(unit)
|
||||
|
||||
if C.clang_getNumDiagnostics(unit) != 0 {
|
||||
return errors.New("cgo: libclang cannot parse fragment")
|
||||
if numDiagnostics := int(C.clang_getNumDiagnostics(unit)); numDiagnostics != 0 {
|
||||
errs := []error{}
|
||||
addDiagnostic := func(diagnostic C.CXDiagnostic) {
|
||||
spelling := getString(C.clang_getDiagnosticSpelling(diagnostic))
|
||||
severity := diagnosticSeverity[C.clang_getDiagnosticSeverity(diagnostic)]
|
||||
location := C.clang_getDiagnosticLocation(diagnostic)
|
||||
var file C.CXFile
|
||||
var line C.unsigned
|
||||
var column C.unsigned
|
||||
var offset C.unsigned
|
||||
C.clang_getExpansionLocation(location, &file, &line, &column, &offset)
|
||||
filename := getString(C.clang_getFileName(file))
|
||||
if filename == posFilename+"!cgo.c" {
|
||||
// Adjust errors from the `import "C"` snippet.
|
||||
// Note: doesn't adjust filenames inside the error message
|
||||
// itself.
|
||||
filename = posFilename
|
||||
line += C.uint(posLine)
|
||||
offset = 0 // hard to calculate
|
||||
} else if filepath.IsAbs(filename) {
|
||||
// Relative paths for readability, like other Go parser errors.
|
||||
relpath, err := filepath.Rel(info.Program.Dir, filename)
|
||||
if err == nil {
|
||||
filename = relpath
|
||||
}
|
||||
}
|
||||
errs = append(errs, &scanner.Error{
|
||||
Pos: token.Position{
|
||||
Filename: filename,
|
||||
Offset: int(offset),
|
||||
Line: int(line),
|
||||
Column: int(column),
|
||||
},
|
||||
Msg: severity + ": " + spelling,
|
||||
})
|
||||
}
|
||||
for i := 0; i < numDiagnostics; i++ {
|
||||
diagnostic := C.clang_getDiagnostic(unit, C.uint(i))
|
||||
addDiagnostic(diagnostic)
|
||||
|
||||
// Child diagnostics (like notes on redefinitions).
|
||||
diagnostics := C.clang_getChildDiagnostics(diagnostic)
|
||||
for j := 0; j < int(C.clang_getNumDiagnosticsInSet(diagnostics)); j++ {
|
||||
addDiagnostic(C.clang_getDiagnosticInSet(diagnostics, C.uint(j)))
|
||||
}
|
||||
}
|
||||
return errs
|
||||
}
|
||||
|
||||
if globalFileInfo != nil {
|
||||
// There is a race condition here but that doesn't really matter as it
|
||||
// is a sanity check anyway.
|
||||
panic("libclang.go cannot be used concurrently yet")
|
||||
}
|
||||
globalFileInfo = info
|
||||
defer func() {
|
||||
globalFileInfo = nil
|
||||
}()
|
||||
|
||||
cursor := C.clang_getTranslationUnitCursor(unit)
|
||||
C.clang_visitChildren(cursor, (*[0]byte)(unsafe.Pointer(C.tinygo_clang_visitor)), C.CXClientData(uintptr(0)))
|
||||
ref := refMap.Put(info)
|
||||
defer refMap.Remove(ref)
|
||||
cursor := C.tinygo_clang_getTranslationUnitCursor(unit)
|
||||
C.tinygo_clang_visitChildren(cursor, C.CXCursorVisitor(C.tinygo_clang_globals_visitor), C.CXClientData(ref))
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
//export tinygo_clang_visitor
|
||||
func tinygo_clang_visitor(c, parent C.CXCursor, client_data C.CXClientData) C.int {
|
||||
info := globalFileInfo
|
||||
kind := C.clang_getCursorKind(c)
|
||||
//export tinygo_clang_globals_visitor
|
||||
func tinygo_clang_globals_visitor(c, parent C.GoCXCursor, client_data C.CXClientData) C.int {
|
||||
info := refMap.Get(unsafe.Pointer(client_data)).(*fileInfo)
|
||||
kind := C.tinygo_clang_getCursorKind(c)
|
||||
switch kind {
|
||||
case C.CXCursor_FunctionDecl:
|
||||
name := getString(C.clang_getCursorSpelling(c))
|
||||
cursorType := C.clang_getCursorType(c)
|
||||
name := getString(C.tinygo_clang_getCursorSpelling(c))
|
||||
cursorType := C.tinygo_clang_getCursorType(c)
|
||||
if C.clang_isFunctionTypeVariadic(cursorType) != 0 {
|
||||
return C.CXChildVisit_Continue // not supported
|
||||
}
|
||||
numArgs := int(C.clang_Cursor_getNumArguments(c))
|
||||
numArgs := int(C.tinygo_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))
|
||||
arg := C.tinygo_clang_Cursor_getArgument(c, C.uint(i))
|
||||
argName := getString(C.tinygo_clang_getCursorSpelling(arg))
|
||||
argType := C.clang_getArgType(cursorType, C.uint(i))
|
||||
if argName == "" {
|
||||
argName = "$" + strconv.Itoa(i)
|
||||
@@ -107,7 +184,7 @@ func tinygo_clang_visitor(c, parent C.CXCursor, client_data C.CXClientData) C.in
|
||||
typeExpr: info.makeASTType(argType),
|
||||
})
|
||||
}
|
||||
resultType := C.clang_getCursorResultType(c)
|
||||
resultType := C.tinygo_clang_getCursorResultType(c)
|
||||
if resultType.kind != C.CXType_Void {
|
||||
fn.results = &ast.FieldList{
|
||||
List: []*ast.Field{
|
||||
@@ -118,9 +195,9 @@ func tinygo_clang_visitor(c, parent C.CXCursor, client_data C.CXClientData) C.in
|
||||
}
|
||||
}
|
||||
case C.CXCursor_TypedefDecl:
|
||||
typedefType := C.clang_getCursorType(c)
|
||||
typedefType := C.tinygo_clang_getCursorType(c)
|
||||
name := getString(C.clang_getTypedefName(typedefType))
|
||||
underlyingType := C.clang_getTypedefDeclUnderlyingType(c)
|
||||
underlyingType := C.tinygo_clang_getTypedefDeclUnderlyingType(c)
|
||||
expr := info.makeASTType(underlyingType)
|
||||
if strings.HasPrefix(name, "_Cgo_") {
|
||||
expr := expr.(*ast.Ident)
|
||||
@@ -155,8 +232,8 @@ func tinygo_clang_visitor(c, parent C.CXCursor, client_data C.CXClientData) C.in
|
||||
typeExpr: expr,
|
||||
}
|
||||
case C.CXCursor_VarDecl:
|
||||
name := getString(C.clang_getCursorSpelling(c))
|
||||
cursorType := C.clang_getCursorType(c)
|
||||
name := getString(C.tinygo_clang_getCursorSpelling(c))
|
||||
cursorType := C.tinygo_clang_getCursorType(c)
|
||||
info.globals[name] = &globalInfo{
|
||||
typeExpr: info.makeASTType(cursorType),
|
||||
}
|
||||
@@ -220,6 +297,16 @@ func (info *fileInfo) makeASTType(typ C.CXType) ast.Expr {
|
||||
Star: info.importCPos,
|
||||
X: info.makeASTType(C.clang_getPointeeType(typ)),
|
||||
}
|
||||
case C.CXType_ConstantArray:
|
||||
return &ast.ArrayType{
|
||||
Lbrack: info.importCPos,
|
||||
Len: &ast.BasicLit{
|
||||
ValuePos: info.importCPos,
|
||||
Kind: token.INT,
|
||||
Value: strconv.FormatInt(int64(C.clang_getArraySize(typ)), 10),
|
||||
},
|
||||
Elt: info.makeASTType(C.clang_getElementType(typ)),
|
||||
}
|
||||
case C.CXType_FunctionProto:
|
||||
// Be compatible with gc, which uses the *[0]byte type for function
|
||||
// pointer types.
|
||||
@@ -237,6 +324,84 @@ func (info *fileInfo) makeASTType(typ C.CXType) ast.Expr {
|
||||
Name: "byte",
|
||||
},
|
||||
}
|
||||
case C.CXType_Typedef:
|
||||
typedefName := getString(C.clang_getTypedefName(typ))
|
||||
return &ast.Ident{
|
||||
NamePos: info.importCPos,
|
||||
Name: "C." + typedefName,
|
||||
}
|
||||
case C.CXType_Elaborated:
|
||||
underlying := C.clang_Type_getNamedType(typ)
|
||||
switch underlying.kind {
|
||||
case C.CXType_Record:
|
||||
cursor := C.tinygo_clang_getTypeDeclaration(typ)
|
||||
name := getString(C.tinygo_clang_getCursorSpelling(cursor))
|
||||
// It is possible that this is a recursive definition, for example
|
||||
// in linked lists (structs contain a pointer to the next element
|
||||
// of the same type). If the name exists in info.elaboratedTypes,
|
||||
// it is being processed, although it may not be fully defined yet.
|
||||
if _, ok := info.elaboratedTypes[name]; !ok {
|
||||
info.elaboratedTypes[name] = nil // predeclare (to avoid endless recursion)
|
||||
info.elaboratedTypes[name] = info.makeASTType(underlying)
|
||||
}
|
||||
return &ast.Ident{
|
||||
NamePos: info.importCPos,
|
||||
Name: "C.struct_" + name,
|
||||
}
|
||||
default:
|
||||
panic("unknown elaborated type")
|
||||
}
|
||||
case C.CXType_Record:
|
||||
cursor := C.tinygo_clang_getTypeDeclaration(typ)
|
||||
fieldList := &ast.FieldList{
|
||||
Opening: info.importCPos,
|
||||
Closing: info.importCPos,
|
||||
}
|
||||
ref := refMap.Put(struct {
|
||||
fieldList *ast.FieldList
|
||||
info *fileInfo
|
||||
}{fieldList, info})
|
||||
defer refMap.Remove(ref)
|
||||
C.tinygo_clang_visitChildren(cursor, C.CXCursorVisitor(C.tinygo_clang_struct_visitor), C.CXClientData(ref))
|
||||
switch C.tinygo_clang_getCursorKind(cursor) {
|
||||
case C.CXCursor_StructDecl:
|
||||
return &ast.StructType{
|
||||
Struct: info.importCPos,
|
||||
Fields: fieldList,
|
||||
}
|
||||
case C.CXCursor_UnionDecl:
|
||||
if len(fieldList.List) > 1 {
|
||||
// Insert a special field at the front (of zero width) as a
|
||||
// marker that this is struct is actually a union. This is done
|
||||
// by giving the field a name that cannot be expressed directly
|
||||
// in Go.
|
||||
// Other parts of the compiler look at the first element in a
|
||||
// struct (of size > 2) to know whether this is a union.
|
||||
// Note that we don't have to insert it for single-element
|
||||
// unions as they're basically equivalent to a struct.
|
||||
unionMarker := &ast.Field{
|
||||
Type: &ast.StructType{
|
||||
Struct: info.importCPos,
|
||||
},
|
||||
}
|
||||
unionMarker.Names = []*ast.Ident{
|
||||
&ast.Ident{
|
||||
NamePos: info.importCPos,
|
||||
Name: "C union",
|
||||
Obj: &ast.Object{
|
||||
Kind: ast.Var,
|
||||
Name: "C union",
|
||||
Decl: unionMarker,
|
||||
},
|
||||
},
|
||||
}
|
||||
fieldList.List = append([]*ast.Field{unionMarker}, fieldList.List...)
|
||||
}
|
||||
return &ast.StructType{
|
||||
Struct: info.importCPos,
|
||||
Fields: fieldList,
|
||||
}
|
||||
}
|
||||
}
|
||||
if typeName == "" {
|
||||
// Fallback, probably incorrect but at least the error points to an odd
|
||||
@@ -248,3 +413,34 @@ func (info *fileInfo) makeASTType(typ C.CXType) ast.Expr {
|
||||
Name: typeName,
|
||||
}
|
||||
}
|
||||
|
||||
//export tinygo_clang_struct_visitor
|
||||
func tinygo_clang_struct_visitor(c, parent C.GoCXCursor, client_data C.CXClientData) C.int {
|
||||
passed := refMap.Get(unsafe.Pointer(client_data)).(struct {
|
||||
fieldList *ast.FieldList
|
||||
info *fileInfo
|
||||
})
|
||||
fieldList := passed.fieldList
|
||||
info := passed.info
|
||||
if C.tinygo_clang_getCursorKind(c) != C.CXCursor_FieldDecl {
|
||||
panic("expected field inside cursor")
|
||||
}
|
||||
name := getString(C.tinygo_clang_getCursorSpelling(c))
|
||||
typ := C.tinygo_clang_getCursorType(c)
|
||||
field := &ast.Field{
|
||||
Type: info.makeASTType(typ),
|
||||
}
|
||||
field.Names = []*ast.Ident{
|
||||
&ast.Ident{
|
||||
NamePos: info.importCPos,
|
||||
Name: name,
|
||||
Obj: &ast.Object{
|
||||
Kind: ast.Var,
|
||||
Name: name,
|
||||
Decl: field,
|
||||
},
|
||||
},
|
||||
}
|
||||
fieldList.List = append(fieldList.List, field)
|
||||
return C.CXChildVisit_Continue
|
||||
}
|
||||
|
||||
@@ -3,9 +3,9 @@
|
||||
package loader
|
||||
|
||||
/*
|
||||
#cgo linux CFLAGS: -I/usr/lib/llvm-7/include
|
||||
#cgo linux CFLAGS: -I/usr/lib/llvm-8/include
|
||||
#cgo darwin CFLAGS: -I/usr/local/opt/llvm/include
|
||||
#cgo linux LDFLAGS: -L/usr/lib/llvm-7/lib -lclang
|
||||
#cgo linux LDFLAGS: -L/usr/lib/llvm-8/lib -lclang
|
||||
#cgo darwin LDFLAGS: -L/usr/local/opt/llvm/lib -lclang -lffi
|
||||
*/
|
||||
import "C"
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
|
||||
// This file implements some small trampoline functions. The signatures
|
||||
// are slightly different from the ones defined in libclang.go, but they
|
||||
// should be ABI compatible.
|
||||
|
||||
#include <clang-c/Index.h> // if this fails, install libclang-8-dev
|
||||
|
||||
CXCursor tinygo_clang_getTranslationUnitCursor(CXTranslationUnit tu) {
|
||||
return clang_getTranslationUnitCursor(tu);
|
||||
}
|
||||
|
||||
unsigned tinygo_clang_visitChildren(CXCursor parent, CXCursorVisitor visitor, CXClientData client_data) {
|
||||
return clang_visitChildren(parent, visitor, client_data);
|
||||
}
|
||||
|
||||
CXString tinygo_clang_getCursorSpelling(CXCursor c) {
|
||||
return clang_getCursorSpelling(c);
|
||||
}
|
||||
|
||||
enum CXCursorKind tinygo_clang_getCursorKind(CXCursor c) {
|
||||
return clang_getCursorKind(c);
|
||||
}
|
||||
|
||||
CXType tinygo_clang_getCursorType(CXCursor c) {
|
||||
return clang_getCursorType(c);
|
||||
}
|
||||
|
||||
CXCursor tinygo_clang_getTypeDeclaration(CXType t) {
|
||||
return clang_getTypeDeclaration(t);
|
||||
}
|
||||
|
||||
CXType tinygo_clang_getTypedefDeclUnderlyingType(CXCursor c) {
|
||||
return clang_getTypedefDeclUnderlyingType(c);
|
||||
}
|
||||
|
||||
CXType tinygo_clang_getCursorResultType(CXCursor c) {
|
||||
return clang_getCursorResultType(c);
|
||||
}
|
||||
|
||||
int tinygo_clang_Cursor_getNumArguments(CXCursor c) {
|
||||
return clang_Cursor_getNumArguments(c);
|
||||
}
|
||||
|
||||
CXCursor tinygo_clang_Cursor_getArgument(CXCursor c, unsigned i) {
|
||||
return clang_Cursor_getArgument(c, i);
|
||||
}
|
||||
+17
-11
@@ -14,13 +14,15 @@ import (
|
||||
|
||||
// Program holds all packages and some metadata about the program as a whole.
|
||||
type Program struct {
|
||||
Build *build.Context
|
||||
Packages map[string]*Package
|
||||
sorted []*Package
|
||||
fset *token.FileSet
|
||||
TypeChecker types.Config
|
||||
Dir string // current working directory (for error reporting)
|
||||
CFlags []string
|
||||
Build *build.Context
|
||||
OverlayBuild *build.Context
|
||||
ShouldOverlay func(path string) bool
|
||||
Packages map[string]*Package
|
||||
sorted []*Package
|
||||
fset *token.FileSet
|
||||
TypeChecker types.Config
|
||||
Dir string // current working directory (for error reporting)
|
||||
CFlags []string
|
||||
}
|
||||
|
||||
// Package holds a loaded package, its imports, and its parsed files.
|
||||
@@ -42,7 +44,11 @@ func (p *Program) Import(path, srcDir string) (*Package, error) {
|
||||
}
|
||||
|
||||
// Load this package.
|
||||
buildPkg, err := p.Build.Import(path, srcDir, build.ImportComment)
|
||||
ctx := p.Build
|
||||
if p.ShouldOverlay(path) {
|
||||
ctx = p.OverlayBuild
|
||||
}
|
||||
buildPkg, err := ctx.Import(path, srcDir, build.ImportComment)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -293,9 +299,9 @@ func (p *Package) parseFiles() ([]*ast.File, error) {
|
||||
fileErrs = append(fileErrs, err)
|
||||
continue
|
||||
}
|
||||
err = p.processCgo(path, f, append(p.CFlags, "-I"+p.Package.Dir))
|
||||
if err != nil {
|
||||
fileErrs = append(fileErrs, err)
|
||||
errs := p.processCgo(path, f, append(p.CFlags, "-I"+p.Package.Dir))
|
||||
if errs != nil {
|
||||
fileErrs = append(fileErrs, errs...)
|
||||
continue
|
||||
}
|
||||
files = append(files, f)
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
package loader
|
||||
|
||||
import (
|
||||
"sync"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
// #include <stdlib.h>
|
||||
import "C"
|
||||
|
||||
// RefMap is a convenient way to store opaque references that can be passed to
|
||||
// C. It is useful if an API uses function pointers and you cannot pass a Go
|
||||
// pointer but only a C pointer.
|
||||
type RefMap struct {
|
||||
refs map[unsafe.Pointer]interface{}
|
||||
lock sync.Mutex
|
||||
}
|
||||
|
||||
// Put stores a value in the map. It can later be retrieved using Get. It must
|
||||
// be removed using Remove to avoid memory leaks.
|
||||
func (m *RefMap) Put(v interface{}) unsafe.Pointer {
|
||||
m.lock.Lock()
|
||||
defer m.lock.Unlock()
|
||||
if m.refs == nil {
|
||||
m.refs = make(map[unsafe.Pointer]interface{}, 1)
|
||||
}
|
||||
ref := C.malloc(1)
|
||||
m.refs[ref] = v
|
||||
return ref
|
||||
}
|
||||
|
||||
// Get returns a stored value previously inserted with Put. Use the same
|
||||
// reference as you got from Put.
|
||||
func (m *RefMap) Get(ref unsafe.Pointer) interface{} {
|
||||
m.lock.Lock()
|
||||
defer m.lock.Unlock()
|
||||
return m.refs[ref]
|
||||
}
|
||||
|
||||
// Remove deletes a single reference from the map.
|
||||
func (m *RefMap) Remove(ref unsafe.Pointer) {
|
||||
m.lock.Lock()
|
||||
defer m.lock.Unlock()
|
||||
delete(m.refs, ref)
|
||||
C.free(ref)
|
||||
}
|
||||
@@ -21,13 +21,6 @@ import (
|
||||
"github.com/tinygo-org/tinygo/loader"
|
||||
)
|
||||
|
||||
var commands = map[string]string{
|
||||
"ar": "llvm-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 {
|
||||
@@ -198,7 +191,7 @@ func Compile(pkgName, outpath string, spec *TargetSpec, config *BuildConfig, act
|
||||
// Prepare link command.
|
||||
executable := filepath.Join(dir, "main")
|
||||
tmppath := executable // final file
|
||||
ldflags := append(spec.LDFlags, "-o", executable, objfile)
|
||||
ldflags := append(spec.LDFlags, "-o", executable, objfile, "-L", sourceDir())
|
||||
if spec.RTLib == "compiler-rt" {
|
||||
ldflags = append(ldflags, librt)
|
||||
}
|
||||
@@ -235,7 +228,11 @@ func Compile(pkgName, outpath string, spec *TargetSpec, config *BuildConfig, act
|
||||
}
|
||||
|
||||
// Link the object files together.
|
||||
err = Link(sourceDir(), spec.Linker, ldflags...)
|
||||
if linker, ok := commands[spec.Linker]; ok {
|
||||
err = Link(linker, ldflags...)
|
||||
} else {
|
||||
err = Link(spec.Linker, ldflags...)
|
||||
}
|
||||
if err != nil {
|
||||
return &commandError{"failed to link", executable, err}
|
||||
}
|
||||
|
||||
+62
-13
@@ -2,8 +2,10 @@ package main
|
||||
|
||||
import (
|
||||
"debug/elf"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
|
||||
"github.com/marcinbor85/gohex"
|
||||
)
|
||||
@@ -21,24 +23,72 @@ func (e ObjcopyError) Error() string {
|
||||
return e.Op + ": " + e.Err.Error()
|
||||
}
|
||||
|
||||
// ExtractTextSegment returns the .text segment and the first address from the
|
||||
// ELF file in the given path.
|
||||
func ExtractTextSegment(path string) (uint64, []byte, error) {
|
||||
type ProgSlice []*elf.Prog
|
||||
|
||||
func (s ProgSlice) Len() int { return len(s) }
|
||||
func (s ProgSlice) Less(i, j int) bool { return s[i].Paddr < s[j].Paddr }
|
||||
func (s ProgSlice) Swap(i, j int) { s[i], s[j] = s[j], s[i] }
|
||||
|
||||
// ExtractROM extracts a firmware image and the first load address from the
|
||||
// given ELF file. It tries to emulate the behavior of objcopy.
|
||||
func ExtractROM(path string) (uint64, []byte, error) {
|
||||
f, err := elf.Open(path)
|
||||
if err != nil {
|
||||
return 0, nil, ObjcopyError{"failed to open ELF file to extract text segment", err}
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
text := f.Section(".text")
|
||||
if text == nil {
|
||||
return 0, nil, ObjcopyError{"file does not contain .text segment: " + path, nil}
|
||||
// The GNU objcopy command does the following for firmware extraction (from
|
||||
// the man page):
|
||||
// > When objcopy generates a raw binary file, it will essentially produce a
|
||||
// > memory dump of the contents of the input object file. All symbols and
|
||||
// > relocation information will be discarded. The memory dump will start at
|
||||
// > the load address of the lowest section copied into the output file.
|
||||
|
||||
// Find the lowest section address.
|
||||
startAddr := ^uint64(0)
|
||||
for _, section := range f.Sections {
|
||||
if section.Type != elf.SHT_PROGBITS || section.Flags&elf.SHF_ALLOC == 0 {
|
||||
continue
|
||||
}
|
||||
if section.Addr < startAddr {
|
||||
startAddr = section.Addr
|
||||
}
|
||||
}
|
||||
data, err := text.Data()
|
||||
if err != nil {
|
||||
return 0, nil, ObjcopyError{"failed to extract .text segment from ELF file", err}
|
||||
|
||||
progs := make(ProgSlice, 0, 2)
|
||||
for _, prog := range f.Progs {
|
||||
if prog.Type != elf.PT_LOAD || prog.Filesz == 0 {
|
||||
continue
|
||||
}
|
||||
progs = append(progs, prog)
|
||||
}
|
||||
if len(progs) == 0 {
|
||||
return 0, nil, ObjcopyError{"file does not contain ROM segments: " + path, nil}
|
||||
}
|
||||
sort.Sort(progs)
|
||||
|
||||
var rom []byte
|
||||
for _, prog := range progs {
|
||||
if prog.Paddr != progs[0].Paddr+uint64(len(rom)) {
|
||||
return 0, nil, ObjcopyError{"ROM segments are non-contiguous: " + path, nil}
|
||||
}
|
||||
data, err := ioutil.ReadAll(prog.Open())
|
||||
if err != nil {
|
||||
return 0, nil, ObjcopyError{"failed to extract segment from ELF file: " + path, err}
|
||||
}
|
||||
rom = append(rom, data...)
|
||||
}
|
||||
if progs[0].Paddr < startAddr {
|
||||
// The lowest memory address is before the first section. This means
|
||||
// that there is some extra data loaded at the start of the image that
|
||||
// should be discarded.
|
||||
// Example: ELF files where .text doesn't start at address 0 because
|
||||
// there is a bootloader at the start.
|
||||
return startAddr, rom[startAddr-progs[0].Paddr:], nil
|
||||
} else {
|
||||
return progs[0].Paddr, rom, nil
|
||||
}
|
||||
return text.Addr, data, nil
|
||||
}
|
||||
|
||||
// Objcopy converts an ELF file to a different (simpler) output file format:
|
||||
@@ -51,7 +101,7 @@ func Objcopy(infile, outfile string) error {
|
||||
defer f.Close()
|
||||
|
||||
// Read the .text segment.
|
||||
addr, data, err := ExtractTextSegment(infile)
|
||||
addr, data, err := ExtractROM(infile)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -65,12 +115,11 @@ func Objcopy(infile, outfile string) error {
|
||||
return err
|
||||
case ".hex":
|
||||
mem := gohex.NewMemory()
|
||||
mem.SetStartAddress(uint32(addr)) // ignored in most cases (Intel-specific)
|
||||
err := mem.AddBinary(uint32(addr), data)
|
||||
if err != nil {
|
||||
return ObjcopyError{"failed to create .hex file", err}
|
||||
}
|
||||
mem.DumpIntelHex(f, 32) // TODO: handle error
|
||||
mem.DumpIntelHex(f, 16) // TODO: handle error
|
||||
return nil
|
||||
default:
|
||||
panic("unreachable")
|
||||
|
||||
@@ -1,79 +0,0 @@
|
||||
// This program runs on an Arduino that has the following four devices connected:
|
||||
// - Button connected to D2
|
||||
// - Rotary analog dial connected to A0
|
||||
// - RGB LED connected to D3, D5, and D6 used as PWM pins
|
||||
// - BlinkM I2C RGB LED
|
||||
//
|
||||
// Pushing the button switches which color is selected.
|
||||
// Rotating the dial changes the value for the currently selected color.
|
||||
// Changing the color value updates the color displayed on both the
|
||||
// PWM-controlled RGB LED and the I2C-controlled BlinkM.
|
||||
package main
|
||||
|
||||
import (
|
||||
"machine"
|
||||
"time"
|
||||
)
|
||||
|
||||
const (
|
||||
buttonPin = 2
|
||||
redPin = 3
|
||||
greenPin = 5
|
||||
bluePin = 6
|
||||
|
||||
red = 0
|
||||
green = 1
|
||||
blue = 2
|
||||
)
|
||||
|
||||
func main() {
|
||||
machine.InitADC()
|
||||
machine.InitPWM()
|
||||
machine.I2C0.Configure(machine.I2CConfig{})
|
||||
|
||||
// Init BlinkM
|
||||
machine.I2C0.WriteTo(0x09, []byte("o"))
|
||||
|
||||
button := machine.GPIO{buttonPin}
|
||||
button.Configure(machine.GPIOConfig{Mode: machine.GPIO_INPUT})
|
||||
|
||||
dial := machine.ADC{machine.ADC0}
|
||||
dial.Configure()
|
||||
|
||||
redLED := machine.PWM{redPin}
|
||||
redLED.Configure()
|
||||
|
||||
greenLED := machine.PWM{greenPin}
|
||||
greenLED.Configure()
|
||||
|
||||
blueLED := machine.PWM{bluePin}
|
||||
blueLED.Configure()
|
||||
|
||||
selectedColor := red
|
||||
colors := []uint16{0, 0, 0}
|
||||
|
||||
for {
|
||||
// If we pushed the button, switch active color.
|
||||
if !button.Get() {
|
||||
if selectedColor == blue {
|
||||
selectedColor = red
|
||||
} else {
|
||||
selectedColor++
|
||||
}
|
||||
}
|
||||
|
||||
// Change the intensity for the currently selected color based on the dial setting.
|
||||
colors[selectedColor] = (dial.Get())
|
||||
|
||||
// Update the RGB LED.
|
||||
redLED.Set(colors[red])
|
||||
greenLED.Set(colors[green])
|
||||
blueLED.Set(colors[blue])
|
||||
|
||||
// Update the BlinkM.
|
||||
machine.I2C0.WriteTo(0x09, []byte("n"))
|
||||
machine.I2C0.WriteTo(0x09, []byte{byte(colors[red] >> 8), byte(colors[green] >> 8), byte(colors[blue] >> 8)})
|
||||
|
||||
time.Sleep(time.Millisecond * 100)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
// blink program for the BBC micro:bit that uses the entire LED matrix
|
||||
package main
|
||||
|
||||
import (
|
||||
"machine"
|
||||
"time"
|
||||
)
|
||||
|
||||
func main() {
|
||||
machine.InitLEDMatrix()
|
||||
|
||||
for {
|
||||
machine.ClearLEDMatrix()
|
||||
time.Sleep(time.Millisecond * 500)
|
||||
|
||||
machine.SetEntireLEDMatrixOn()
|
||||
time.Sleep(time.Millisecond * 500)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
html/*
|
||||
@@ -0,0 +1,15 @@
|
||||
export: clean wasm_exec
|
||||
tinygo build -o ./html/wasm.wasm -target wasm ./export/wasm.go
|
||||
cp ./export/wasm.js ./html/
|
||||
cp ./export/index.html ./html/
|
||||
|
||||
main: clean wasm_exec
|
||||
tinygo build -o ./html/wasm.wasm -target wasm ./main/main.go
|
||||
cp ./main/index.html ./html/
|
||||
|
||||
wasm_exec:
|
||||
cp ../../../targets/wasm_exec.js ./html/
|
||||
|
||||
clean:
|
||||
rm -rf ./html
|
||||
mkdir ./html
|
||||
@@ -0,0 +1,111 @@
|
||||
# TinyGo WebAssembly examples
|
||||
|
||||
The examples here show two different ways of using WebAssembly with TinyGo;
|
||||
|
||||
1. Defining and exporting functions via the `//go:export <name>` directive. See
|
||||
[the export folder](./export) for an example of this.
|
||||
1. Defining and executing a `func main()`. This is similar to how the Go
|
||||
standard library implementation works. See [the main folder](./main) for an
|
||||
example of this.
|
||||
|
||||
## Building
|
||||
|
||||
Build using the `tinygo` compiler:
|
||||
|
||||
```bash
|
||||
$ tinygo build -o ./wasm.wasm -target wasm ./main/main.go
|
||||
```
|
||||
|
||||
This creates a `wasm.wasm` file, which we can load in JavaScript and execute in
|
||||
a browser.
|
||||
|
||||
This examples folder contains two examples that can be built using `make`:
|
||||
|
||||
```bash
|
||||
$ make export
|
||||
```
|
||||
|
||||
```bash
|
||||
$ make main
|
||||
```
|
||||
|
||||
## Running
|
||||
|
||||
Start the local webserver:
|
||||
|
||||
```bash
|
||||
$ go run main.go
|
||||
Serving ./html on http://localhost:8080
|
||||
```
|
||||
|
||||
`fmt.Println` prints to the browser console.
|
||||
|
||||
## How it works
|
||||
|
||||
Execution of the contents require a few JS helper functions which are called
|
||||
from WebAssembly. We have defined these in
|
||||
[wasm_exec.js](../../../targets/wasm_exec.js). It is based on
|
||||
`$GOROOT/misc/wasm/wasm_exec.js` from the standard library, but is slightly
|
||||
different. Ensure you are using the same version of `wasm_exec.js` as the
|
||||
version of `tinygo` you are using to compile.
|
||||
|
||||
The general steps required to run the WebAssembly file in the browser includes
|
||||
loading it into JavaScript with `WebAssembly.instantiateStreaming`, or
|
||||
`WebAssembly.instantiate` in some browsers:
|
||||
|
||||
```js
|
||||
const go = new Go(); // Defined in wasm_exec.js
|
||||
const WASM_URL = 'wasm.wasm';
|
||||
|
||||
var wasm;
|
||||
|
||||
if ('instantiateStreaming' in WebAssembly) {
|
||||
WebAssembly.instantiateStreaming(fetch(WASM_URL), go.importObject).then(function (obj) {
|
||||
wasm = obj.instance;
|
||||
go.run(wasm);
|
||||
})
|
||||
} else {
|
||||
fetch(WASM_URL).then(resp =>
|
||||
resp.arrayBuffer()
|
||||
).then(bytes =>
|
||||
WebAssembly.instantiate(bytes, go.importObject).then(function (obj) {
|
||||
wasm = obj.instance;
|
||||
go.run(wasm);
|
||||
})
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
If you have used explicit exports, you can call them by invoking them under the
|
||||
`wasm.exports` namespace. See the [`export`](./export/wasm.js) directory for an
|
||||
example of this.
|
||||
|
||||
In addition to this piece of JavaScript, it is important that the file is served
|
||||
with the correct `Content-Type` header set.
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"log"
|
||||
"net/http"
|
||||
"strings"
|
||||
)
|
||||
|
||||
const dir = "./html"
|
||||
|
||||
func main() {
|
||||
fs := http.FileServer(http.Dir(dir))
|
||||
log.Print("Serving " + dir + " on http://localhost:8080")
|
||||
http.ListenAndServe(":8080", http.HandlerFunc(func(resp http.ResponseWriter, req *http.Request) {
|
||||
if strings.HasSuffix(req.URL.Path, ".wasm") {
|
||||
resp.Header().Set("content-type", "application/wasm")
|
||||
}
|
||||
|
||||
fs.ServeHTTP(resp, req)
|
||||
}))
|
||||
}
|
||||
```
|
||||
|
||||
This simple server serves anything inside the `./html` directory on port `8080`,
|
||||
setting any `*.wasm` files `Content-Type` header appropriately.
|
||||
@@ -0,0 +1,20 @@
|
||||
<!DOCTYPE html>
|
||||
|
||||
<html>
|
||||
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<title>Go WebAssembly</title>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<script src="wasm_exec.js" defer></script>
|
||||
<script src="wasm.js" defer></script>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<h1>WebAssembly</h1>
|
||||
<p>Add two numbers, using WebAssembly:</p>
|
||||
<input type="number" id="a" value="2" /> + <input type="number" id="b" value="2" /> = <input type="number"
|
||||
id="result" readonly />
|
||||
</body>
|
||||
|
||||
</html>
|
||||
@@ -16,10 +16,10 @@ func add(a, b int) int {
|
||||
//go:export update
|
||||
func update() {
|
||||
document := js.Global().Get("document")
|
||||
a_str := document.Call("getElementById", "a").Get("value").String()
|
||||
b_str := document.Call("getElementById", "b").Get("value").String()
|
||||
a, _ := strconv.Atoi(a_str)
|
||||
b, _ := strconv.Atoi(b_str)
|
||||
result := a + b
|
||||
aStr := document.Call("getElementById", "a").Get("value").String()
|
||||
bStr := document.Call("getElementById", "b").Get("value").String()
|
||||
a, _ := strconv.Atoi(aStr)
|
||||
b, _ := strconv.Atoi(bStr)
|
||||
result := add(a, b)
|
||||
document.Call("getElementById", "result").Set("value", result)
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
'use strict';
|
||||
|
||||
const WASM_URL = '../../../wasm.wasm';
|
||||
const WASM_URL = 'wasm.wasm';
|
||||
|
||||
var wasm;
|
||||
|
||||
@@ -14,7 +14,7 @@ function init() {
|
||||
|
||||
const go = new Go();
|
||||
if ('instantiateStreaming' in WebAssembly) {
|
||||
WebAssembly.instantiateStreaming(fetch(WASM_URL), go.importObject).then(function(obj) {
|
||||
WebAssembly.instantiateStreaming(fetch(WASM_URL), go.importObject).then(function (obj) {
|
||||
wasm = obj.instance;
|
||||
go.run(wasm);
|
||||
updateResult();
|
||||
@@ -23,7 +23,7 @@ function init() {
|
||||
fetch(WASM_URL).then(resp =>
|
||||
resp.arrayBuffer()
|
||||
).then(bytes =>
|
||||
WebAssembly.instantiate(bytes, go.importObject).then(function(obj) {
|
||||
WebAssembly.instantiate(bytes, go.importObject).then(function (obj) {
|
||||
wasm = obj.instance;
|
||||
go.run(wasm);
|
||||
updateResult();
|
||||
@@ -0,0 +1,8 @@
|
||||
# WebAssembly main execution example
|
||||
|
||||
A simple hello world that prints to the browser console.
|
||||
|
||||
## License
|
||||
|
||||
Note that `index.html` is copied almost verbatim from the Go 1.12 source at
|
||||
`$GOROOT/misc/wasm/wasm_exec.html`. Its license applies to this file.
|
||||
@@ -0,0 +1,49 @@
|
||||
<!doctype html>
|
||||
<!--
|
||||
Copyright 2018 The Go Authors. All rights reserved.
|
||||
Use of this source code is governed by a BSD-style
|
||||
license that can be found in the LICENSE file.
|
||||
-->
|
||||
<html>
|
||||
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>Go wasm</title>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<!--
|
||||
Add the following polyfill for Microsoft Edge 17/18 support:
|
||||
<script src="https://cdn.jsdelivr.net/npm/text-encoding@0.7.0/lib/encoding.min.js"></script>
|
||||
(see https://caniuse.com/#feat=textencoder)
|
||||
-->
|
||||
<script src="wasm_exec.js"></script>
|
||||
<script>
|
||||
if (!WebAssembly.instantiateStreaming) { // polyfill
|
||||
WebAssembly.instantiateStreaming = async (resp, importObject) => {
|
||||
const source = await (await resp).arrayBuffer();
|
||||
return await WebAssembly.instantiate(source, importObject);
|
||||
};
|
||||
}
|
||||
|
||||
const go = new Go();
|
||||
let mod, inst;
|
||||
WebAssembly.instantiateStreaming(fetch("wasm.wasm"), go.importObject).then((result) => {
|
||||
mod = result.module;
|
||||
inst = result.instance;
|
||||
document.getElementById("runButton").disabled = false;
|
||||
}).catch((err) => {
|
||||
console.error(err);
|
||||
});
|
||||
|
||||
async function run() {
|
||||
console.clear();
|
||||
await go.run(inst);
|
||||
inst = await WebAssembly.instantiate(mod, go.importObject); // reset instance
|
||||
}
|
||||
</script>
|
||||
|
||||
<button onClick="run();" id="runButton" disabled>Run</button>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
@@ -0,0 +1,9 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
)
|
||||
|
||||
func main() {
|
||||
fmt.Println("Hello world!")
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"log"
|
||||
"net/http"
|
||||
"strings"
|
||||
)
|
||||
|
||||
const dir = "./html"
|
||||
|
||||
func main() {
|
||||
fs := http.FileServer(http.Dir(dir))
|
||||
log.Print("Serving " + dir + " on http://localhost:8080")
|
||||
http.ListenAndServe(":8080", http.HandlerFunc(func(resp http.ResponseWriter, req *http.Request) {
|
||||
if strings.HasSuffix(req.URL.Path, ".wasm") {
|
||||
resp.Header().Set("content-type", "application/wasm")
|
||||
}
|
||||
|
||||
fs.ServeHTTP(resp, req)
|
||||
}))
|
||||
}
|
||||
@@ -1,16 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8"/>
|
||||
<title>Go WebAssembly</title>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1"/>
|
||||
<script src="../../../targets/wasm_exec.js" defer></script>
|
||||
<script src="wasm.js" defer></script>
|
||||
</head>
|
||||
<body>
|
||||
<h1>WebAssembly</h1>
|
||||
<p>Add two numbers, using WebAssembly:</p>
|
||||
<input type="number" id="a" value="2"/> + <input type="number" id="b" value="2"/> = <input type="number" id="result" readonly/>
|
||||
</body>
|
||||
</html>
|
||||
@@ -76,8 +76,16 @@ const (
|
||||
|
||||
// I2C on the Circuit Playground Express.
|
||||
var (
|
||||
I2C0 = I2C{Bus: sam.SERCOM5_I2CM} // external device
|
||||
I2C1 = I2C{Bus: sam.SERCOM1_I2CM} // internal device
|
||||
// external device
|
||||
I2C0 = I2C{Bus: sam.SERCOM5_I2CM,
|
||||
SDA: SDA_PIN,
|
||||
SCL: SCL_PIN,
|
||||
PinMode: GPIO_SERCOM}
|
||||
// internal device
|
||||
I2C1 = I2C{Bus: sam.SERCOM1_I2CM,
|
||||
SDA: SDA1_PIN,
|
||||
SCL: SCL1_PIN,
|
||||
PinMode: GPIO_SERCOM_ALT}
|
||||
)
|
||||
|
||||
// SPI pins (internal flash)
|
||||
|
||||
@@ -44,8 +44,8 @@ const (
|
||||
|
||||
// UART1 pins
|
||||
const (
|
||||
UART_TX_PIN = D1
|
||||
UART_RX_PIN = D0
|
||||
UART_TX_PIN = D10
|
||||
UART_RX_PIN = D11
|
||||
)
|
||||
|
||||
// I2C pins
|
||||
@@ -56,7 +56,10 @@ const (
|
||||
|
||||
// I2C on the ItsyBitsy M0.
|
||||
var (
|
||||
I2C0 = I2C{Bus: sam.SERCOM3_I2CM}
|
||||
I2C0 = I2C{Bus: sam.SERCOM3_I2CM,
|
||||
SDA: SDA_PIN,
|
||||
SCL: SCL_PIN,
|
||||
PinMode: GPIO_SERCOM}
|
||||
)
|
||||
|
||||
// SPI pins
|
||||
|
||||
@@ -0,0 +1,112 @@
|
||||
// +build stm32,stm32f4disco
|
||||
|
||||
package machine
|
||||
|
||||
const (
|
||||
PA0 = portA + 0
|
||||
PA1 = portA + 1
|
||||
PA2 = portA + 2
|
||||
PA3 = portA + 3
|
||||
PA4 = portA + 4
|
||||
PA5 = portA + 5
|
||||
PA6 = portA + 6
|
||||
PA7 = portA + 7
|
||||
PA8 = portA + 8
|
||||
PA9 = portA + 9
|
||||
PA10 = portA + 10
|
||||
PA11 = portA + 11
|
||||
PA12 = portA + 12
|
||||
PA13 = portA + 13
|
||||
PA14 = portA + 14
|
||||
PA15 = portA + 15
|
||||
|
||||
PB0 = portB + 0
|
||||
PB1 = portB + 1
|
||||
PB2 = portB + 2
|
||||
PB3 = portB + 3
|
||||
PB4 = portB + 4
|
||||
PB5 = portB + 5
|
||||
PB6 = portB + 6
|
||||
PB7 = portB + 7
|
||||
PB8 = portB + 8
|
||||
PB9 = portB + 9
|
||||
PB10 = portB + 10
|
||||
PB11 = portB + 11
|
||||
PB12 = portB + 12
|
||||
PB13 = portB + 13
|
||||
PB14 = portB + 14
|
||||
PB15 = portB + 15
|
||||
|
||||
PC0 = portC + 0
|
||||
PC1 = portC + 1
|
||||
PC2 = portC + 2
|
||||
PC3 = portC + 3
|
||||
PC4 = portC + 4
|
||||
PC5 = portC + 5
|
||||
PC6 = portC + 6
|
||||
PC7 = portC + 7
|
||||
PC8 = portC + 8
|
||||
PC9 = portC + 9
|
||||
PC10 = portC + 10
|
||||
PC11 = portC + 11
|
||||
PC12 = portC + 12
|
||||
PC13 = portC + 13
|
||||
PC14 = portC + 14
|
||||
PC15 = portC + 15
|
||||
|
||||
PD0 = portD + 0
|
||||
PD1 = portD + 1
|
||||
PD2 = portD + 2
|
||||
PD3 = portD + 3
|
||||
PD4 = portD + 4
|
||||
PD5 = portD + 5
|
||||
PD6 = portD + 6
|
||||
PD7 = portD + 7
|
||||
PD8 = portD + 8
|
||||
PD9 = portD + 9
|
||||
PD10 = portD + 10
|
||||
PD11 = portD + 11
|
||||
PD12 = portD + 12
|
||||
PD13 = portD + 13
|
||||
PD14 = portD + 14
|
||||
PD15 = portD + 15
|
||||
|
||||
PE0 = portE + 0
|
||||
PE1 = portE + 1
|
||||
PE2 = portE + 2
|
||||
PE3 = portE + 3
|
||||
PE4 = portE + 4
|
||||
PE5 = portE + 5
|
||||
PE6 = portE + 6
|
||||
PE7 = portE + 7
|
||||
PE8 = portE + 8
|
||||
PE9 = portE + 9
|
||||
PE10 = portE + 10
|
||||
PE11 = portE + 11
|
||||
PE12 = portE + 12
|
||||
PE13 = portE + 13
|
||||
PE14 = portE + 14
|
||||
PE15 = portE + 15
|
||||
|
||||
PH0 = portH + 0
|
||||
PH1 = portH + 1
|
||||
)
|
||||
|
||||
const (
|
||||
LED = LED_BUILTIN
|
||||
LED1 = LED_GREEN
|
||||
LED2 = LED_ORANGE
|
||||
LED3 = LED_RED
|
||||
LED4 = LED_BLUE
|
||||
LED_BUILTIN = LED_GREEN
|
||||
LED_GREEN = PD12
|
||||
LED_ORANGE = PD13
|
||||
LED_RED = PD14
|
||||
LED_BLUE = PD15
|
||||
)
|
||||
|
||||
// UART pins
|
||||
const (
|
||||
UART_TX_PIN = PA2
|
||||
UART_RX_PIN = PA3
|
||||
)
|
||||
@@ -416,7 +416,7 @@ var (
|
||||
UART0 = USBCDC{Buffer: NewRingBuffer()}
|
||||
|
||||
// The first hardware serial port on the SAMD21. Uses the SERCOM0 interface.
|
||||
UART1 = UART{Bus: sam.SERCOM0_USART, Buffer: NewRingBuffer()}
|
||||
UART1 = UART{Bus: sam.SERCOM1_USART, Buffer: NewRingBuffer()}
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -453,26 +453,26 @@ func (uart UART) Configure(config UARTConfig) {
|
||||
// determine pads
|
||||
var txpad, rxpad int
|
||||
switch config.TX {
|
||||
case UART_TX_PIN:
|
||||
case PA10:
|
||||
txpad = sercomTXPad2
|
||||
case D10:
|
||||
case PA18:
|
||||
txpad = sercomTXPad2
|
||||
case D11:
|
||||
case PA16:
|
||||
txpad = sercomTXPad0
|
||||
default:
|
||||
panic("Invalid TX pin for UART")
|
||||
}
|
||||
|
||||
switch config.RX {
|
||||
case UART_RX_PIN:
|
||||
case PA11:
|
||||
rxpad = sercomRXPad3
|
||||
case D10:
|
||||
case PA18:
|
||||
rxpad = sercomRXPad2
|
||||
case D11:
|
||||
case PA16:
|
||||
rxpad = sercomRXPad0
|
||||
case D12:
|
||||
case PA19:
|
||||
rxpad = sercomRXPad3
|
||||
case D13:
|
||||
case PA17:
|
||||
rxpad = sercomRXPad1
|
||||
default:
|
||||
panic("Invalid RX pin for UART")
|
||||
@@ -531,11 +531,11 @@ func (uart UART) Configure(config UARTConfig) {
|
||||
uart.Bus.INTENSET = sam.SERCOM_USART_INTENSET_RXC
|
||||
|
||||
// Enable RX IRQ.
|
||||
if config.TX == UART_TX_PIN {
|
||||
if config.TX == PA10 {
|
||||
// UART0
|
||||
arm.EnableIRQ(sam.IRQ_SERCOM0)
|
||||
} else {
|
||||
// UART1
|
||||
// UART1 which is the normal default, since UART0 is used for USBCDC.
|
||||
arm.EnableIRQ(sam.IRQ_SERCOM1)
|
||||
}
|
||||
}
|
||||
@@ -563,7 +563,7 @@ func (uart UART) WriteByte(c byte) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
//go:export SERCOM0_IRQHandler
|
||||
//go:export SERCOM1_IRQHandler
|
||||
func handleUART1() {
|
||||
// should reset IRQ
|
||||
UART1.Receive(byte((UART1.Bus.DATA & 0xFF)))
|
||||
@@ -572,7 +572,10 @@ func handleUART1() {
|
||||
|
||||
// I2C on the SAMD21.
|
||||
type I2C struct {
|
||||
Bus *sam.SERCOM_I2CM_Type
|
||||
Bus *sam.SERCOM_I2CM_Type
|
||||
SCL uint8
|
||||
SDA uint8
|
||||
PinMode GPIOMode
|
||||
}
|
||||
|
||||
// I2CConfig is used to store config info for I2C.
|
||||
@@ -608,7 +611,7 @@ func (i2c I2C) Configure(config I2CConfig) {
|
||||
config.Frequency = TWI_FREQ_100KHZ
|
||||
}
|
||||
|
||||
// reset SERCOM3
|
||||
// reset SERCOM
|
||||
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 {
|
||||
@@ -632,8 +635,8 @@ func (i2c I2C) Configure(config I2CConfig) {
|
||||
}
|
||||
|
||||
// enable pins
|
||||
GPIO{SDA_PIN}.Configure(GPIOConfig{Mode: GPIO_SERCOM})
|
||||
GPIO{SCL_PIN}.Configure(GPIOConfig{Mode: GPIO_SERCOM})
|
||||
GPIO{i2c.SDA}.Configure(GPIOConfig{Mode: i2c.PinMode})
|
||||
GPIO{i2c.SCL}.Configure(GPIOConfig{Mode: i2c.PinMode})
|
||||
}
|
||||
|
||||
// SetBaudRate sets the communication speed for the I2C.
|
||||
|
||||
@@ -14,4 +14,5 @@ const (
|
||||
portE
|
||||
portF
|
||||
portG
|
||||
portH
|
||||
)
|
||||
|
||||
@@ -0,0 +1,220 @@
|
||||
// +build stm32,stm32f407
|
||||
|
||||
package machine
|
||||
|
||||
// Peripheral abstraction layer for the stm32.
|
||||
|
||||
import (
|
||||
"device/arm"
|
||||
"device/stm32"
|
||||
)
|
||||
|
||||
const CPU_FREQUENCY = 168000000
|
||||
|
||||
const (
|
||||
// Mode Flag
|
||||
GPIO_OUTPUT = 0
|
||||
GPIO_INPUT = GPIO_INPUT_PULLDOWN
|
||||
GPIO_INPUT_FLOATING = 1
|
||||
GPIO_INPUT_PULLDOWN = 2
|
||||
GPIO_INPUT_PULLUP = 3
|
||||
|
||||
// for UART
|
||||
GPIO_UART_TX = 4
|
||||
GPIO_UART_RX = 5
|
||||
|
||||
//GPIOx_MODER
|
||||
GPIO_MODE_INPUT = 0
|
||||
GPIO_MODE_GENERAL_OUTPUT = 1
|
||||
GPIO_MODE_ALTERNABTIVE = 2
|
||||
GPIO_MODE_ANALOG = 3
|
||||
|
||||
//GPIOx_OTYPER
|
||||
GPIO_OUTPUT_MODE_PUSH_PULL = 0
|
||||
GPIO_OUTPUT_MODE_OPEN_DRAIN = 1
|
||||
|
||||
// GPIOx_OSPEEDR
|
||||
GPIO_SPEED_LOW = 0
|
||||
GPIO_SPEED_MID = 1
|
||||
GPIO_SPEED_HI = 2
|
||||
GPIO_SPEED_VERY_HI = 3
|
||||
|
||||
// GPIOx_PUPDR
|
||||
GPIO_FLOATING = 0
|
||||
GPIO_PULL_UP = 1
|
||||
GPIO_PULL_DOWN = 2
|
||||
)
|
||||
|
||||
func (p GPIO) getPort() *stm32.GPIO_Type {
|
||||
switch p.Pin / 16 {
|
||||
case 0:
|
||||
return stm32.GPIOA
|
||||
case 1:
|
||||
return stm32.GPIOB
|
||||
case 2:
|
||||
return stm32.GPIOC
|
||||
case 3:
|
||||
return stm32.GPIOD
|
||||
case 4:
|
||||
return stm32.GPIOE
|
||||
case 5:
|
||||
return stm32.GPIOF
|
||||
case 6:
|
||||
return stm32.GPIOG
|
||||
case 7:
|
||||
return stm32.GPIOH
|
||||
case 8:
|
||||
return stm32.GPIOI
|
||||
default:
|
||||
panic("machine: unknown port")
|
||||
}
|
||||
}
|
||||
|
||||
// enableClock enables the clock for this desired GPIO port.
|
||||
func (p GPIO) enableClock() {
|
||||
switch p.Pin / 16 {
|
||||
case 0:
|
||||
stm32.RCC.AHB1ENR |= stm32.RCC_AHB1ENR_GPIOAEN
|
||||
case 1:
|
||||
stm32.RCC.AHB1ENR |= stm32.RCC_AHB1ENR_GPIOBEN
|
||||
case 2:
|
||||
stm32.RCC.AHB1ENR |= stm32.RCC_AHB1ENR_GPIOCEN
|
||||
case 3:
|
||||
stm32.RCC.AHB1ENR |= stm32.RCC_AHB1ENR_GPIODEN
|
||||
case 4:
|
||||
stm32.RCC.AHB1ENR |= stm32.RCC_AHB1ENR_GPIOEEN
|
||||
case 5:
|
||||
stm32.RCC.AHB1ENR |= stm32.RCC_AHB1ENR_GPIOFEN
|
||||
case 6:
|
||||
stm32.RCC.AHB1ENR |= stm32.RCC_AHB1ENR_GPIOGEN
|
||||
case 7:
|
||||
stm32.RCC.AHB1ENR |= stm32.RCC_AHB1ENR_GPIOHEN
|
||||
case 8:
|
||||
stm32.RCC.AHB1ENR |= stm32.RCC_AHB1ENR_GPIOIEN
|
||||
default:
|
||||
panic("machine: unknown port")
|
||||
}
|
||||
}
|
||||
|
||||
// Configure this pin with the given configuration.
|
||||
func (p GPIO) Configure(config GPIOConfig) {
|
||||
// Configure the GPIO pin.
|
||||
p.enableClock()
|
||||
port := p.getPort()
|
||||
pin := p.Pin % 16
|
||||
pos := pin * 2
|
||||
|
||||
if config.Mode == GPIO_INPUT_FLOATING {
|
||||
port.MODER = stm32.RegValue((uint32(port.MODER)&^(0x3<<pos) | (uint32(GPIO_MODE_INPUT) << pos)))
|
||||
port.PUPDR = stm32.RegValue((uint32(port.PUPDR)&^(0x3<<pos) | (uint32(GPIO_FLOATING) << pos)))
|
||||
} else if config.Mode == GPIO_INPUT_PULLDOWN {
|
||||
port.MODER = stm32.RegValue((uint32(port.MODER)&^(0x3<<pos) | (uint32(GPIO_MODE_INPUT) << pos)))
|
||||
port.PUPDR = stm32.RegValue((uint32(port.PUPDR)&^(0x3<<pos) | (uint32(GPIO_PULL_DOWN) << pos)))
|
||||
} else if config.Mode == GPIO_INPUT_PULLUP {
|
||||
port.MODER = stm32.RegValue((uint32(port.MODER)&^(0x3<<pos) | (uint32(GPIO_MODE_INPUT) << pos)))
|
||||
port.PUPDR = stm32.RegValue((uint32(port.PUPDR)&^(0x3<<pos) | (uint32(GPIO_PULL_UP) << pos)))
|
||||
} else if config.Mode == GPIO_OUTPUT {
|
||||
port.MODER = stm32.RegValue((uint32(port.MODER)&^(0x3<<pos) | (uint32(GPIO_MODE_GENERAL_OUTPUT) << pos)))
|
||||
port.OSPEEDR = stm32.RegValue((uint32(port.OSPEEDR)&^(0x3<<pos) | (uint32(GPIO_SPEED_HI) << pos)))
|
||||
} else if config.Mode == GPIO_UART_TX {
|
||||
port.MODER = stm32.RegValue((uint32(port.MODER)&^(0x3<<pos) | (uint32(GPIO_MODE_ALTERNABTIVE) << pos)))
|
||||
port.OSPEEDR = stm32.RegValue((uint32(port.OSPEEDR)&^(0x3<<pos) | (uint32(GPIO_SPEED_HI) << pos)))
|
||||
port.PUPDR = stm32.RegValue((uint32(port.PUPDR)&^(0x3<<pos) | (uint32(GPIO_PULL_UP) << pos)))
|
||||
p.setAltFunc(0x7)
|
||||
} else if config.Mode == GPIO_UART_RX {
|
||||
port.MODER = stm32.RegValue((uint32(port.MODER)&^(0x3<<pos) | (uint32(GPIO_MODE_ALTERNABTIVE) << pos)))
|
||||
port.PUPDR = stm32.RegValue((uint32(port.PUPDR)&^(0x3<<pos) | (uint32(GPIO_FLOATING) << pos)))
|
||||
p.setAltFunc(0x7)
|
||||
}
|
||||
}
|
||||
|
||||
func (p GPIO) setAltFunc(af uint32) {
|
||||
port := p.getPort()
|
||||
pin := p.Pin % 16
|
||||
pos := pin * 4
|
||||
if pin >= 8 {
|
||||
port.AFRH = stm32.RegValue(uint32(port.AFRH)&^(0xF<<pos) | ((af & 0xF) << pos))
|
||||
} else {
|
||||
port.AFRL = stm32.RegValue(uint32(port.AFRL)&^(0xF<<pos) | ((af & 0xF) << pos))
|
||||
}
|
||||
}
|
||||
|
||||
// Set the pin to high or low.
|
||||
// Warning: only use this on an output pin!
|
||||
func (p GPIO) Set(high bool) {
|
||||
port := p.getPort()
|
||||
pin := p.Pin % 16
|
||||
if high {
|
||||
port.BSRR = 1 << pin
|
||||
} else {
|
||||
port.BSRR = 1 << (pin + 16)
|
||||
}
|
||||
}
|
||||
|
||||
// UART
|
||||
type UART struct {
|
||||
Buffer *RingBuffer
|
||||
}
|
||||
|
||||
var (
|
||||
// Both UART0 and UART1 refer to USART2.
|
||||
UART0 = UART{Buffer: NewRingBuffer()}
|
||||
UART1 = &UART0
|
||||
)
|
||||
|
||||
// Configure the UART.
|
||||
func (uart UART) Configure(config UARTConfig) {
|
||||
// Default baud rate to 115200.
|
||||
if config.BaudRate == 0 {
|
||||
config.BaudRate = 115200
|
||||
}
|
||||
|
||||
// pins
|
||||
switch config.TX {
|
||||
default:
|
||||
// use standard TX/RX pins PA2 and PA3
|
||||
GPIO{UART_TX_PIN}.Configure(GPIOConfig{Mode: GPIO_UART_TX})
|
||||
GPIO{UART_RX_PIN}.Configure(GPIOConfig{Mode: GPIO_UART_RX})
|
||||
}
|
||||
|
||||
// Enable USART2 clock
|
||||
stm32.RCC.APB1ENR |= stm32.RCC_APB1ENR_USART2EN
|
||||
|
||||
/*
|
||||
Set baud rate(115200)
|
||||
OVER8 = 0, APB2 = 42mhz
|
||||
+----------+--------+
|
||||
| baudrate | BRR |
|
||||
+----------+--------+
|
||||
| 1200 | 0x88B8 |
|
||||
| 2400 | 0x445C |
|
||||
| 9600 | 0x1117 |
|
||||
| 19200 | 0x88C |
|
||||
| 38400 | 0x446 |
|
||||
| 57600 | 0x2D9 |
|
||||
| 115200 | 0x16D |
|
||||
+----------+--------+
|
||||
*/
|
||||
stm32.USART2.BRR = 0x16c
|
||||
|
||||
// Enable USART2 port.
|
||||
stm32.USART2.CR1 = stm32.USART_CR1_TE | stm32.USART_CR1_RE | stm32.USART_CR1_RXNEIE | stm32.USART_CR1_UE
|
||||
|
||||
// Enable RX IRQ.
|
||||
arm.SetPriority(stm32.IRQ_USART2, 0xc0)
|
||||
arm.EnableIRQ(stm32.IRQ_USART2)
|
||||
}
|
||||
|
||||
// WriteByte writes a byte of data to the UART.
|
||||
func (uart UART) WriteByte(c byte) error {
|
||||
stm32.USART2.DR = stm32.RegValue(c)
|
||||
|
||||
for (stm32.USART2.SR & stm32.USART_SR_TXE) == 0 {
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
//go:export USART2_IRQHandler
|
||||
func handleUSART2() {
|
||||
UART1.Receive(byte((stm32.USART2.DR & 0xFF)))
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
// +build wasm
|
||||
// +build avr cortexm wasm
|
||||
|
||||
package os
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
// +build darwin linux
|
||||
// +build darwin linux,!avr,!cortexm
|
||||
|
||||
package os
|
||||
|
||||
|
||||
+3
-3
@@ -134,18 +134,18 @@ func (t Type) String() string {
|
||||
}
|
||||
|
||||
func (t Type) Kind() Kind {
|
||||
if t % 2 == 0 {
|
||||
if t%2 == 0 {
|
||||
// basic type
|
||||
return Kind((t >> 1) % 32)
|
||||
} else {
|
||||
return Kind(t >> 1) % 8 + 19
|
||||
return Kind(t>>1)%8 + 19
|
||||
}
|
||||
}
|
||||
|
||||
func (t Type) Elem() Type {
|
||||
switch t.Kind() {
|
||||
case Chan, Ptr, Slice:
|
||||
if (t >> 4) % 2 != 0 {
|
||||
if (t>>4)%2 != 0 {
|
||||
panic("unimplemented: (reflect.Type).Elem() for named types")
|
||||
}
|
||||
return t >> 5
|
||||
|
||||
+21
-2
@@ -335,7 +335,7 @@ func (v Value) Index(i int) Value {
|
||||
typecode: v.Type().Elem(),
|
||||
indirect: true,
|
||||
}
|
||||
addr := uintptr(slice.Data) + elem.Type().Size() * uintptr(i) // pointer to new value
|
||||
addr := uintptr(slice.Data) + elem.Type().Size()*uintptr(i) // pointer to new value
|
||||
elem.value = unsafe.Pointer(addr)
|
||||
return elem
|
||||
case String:
|
||||
@@ -348,7 +348,7 @@ func (v Value) Index(i int) Value {
|
||||
}
|
||||
return Value{
|
||||
typecode: Uint8.basicType(),
|
||||
value: unsafe.Pointer(uintptr(*(*uint8)(unsafe.Pointer(s.Data + uintptr(i))))),
|
||||
value: unsafe.Pointer(uintptr(*(*uint8)(unsafe.Pointer(s.Data + uintptr(i))))),
|
||||
}
|
||||
case Array:
|
||||
panic("unimplemented: (reflect.Value).Index()")
|
||||
@@ -365,6 +365,25 @@ func (v Value) MapIndex(key Value) Value {
|
||||
panic("unimplemented: (reflect.Value).MapIndex()")
|
||||
}
|
||||
|
||||
func (v Value) MapRange() *MapIter {
|
||||
panic("unimplemented: (reflect.Value).MapRange()")
|
||||
}
|
||||
|
||||
type MapIter struct {
|
||||
}
|
||||
|
||||
func (it *MapIter) Key() Value {
|
||||
panic("unimplemented: (*reflect.MapIter).Key()")
|
||||
}
|
||||
|
||||
func (it *MapIter) Value() Value {
|
||||
panic("unimplemented: (*reflect.MapIter).Value()")
|
||||
}
|
||||
|
||||
func (it *MapIter) Next() bool {
|
||||
panic("unimplemented: (*reflect.MapIter).Next()")
|
||||
}
|
||||
|
||||
func (v Value) Set(x Value) {
|
||||
if !v.indirect {
|
||||
panic("reflect: value is not addressable")
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
// +build arm,!avr,!cortexm
|
||||
|
||||
package runtime
|
||||
|
||||
const GOARCH = "arm"
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// +build tinygo.arm
|
||||
// +build cortexm
|
||||
|
||||
package runtime
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// +build wasm,!tinygo.arm,!avr
|
||||
// +build wasm
|
||||
|
||||
package runtime
|
||||
|
||||
|
||||
@@ -41,6 +41,7 @@ const (
|
||||
|
||||
func chanSendStub(caller *coroutine, ch *channel, _ unsafe.Pointer, size uintptr)
|
||||
func chanRecvStub(caller *coroutine, ch *channel, _ unsafe.Pointer, _ *bool, size uintptr)
|
||||
func deadlockStub()
|
||||
|
||||
// chanSend sends a single value over the channel. If this operation can
|
||||
// complete immediately (there is a goroutine waiting for a value), it sends the
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
package runtime
|
||||
|
||||
// This file implements some data types that may be useful for some
|
||||
// implementations of func values.
|
||||
|
||||
import (
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
// funcValue is the underlying type of func values, depending on which func
|
||||
// value representation was used.
|
||||
type funcValue struct {
|
||||
context unsafe.Pointer // function context, for closures and bound methods
|
||||
id uintptr // ptrtoint of *funcValueWithSignature before lowering, opaque index (non-0) after lowering
|
||||
}
|
||||
|
||||
// funcValueWithSignature is used before the func lowering pass.
|
||||
type funcValueWithSignature struct {
|
||||
funcPtr uintptr // ptrtoint of the actual function pointer
|
||||
signature *uint8 // pointer to identify this signature (the value is undef)
|
||||
}
|
||||
|
||||
// getFuncPtr is a dummy function that may be used if the func lowering pass is
|
||||
// not used. It is generally too slow but may be a useful fallback to debug the
|
||||
// func lowering pass.
|
||||
func getFuncPtr(val funcValue, signature *uint8) uintptr {
|
||||
return (*funcValueWithSignature)(unsafe.Pointer(val.id)).funcPtr
|
||||
}
|
||||
@@ -43,15 +43,22 @@ type interfaceMethodInfo struct {
|
||||
funcptr uintptr // bitcast from the actual function pointer
|
||||
}
|
||||
|
||||
// Pseudo function call used while putting a concrete value in an interface,
|
||||
// that must be lowered to a constant uintptr.
|
||||
func makeInterface(typecode *uint8, methodSet *interfaceMethodInfo) uintptr
|
||||
type typecodeID struct{}
|
||||
|
||||
// Pseudo type used before interface lowering. By using a struct instead of a
|
||||
// function call, this is simpler to reason about during init interpretation
|
||||
// than a function call. Also, by keeping the method set around it is easier to
|
||||
// implement interfaceImplements in the interp package.
|
||||
type typeInInterface struct {
|
||||
typecode *typecodeID
|
||||
methodSet *interfaceMethodInfo // nil or a GEP of an array
|
||||
}
|
||||
|
||||
// Pseudo function call used during a type assert. It is used during interface
|
||||
// lowering, to assign the lowest type numbers to the types with the most type
|
||||
// asserts. Also, it is replaced with const false if this type assert can never
|
||||
// happen.
|
||||
func typeAssert(actualType uintptr, assertedType *uint8) bool
|
||||
func typeAssert(actualType uintptr, assertedType *typecodeID) bool
|
||||
|
||||
// Pseudo function call that returns whether a given type implements all methods
|
||||
// of the given interface.
|
||||
|
||||
@@ -1 +1,3 @@
|
||||
TheVersion = `go0.1.0`
|
||||
package sys
|
||||
|
||||
const TheVersion = `go0.1.0`
|
||||
|
||||
@@ -1,28 +0,0 @@
|
||||
// +build js,tinygo.arm avr
|
||||
|
||||
package runtime
|
||||
|
||||
// This file stubs out some external functions declared by the syscall/js
|
||||
// package. They cannot be used on microcontrollers.
|
||||
|
||||
type js_ref uint64
|
||||
|
||||
//go:linkname js_valueGet syscall/js.valueGet
|
||||
func js_valueGet(v js_ref, p string) js_ref {
|
||||
return 0
|
||||
}
|
||||
|
||||
//go:linkname js_valueNew syscall/js.valueNew
|
||||
func js_valueNew(v js_ref, args []js_ref) (js_ref, bool) {
|
||||
return 0, true
|
||||
}
|
||||
|
||||
//go:linkname js_valueCall syscall/js.valueCall
|
||||
func js_valueCall(v js_ref, m string, args []js_ref) (js_ref, bool) {
|
||||
return 0, true
|
||||
}
|
||||
|
||||
//go:linkname js_stringVal syscall/js.stringVal
|
||||
func js_stringVal(x string) js_ref {
|
||||
return 0
|
||||
}
|
||||
+8
-14
@@ -27,6 +27,14 @@ func _recover() interface{} {
|
||||
return nil
|
||||
}
|
||||
|
||||
// See emitNilCheck in compiler/asserts.go.
|
||||
// This function is a dummy function that has its first and only parameter
|
||||
// marked 'nocapture' to work around a limitation in LLVM: a regular pointer
|
||||
// comparison captures the pointer.
|
||||
func isnil(ptr *uint8) bool {
|
||||
return ptr == nil
|
||||
}
|
||||
|
||||
// Panic when trying to dereference a nil pointer.
|
||||
func nilpanic() {
|
||||
runtimePanic("nil pointer dereference")
|
||||
@@ -41,17 +49,3 @@ func lookuppanic() {
|
||||
func slicepanic() {
|
||||
runtimePanic("slice out of range")
|
||||
}
|
||||
|
||||
// Check for bounds in *ssa.MakeSlice.
|
||||
func sliceBoundsCheckMake(length, capacity uintptr, max uintptr) {
|
||||
if length > capacity || capacity > max {
|
||||
runtimePanic("slice size out of range")
|
||||
}
|
||||
}
|
||||
|
||||
// Check for bounds in *ssa.MakeSlice. Supports 64-bit indexes.
|
||||
func sliceBoundsCheckMake64(length, capacity uint64, max uintptr) {
|
||||
if length > capacity || capacity > uint64(max) {
|
||||
runtimePanic("slice size out of range")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -39,7 +39,7 @@ func printuint16(n uint16) {
|
||||
printuint32(uint32(n))
|
||||
}
|
||||
|
||||
func printint16(n uint16) {
|
||||
func printint16(n int16) {
|
||||
printint32(int32(n))
|
||||
}
|
||||
|
||||
|
||||
@@ -84,9 +84,13 @@ func sleep(d int64) {
|
||||
sleepTicks(timeUnit(d / tickMicros))
|
||||
}
|
||||
|
||||
func nanotime() int64 {
|
||||
return int64(ticks()) * tickMicros
|
||||
}
|
||||
|
||||
//go:linkname now time.now
|
||||
func now() (sec int64, nsec int32, mono int64) {
|
||||
mono = int64(ticks()) * tickMicros
|
||||
mono = nanotime()
|
||||
sec = mono / (1000 * 1000 * 1000)
|
||||
nsec = int32(mono - sec*(1000*1000*1000))
|
||||
return
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// +build tinygo.arm
|
||||
// +build cortexm
|
||||
|
||||
package runtime
|
||||
|
||||
@@ -46,10 +46,16 @@ func abort() {
|
||||
}
|
||||
}
|
||||
|
||||
// Implement memset for compiler-rt.
|
||||
// Implement memset for LLVM and compiler-rt.
|
||||
//go:export memset
|
||||
func memset(ptr unsafe.Pointer, c byte, size uintptr) {
|
||||
func libc_memset(ptr unsafe.Pointer, c byte, size uintptr) {
|
||||
for i := uintptr(0); i < size; i++ {
|
||||
*(*byte)(unsafe.Pointer(uintptr(ptr) + i)) = c
|
||||
}
|
||||
}
|
||||
|
||||
// Implement memmove for LLVM and compiler-rt.
|
||||
//go:export memmove
|
||||
func libc_memmove(dst, src unsafe.Pointer, size uintptr) {
|
||||
memmove(dst, src, size)
|
||||
}
|
||||
@@ -0,0 +1,208 @@
|
||||
// +build stm32,stm32f407
|
||||
|
||||
package runtime
|
||||
|
||||
import (
|
||||
"device/arm"
|
||||
"device/stm32"
|
||||
"machine"
|
||||
)
|
||||
|
||||
func init() {
|
||||
initCLK()
|
||||
initTIM3()
|
||||
machine.UART0.Configure(machine.UARTConfig{})
|
||||
initTIM7()
|
||||
}
|
||||
|
||||
func putchar(c byte) {
|
||||
machine.UART0.WriteByte(c)
|
||||
}
|
||||
|
||||
const (
|
||||
HSE_STARTUP_TIMEOUT = 0x0500
|
||||
/* PLL Options - See RM0090 Reference Manual pg. 95 */
|
||||
PLL_M = 8 /* PLL_VCO = (HSE_VALUE or HSI_VLAUE / PLL_M) * PLL_N */
|
||||
PLL_N = 336
|
||||
PLL_P = 2 /* SYSCLK = PLL_VCO / PLL_P */
|
||||
PLL_Q = 7 /* USB OTS FS, SDIO and RNG Clock = PLL_VCO / PLL_Q */
|
||||
)
|
||||
|
||||
/*
|
||||
clock settings
|
||||
+-------------+--------+
|
||||
| HSE | 8mhz |
|
||||
| SYSCLK | 168mhz |
|
||||
| HCLK | 168mhz |
|
||||
| APB2(PCLK2) | 84mhz |
|
||||
| APB1(PCLK1) | 42mhz |
|
||||
+-------------+--------+
|
||||
*/
|
||||
func initCLK() {
|
||||
|
||||
// Reset clock registers
|
||||
// Set HSION
|
||||
stm32.RCC.CR |= stm32.RCC_CR_HSION
|
||||
for (stm32.RCC.CR & stm32.RCC_CR_HSIRDY) == 0 {
|
||||
}
|
||||
|
||||
// Reset CFGR
|
||||
stm32.RCC.CFGR = 0x00000000
|
||||
// Reset HSEON, CSSON and PLLON
|
||||
stm32.RCC.CR &= 0xFEF6FFFF
|
||||
// Reset PLLCFGR
|
||||
stm32.RCC.PLLCFGR = 0x24003010
|
||||
// Reset HSEBYP
|
||||
stm32.RCC.CR &= 0xFFFBFFFF
|
||||
// Disable all interrupts
|
||||
stm32.RCC.CIR = 0x00000000
|
||||
|
||||
// Set up the clock
|
||||
var startupCounter uint32 = 0
|
||||
|
||||
// Enable HSE
|
||||
stm32.RCC.CR = stm32.RCC_CR_HSEON
|
||||
// Wait till HSE is ready and if timeout is reached exit
|
||||
for {
|
||||
startupCounter++
|
||||
if (stm32.RCC.CR&stm32.RCC_CR_HSERDY != 0) || (startupCounter == HSE_STARTUP_TIMEOUT) {
|
||||
break
|
||||
}
|
||||
}
|
||||
if (stm32.RCC.CR & stm32.RCC_CR_HSERDY) != 0 {
|
||||
// Enable high performance mode, System frequency up to 168MHz
|
||||
stm32.RCC.APB1ENR |= stm32.RCC_APB1ENR_PWREN
|
||||
stm32.PWR.CR |= 0x4000 // PWR_CR_VOS
|
||||
// HCLK = SYSCLK / 1
|
||||
stm32.RCC.CFGR |= (0x0 << stm32.RCC_CFGR_HPRE_Pos)
|
||||
// PCLK2 = HCLK / 2
|
||||
stm32.RCC.CFGR |= (0x4 << stm32.RCC_CFGR_PPRE2_Pos)
|
||||
// PCLK1 = HCLK / 4
|
||||
stm32.RCC.CFGR |= (0x5 << stm32.RCC_CFGR_PPRE1_Pos)
|
||||
// Configure the main PLL
|
||||
// PLL Options - See RM0090 Reference Manual pg. 95
|
||||
stm32.RCC.PLLCFGR = PLL_M | (PLL_N << 6) | (((PLL_P >> 1) - 1) << 16) |
|
||||
(1 << stm32.RCC_PLLCFGR_PLLSRC_Pos) | (PLL_Q << 24)
|
||||
// Enable main PLL
|
||||
stm32.RCC.CR |= stm32.RCC_CR_PLLON
|
||||
// Wait till the main PLL is ready
|
||||
for (stm32.RCC.CR & stm32.RCC_CR_PLLRDY) == 0 {
|
||||
}
|
||||
// Configure Flash prefetch, Instruction cache, Data cache and wait state
|
||||
stm32.FLASH.ACR = stm32.FLASH_ACR_ICEN | stm32.FLASH_ACR_DCEN | (5 << stm32.FLASH_ACR_LATENCY_Pos)
|
||||
// Select the main PLL as system clock source
|
||||
stm32.RCC.CFGR &^= stm32.RCC_CFGR_SW0 | stm32.RCC_CFGR_SW1
|
||||
stm32.RCC.CFGR |= (0x2 << stm32.RCC_CFGR_SW0_Pos)
|
||||
for (stm32.RCC.CFGR & (0x3 << stm32.RCC_CFGR_SWS0_Pos)) != (0x2 << stm32.RCC_CFGR_SWS0_Pos) {
|
||||
}
|
||||
|
||||
} else {
|
||||
// If HSE failed to start up, the application will have wrong clock configuration
|
||||
for {
|
||||
}
|
||||
}
|
||||
// Enable the CCM RAM clock
|
||||
stm32.RCC.AHB1ENR |= (1 << 20)
|
||||
|
||||
}
|
||||
|
||||
const tickMicros = 1000
|
||||
|
||||
var (
|
||||
// tick in milliseconds
|
||||
tickCount timeUnit
|
||||
)
|
||||
|
||||
//go:volatile
|
||||
type isrFlag bool
|
||||
|
||||
var timerWakeup isrFlag
|
||||
|
||||
// Enable the TIM3 clock.(sleep count)
|
||||
func initTIM3() {
|
||||
stm32.RCC.APB1ENR |= stm32.RCC_APB1ENR_TIM3EN
|
||||
|
||||
arm.SetPriority(stm32.IRQ_TIM3, 0xc3)
|
||||
arm.EnableIRQ(stm32.IRQ_TIM3)
|
||||
}
|
||||
|
||||
// Enable the TIM7 clock.(tick count)
|
||||
func initTIM7() {
|
||||
stm32.RCC.APB1ENR |= stm32.RCC_APB1ENR_TIM7EN
|
||||
|
||||
// CK_INT = APB1 x2 = 84mhz
|
||||
stm32.TIM7.PSC = 84000000/10000 - 1 // 84mhz to 10khz(0.1ms)
|
||||
stm32.TIM7.ARR = stm32.RegValue(10) - 1 // interrupt per 1ms
|
||||
|
||||
// Enable the hardware interrupt.
|
||||
stm32.TIM7.DIER |= stm32.TIM_DIER_UIE
|
||||
|
||||
// Enable the timer.
|
||||
stm32.TIM7.CR1 |= stm32.TIM_CR1_CEN
|
||||
|
||||
arm.SetPriority(stm32.IRQ_TIM7, 0xc1)
|
||||
arm.EnableIRQ(stm32.IRQ_TIM7)
|
||||
}
|
||||
|
||||
const asyncScheduler = false
|
||||
|
||||
// sleepTicks should sleep for specific number of microseconds.
|
||||
func sleepTicks(d timeUnit) {
|
||||
timerSleep(uint32(d))
|
||||
}
|
||||
|
||||
// number of ticks (microseconds) since start.
|
||||
func ticks() timeUnit {
|
||||
// milliseconds to microseconds
|
||||
return tickCount * 1000
|
||||
}
|
||||
|
||||
// ticks are in microseconds
|
||||
func timerSleep(ticks uint32) {
|
||||
timerWakeup = false
|
||||
|
||||
// CK_INT = APB1 x2 = 84mhz
|
||||
// prescale counter down from 84mhz to 10khz aka 0.1 ms frequency.
|
||||
stm32.TIM3.PSC = 84000000/10000 - 1 // 8399
|
||||
|
||||
// set duty aka duration
|
||||
arr := (ticks / 100) - 1 // convert from microseconds to 0.1 ms
|
||||
if arr == 0 {
|
||||
arr = 1 // avoid blocking
|
||||
}
|
||||
stm32.TIM3.ARR = stm32.RegValue(arr)
|
||||
|
||||
// Enable the hardware interrupt.
|
||||
stm32.TIM3.DIER |= stm32.TIM_DIER_UIE
|
||||
|
||||
// Enable the timer.
|
||||
stm32.TIM3.CR1 |= stm32.TIM_CR1_CEN
|
||||
|
||||
// wait till timer wakes up
|
||||
for !timerWakeup {
|
||||
arm.Asm("wfi")
|
||||
}
|
||||
}
|
||||
|
||||
//go:export TIM3_IRQHandler
|
||||
func handleTIM3() {
|
||||
if (stm32.TIM3.SR & stm32.TIM_SR_UIF) > 0 {
|
||||
// Disable the timer.
|
||||
stm32.TIM3.CR1 &^= stm32.TIM_CR1_CEN
|
||||
|
||||
// clear the update flag
|
||||
stm32.TIM3.SR &^= stm32.TIM_SR_UIF
|
||||
|
||||
// timer was triggered
|
||||
timerWakeup = true
|
||||
}
|
||||
}
|
||||
|
||||
//go:export TIM7_IRQHandler
|
||||
func handleTIM7() {
|
||||
if (stm32.TIM7.SR & stm32.TIM_SR_UIF) > 0 {
|
||||
// clear the update flag
|
||||
stm32.TIM7.SR &^= stm32.TIM_SR_UIF
|
||||
tickCount++
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
// +build darwin linux
|
||||
// +build darwin linux,!avr,!cortexm
|
||||
|
||||
package runtime
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// +build wasm,!tinygo.arm,!avr
|
||||
// +build wasm
|
||||
|
||||
package runtime
|
||||
|
||||
@@ -37,6 +37,11 @@ func putchar(c byte) {
|
||||
resource_write(stdout, &c, 1)
|
||||
}
|
||||
|
||||
//go:linkname setEventHandler syscall/js.setEventHandler
|
||||
func setEventHandler(fn func()) {
|
||||
// TODO
|
||||
}
|
||||
|
||||
//go:export go_scheduler
|
||||
func go_scheduler() {
|
||||
scheduler()
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
package syscall
|
||||
|
||||
// Most code here has been copied from the Go sources:
|
||||
// https://github.com/golang/go/blob/go1.12/src/syscall/syscall_js.go
|
||||
// It has the following copyright note:
|
||||
//
|
||||
// Copyright 2018 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// An Errno is an unsigned number describing an error condition.
|
||||
// It implements the error interface. The zero Errno is by convention
|
||||
// a non-error, so code to convert from Errno to error should use:
|
||||
// err = nil
|
||||
// if errno != 0 {
|
||||
// err = errno
|
||||
// }
|
||||
type Errno uintptr
|
||||
|
||||
func (e Errno) Error() string {
|
||||
return "errno " + itoa(int(e))
|
||||
}
|
||||
|
||||
func (e Errno) Temporary() bool {
|
||||
return e == EINTR || e == EMFILE || e.Timeout()
|
||||
}
|
||||
|
||||
func (e Errno) Timeout() bool {
|
||||
return e == EAGAIN || e == EWOULDBLOCK || e == ETIMEDOUT
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
// Copyright 2009 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package syscall
|
||||
|
||||
func itoa(val int) string { // do it here rather than with fmt to avoid dependency
|
||||
if val < 0 {
|
||||
return "-" + uitoa(uint(-val))
|
||||
}
|
||||
return uitoa(uint(val))
|
||||
}
|
||||
|
||||
func uitoa(val uint) string {
|
||||
var buf [32]byte // big enough for int64
|
||||
i := len(buf) - 1
|
||||
for val >= 10 {
|
||||
buf[i] = byte(val%10 + '0')
|
||||
i--
|
||||
val /= 10
|
||||
}
|
||||
buf[i] = byte(val + '0')
|
||||
return string(buf[i:])
|
||||
}
|
||||
@@ -0,0 +1,123 @@
|
||||
// +build avr cortexm
|
||||
|
||||
package syscall
|
||||
|
||||
// Most code here has been copied from the Go sources:
|
||||
// https://github.com/golang/go/blob/go1.12/src/syscall/syscall_js.go
|
||||
// It has the following copyright note:
|
||||
//
|
||||
// Copyright 2018 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// A Signal is a number describing a process signal.
|
||||
// It implements the os.Signal interface.
|
||||
type Signal int
|
||||
|
||||
const (
|
||||
_ Signal = iota
|
||||
SIGCHLD
|
||||
SIGINT
|
||||
SIGKILL
|
||||
SIGTRAP
|
||||
SIGQUIT
|
||||
SIGTERM
|
||||
)
|
||||
|
||||
// File system
|
||||
|
||||
const (
|
||||
Stdin = 0
|
||||
Stdout = 1
|
||||
Stderr = 2
|
||||
)
|
||||
|
||||
const (
|
||||
O_RDONLY = 0
|
||||
O_WRONLY = 1
|
||||
O_RDWR = 2
|
||||
|
||||
O_CREAT = 0100
|
||||
O_CREATE = O_CREAT
|
||||
O_TRUNC = 01000
|
||||
O_APPEND = 02000
|
||||
O_EXCL = 0200
|
||||
O_SYNC = 010000
|
||||
|
||||
O_CLOEXEC = 0
|
||||
)
|
||||
|
||||
func Getenv(key string) (value string, found bool) {
|
||||
return "", false // stub
|
||||
}
|
||||
|
||||
func Open(path string, mode int, perm uint32) (fd int, err error) {
|
||||
return 0, ENOSYS
|
||||
}
|
||||
|
||||
func Read(fd int, p []byte) (n int, err error) {
|
||||
return 0, ENOSYS
|
||||
}
|
||||
|
||||
func Seek(fd int, offset int64, whence int) (off int64, err error) {
|
||||
return 0, ENOSYS
|
||||
}
|
||||
|
||||
func Close(fd int) (err error) {
|
||||
return ENOSYS
|
||||
}
|
||||
|
||||
// Processes
|
||||
|
||||
type WaitStatus uint32
|
||||
|
||||
func (w WaitStatus) Exited() bool { return false }
|
||||
func (w WaitStatus) ExitStatus() int { return 0 }
|
||||
func (w WaitStatus) Signaled() bool { return false }
|
||||
func (w WaitStatus) Signal() Signal { return 0 }
|
||||
func (w WaitStatus) CoreDump() bool { return false }
|
||||
func (w WaitStatus) Stopped() bool { return false }
|
||||
func (w WaitStatus) Continued() bool { return false }
|
||||
func (w WaitStatus) StopSignal() Signal { return 0 }
|
||||
func (w WaitStatus) TrapCause() int { return 0 }
|
||||
|
||||
// XXX made up
|
||||
type Rusage struct {
|
||||
Utime Timeval
|
||||
Stime Timeval
|
||||
}
|
||||
|
||||
// XXX made up
|
||||
type ProcAttr struct {
|
||||
Dir string
|
||||
Env []string
|
||||
Files []uintptr
|
||||
Sys *SysProcAttr
|
||||
}
|
||||
|
||||
type SysProcAttr struct {
|
||||
}
|
||||
|
||||
func Getegid() int { return 1 }
|
||||
func Geteuid() int { return 1 }
|
||||
func Getgid() int { return 1 }
|
||||
func Getgroups() ([]int, error) { return []int{1}, nil }
|
||||
func Getppid() int { return 2 }
|
||||
func Getpid() int { return 3 }
|
||||
func Gettimeofday(tv *Timeval) error { return ENOSYS }
|
||||
func Getuid() int { return 1 }
|
||||
func Kill(pid int, signum Signal) error { return ENOSYS }
|
||||
func Sendfile(outfd int, infd int, offset *int64, count int) (written int, err error) {
|
||||
return 0, ENOSYS
|
||||
}
|
||||
func StartProcess(argv0 string, argv []string, attr *ProcAttr) (pid int, handle uintptr, err error) {
|
||||
return 0, 0, ENOSYS
|
||||
}
|
||||
func Wait4(pid int, wstatus *WaitStatus, options int, rusage *Rusage) (wpid int, err error) {
|
||||
return 0, ENOSYS
|
||||
}
|
||||
|
||||
type Timeval struct {
|
||||
Sec int64
|
||||
Usec int64
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
package syscall
|
||||
|
||||
// This file defines errno and constants to match the darwin libsystem ABI.
|
||||
// Values have been determined experimentally by compiling some C code on macOS
|
||||
// with Clang and looking at the resulting LLVM IR.
|
||||
|
||||
// This function returns the error location in the darwin ABI.
|
||||
// Discovered by compiling the following code using Clang:
|
||||
//
|
||||
// #include <errno.h>
|
||||
// int getErrno() {
|
||||
// return errno;
|
||||
// }
|
||||
//
|
||||
//go:export __error
|
||||
func libc___error() *int32
|
||||
|
||||
// getErrno returns the current C errno. It may not have been caused by the last
|
||||
// call, so it should only be relied upon when the last call indicates an error
|
||||
// (for example, by returning -1).
|
||||
func getErrno() Errno {
|
||||
errptr := libc___error()
|
||||
return Errno(uintptr(*errptr))
|
||||
}
|
||||
|
||||
const (
|
||||
ENOENT Errno = 2
|
||||
EINTR Errno = 4
|
||||
EMFILE Errno = 24
|
||||
EAGAIN Errno = 35
|
||||
ETIMEDOUT Errno = 60
|
||||
ENOSYS Errno = 78
|
||||
EWOULDBLOCK Errno = EAGAIN
|
||||
)
|
||||
|
||||
type Signal int
|
||||
|
||||
const (
|
||||
SIGCHLD Signal = 20
|
||||
SIGINT Signal = 2
|
||||
SIGKILL Signal = 9
|
||||
SIGTRAP Signal = 5
|
||||
SIGQUIT Signal = 3
|
||||
SIGTERM Signal = 15
|
||||
)
|
||||
|
||||
const (
|
||||
O_RDONLY = 0
|
||||
O_WRONLY = 1
|
||||
O_RDWR = 2
|
||||
)
|
||||
@@ -0,0 +1,57 @@
|
||||
// +build darwin
|
||||
|
||||
package syscall
|
||||
|
||||
import (
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
func Close(fd int) (err error) {
|
||||
return ENOSYS // TODO
|
||||
}
|
||||
|
||||
func Write(fd int, p []byte) (n int, err error) {
|
||||
buf, count := splitSlice(p)
|
||||
n = libc_write(int32(fd), buf, uint(count))
|
||||
if n < 0 {
|
||||
err = getErrno()
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func Read(fd int, p []byte) (n int, err error) {
|
||||
return 0, ENOSYS // TODO
|
||||
}
|
||||
|
||||
func Seek(fd int, offset int64, whence int) (off int64, err error) {
|
||||
return 0, ENOSYS // TODO
|
||||
}
|
||||
|
||||
func Open(path string, mode int, perm uint32) (fd int, err error) {
|
||||
return 0, ENOSYS // TODO
|
||||
}
|
||||
|
||||
func Kill(pid int, sig Signal) (err error) {
|
||||
return ENOSYS // TODO
|
||||
}
|
||||
|
||||
func Getpid() (pid int) {
|
||||
panic("unimplemented: getpid") // TODO
|
||||
}
|
||||
|
||||
func Getenv(key string) (value string, found bool) {
|
||||
return "", false // TODO
|
||||
}
|
||||
|
||||
func splitSlice(p []byte) (buf *byte, len uintptr) {
|
||||
slice := (*struct {
|
||||
buf *byte
|
||||
len uintptr
|
||||
cap uintptr
|
||||
})(unsafe.Pointer(&p))
|
||||
return slice.buf, slice.len
|
||||
}
|
||||
|
||||
// ssize_t write(int fd, const void *buf, size_t count)
|
||||
//go:export write
|
||||
func libc_write(fd int32, buf *byte, count uint) int
|
||||
@@ -0,0 +1,371 @@
|
||||
// Copyright 2013 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// +build avr cortexm
|
||||
|
||||
package syscall
|
||||
|
||||
// TODO: generate with runtime/mknacl.sh, allow override with IRT.
|
||||
const (
|
||||
sys_null = 1
|
||||
sys_nameservice = 2
|
||||
sys_dup = 8
|
||||
sys_dup2 = 9
|
||||
sys_open = 10
|
||||
sys_close = 11
|
||||
sys_read = 12
|
||||
sys_write = 13
|
||||
sys_lseek = 14
|
||||
sys_stat = 16
|
||||
sys_fstat = 17
|
||||
sys_chmod = 18
|
||||
sys_isatty = 19
|
||||
sys_brk = 20
|
||||
sys_mmap = 21
|
||||
sys_munmap = 22
|
||||
sys_getdents = 23
|
||||
sys_mprotect = 24
|
||||
sys_list_mappings = 25
|
||||
sys_exit = 30
|
||||
sys_getpid = 31
|
||||
sys_sched_yield = 32
|
||||
sys_sysconf = 33
|
||||
sys_gettimeofday = 40
|
||||
sys_clock = 41
|
||||
sys_nanosleep = 42
|
||||
sys_clock_getres = 43
|
||||
sys_clock_gettime = 44
|
||||
sys_mkdir = 45
|
||||
sys_rmdir = 46
|
||||
sys_chdir = 47
|
||||
sys_getcwd = 48
|
||||
sys_unlink = 49
|
||||
sys_imc_makeboundsock = 60
|
||||
sys_imc_accept = 61
|
||||
sys_imc_connect = 62
|
||||
sys_imc_sendmsg = 63
|
||||
sys_imc_recvmsg = 64
|
||||
sys_imc_mem_obj_create = 65
|
||||
sys_imc_socketpair = 66
|
||||
sys_mutex_create = 70
|
||||
sys_mutex_lock = 71
|
||||
sys_mutex_trylock = 72
|
||||
sys_mutex_unlock = 73
|
||||
sys_cond_create = 74
|
||||
sys_cond_wait = 75
|
||||
sys_cond_signal = 76
|
||||
sys_cond_broadcast = 77
|
||||
sys_cond_timed_wait_abs = 79
|
||||
sys_thread_create = 80
|
||||
sys_thread_exit = 81
|
||||
sys_tls_init = 82
|
||||
sys_thread_nice = 83
|
||||
sys_tls_get = 84
|
||||
sys_second_tls_set = 85
|
||||
sys_second_tls_get = 86
|
||||
sys_exception_handler = 87
|
||||
sys_exception_stack = 88
|
||||
sys_exception_clear_flag = 89
|
||||
sys_sem_create = 100
|
||||
sys_sem_wait = 101
|
||||
sys_sem_post = 102
|
||||
sys_sem_get_value = 103
|
||||
sys_dyncode_create = 104
|
||||
sys_dyncode_modify = 105
|
||||
sys_dyncode_delete = 106
|
||||
sys_test_infoleak = 109
|
||||
sys_test_crash = 110
|
||||
sys_test_syscall_1 = 111
|
||||
sys_test_syscall_2 = 112
|
||||
sys_futex_wait_abs = 120
|
||||
sys_futex_wake = 121
|
||||
sys_pread = 130
|
||||
sys_pwrite = 131
|
||||
sys_truncate = 140
|
||||
sys_lstat = 141
|
||||
sys_link = 142
|
||||
sys_rename = 143
|
||||
sys_symlink = 144
|
||||
sys_access = 145
|
||||
sys_readlink = 146
|
||||
sys_utimes = 147
|
||||
sys_get_random_bytes = 150
|
||||
)
|
||||
|
||||
// TODO: Auto-generate some day. (Hard-coded in binaries so not likely to change.)
|
||||
const (
|
||||
// native_client/src/trusted/service_runtime/include/sys/errno.h
|
||||
// The errors are mainly copied from Linux.
|
||||
EPERM Errno = 1 /* Operation not permitted */
|
||||
ENOENT Errno = 2 /* No such file or directory */
|
||||
ESRCH Errno = 3 /* No such process */
|
||||
EINTR Errno = 4 /* Interrupted system call */
|
||||
EIO Errno = 5 /* I/O error */
|
||||
ENXIO Errno = 6 /* No such device or address */
|
||||
E2BIG Errno = 7 /* Argument list too long */
|
||||
ENOEXEC Errno = 8 /* Exec format error */
|
||||
EBADF Errno = 9 /* Bad file number */
|
||||
ECHILD Errno = 10 /* No child processes */
|
||||
EAGAIN Errno = 11 /* Try again */
|
||||
ENOMEM Errno = 12 /* Out of memory */
|
||||
EACCES Errno = 13 /* Permission denied */
|
||||
EFAULT Errno = 14 /* Bad address */
|
||||
EBUSY Errno = 16 /* Device or resource busy */
|
||||
EEXIST Errno = 17 /* File exists */
|
||||
EXDEV Errno = 18 /* Cross-device link */
|
||||
ENODEV Errno = 19 /* No such device */
|
||||
ENOTDIR Errno = 20 /* Not a directory */
|
||||
EISDIR Errno = 21 /* Is a directory */
|
||||
EINVAL Errno = 22 /* Invalid argument */
|
||||
ENFILE Errno = 23 /* File table overflow */
|
||||
EMFILE Errno = 24 /* Too many open files */
|
||||
ENOTTY Errno = 25 /* Not a typewriter */
|
||||
EFBIG Errno = 27 /* File too large */
|
||||
ENOSPC Errno = 28 /* No space left on device */
|
||||
ESPIPE Errno = 29 /* Illegal seek */
|
||||
EROFS Errno = 30 /* Read-only file system */
|
||||
EMLINK Errno = 31 /* Too many links */
|
||||
EPIPE Errno = 32 /* Broken pipe */
|
||||
ENAMETOOLONG Errno = 36 /* File name too long */
|
||||
ENOSYS Errno = 38 /* Function not implemented */
|
||||
EDQUOT Errno = 122 /* Quota exceeded */
|
||||
EDOM Errno = 33 /* Math arg out of domain of func */
|
||||
ERANGE Errno = 34 /* Math result not representable */
|
||||
EDEADLK Errno = 35 /* Deadlock condition */
|
||||
ENOLCK Errno = 37 /* No record locks available */
|
||||
ENOTEMPTY Errno = 39 /* Directory not empty */
|
||||
ELOOP Errno = 40 /* Too many symbolic links */
|
||||
ENOMSG Errno = 42 /* No message of desired type */
|
||||
EIDRM Errno = 43 /* Identifier removed */
|
||||
ECHRNG Errno = 44 /* Channel number out of range */
|
||||
EL2NSYNC Errno = 45 /* Level 2 not synchronized */
|
||||
EL3HLT Errno = 46 /* Level 3 halted */
|
||||
EL3RST Errno = 47 /* Level 3 reset */
|
||||
ELNRNG Errno = 48 /* Link number out of range */
|
||||
EUNATCH Errno = 49 /* Protocol driver not attached */
|
||||
ENOCSI Errno = 50 /* No CSI structure available */
|
||||
EL2HLT Errno = 51 /* Level 2 halted */
|
||||
EBADE Errno = 52 /* Invalid exchange */
|
||||
EBADR Errno = 53 /* Invalid request descriptor */
|
||||
EXFULL Errno = 54 /* Exchange full */
|
||||
ENOANO Errno = 55 /* No anode */
|
||||
EBADRQC Errno = 56 /* Invalid request code */
|
||||
EBADSLT Errno = 57 /* Invalid slot */
|
||||
EDEADLOCK Errno = EDEADLK /* File locking deadlock error */
|
||||
EBFONT Errno = 59 /* Bad font file fmt */
|
||||
ENOSTR Errno = 60 /* Device not a stream */
|
||||
ENODATA Errno = 61 /* No data (for no delay io) */
|
||||
ETIME Errno = 62 /* Timer expired */
|
||||
ENOSR Errno = 63 /* Out of streams resources */
|
||||
ENONET Errno = 64 /* Machine is not on the network */
|
||||
ENOPKG Errno = 65 /* Package not installed */
|
||||
EREMOTE Errno = 66 /* The object is remote */
|
||||
ENOLINK Errno = 67 /* The link has been severed */
|
||||
EADV Errno = 68 /* Advertise error */
|
||||
ESRMNT Errno = 69 /* Srmount error */
|
||||
ECOMM Errno = 70 /* Communication error on send */
|
||||
EPROTO Errno = 71 /* Protocol error */
|
||||
EMULTIHOP Errno = 72 /* Multihop attempted */
|
||||
EDOTDOT Errno = 73 /* Cross mount point (not really error) */
|
||||
EBADMSG Errno = 74 /* Trying to read unreadable message */
|
||||
EOVERFLOW Errno = 75 /* Value too large for defined data type */
|
||||
ENOTUNIQ Errno = 76 /* Given log. name not unique */
|
||||
EBADFD Errno = 77 /* f.d. invalid for this operation */
|
||||
EREMCHG Errno = 78 /* Remote address changed */
|
||||
ELIBACC Errno = 79 /* Can't access a needed shared lib */
|
||||
ELIBBAD Errno = 80 /* Accessing a corrupted shared lib */
|
||||
ELIBSCN Errno = 81 /* .lib section in a.out corrupted */
|
||||
ELIBMAX Errno = 82 /* Attempting to link in too many libs */
|
||||
ELIBEXEC Errno = 83 /* Attempting to exec a shared library */
|
||||
EILSEQ Errno = 84
|
||||
EUSERS Errno = 87
|
||||
ENOTSOCK Errno = 88 /* Socket operation on non-socket */
|
||||
EDESTADDRREQ Errno = 89 /* Destination address required */
|
||||
EMSGSIZE Errno = 90 /* Message too long */
|
||||
EPROTOTYPE Errno = 91 /* Protocol wrong type for socket */
|
||||
ENOPROTOOPT Errno = 92 /* Protocol not available */
|
||||
EPROTONOSUPPORT Errno = 93 /* Unknown protocol */
|
||||
ESOCKTNOSUPPORT Errno = 94 /* Socket type not supported */
|
||||
EOPNOTSUPP Errno = 95 /* Operation not supported on transport endpoint */
|
||||
EPFNOSUPPORT Errno = 96 /* Protocol family not supported */
|
||||
EAFNOSUPPORT Errno = 97 /* Address family not supported by protocol family */
|
||||
EADDRINUSE Errno = 98 /* Address already in use */
|
||||
EADDRNOTAVAIL Errno = 99 /* Address not available */
|
||||
ENETDOWN Errno = 100 /* Network interface is not configured */
|
||||
ENETUNREACH Errno = 101 /* Network is unreachable */
|
||||
ENETRESET Errno = 102
|
||||
ECONNABORTED Errno = 103 /* Connection aborted */
|
||||
ECONNRESET Errno = 104 /* Connection reset by peer */
|
||||
ENOBUFS Errno = 105 /* No buffer space available */
|
||||
EISCONN Errno = 106 /* Socket is already connected */
|
||||
ENOTCONN Errno = 107 /* Socket is not connected */
|
||||
ESHUTDOWN Errno = 108 /* Can't send after socket shutdown */
|
||||
ETOOMANYREFS Errno = 109
|
||||
ETIMEDOUT Errno = 110 /* Connection timed out */
|
||||
ECONNREFUSED Errno = 111 /* Connection refused */
|
||||
EHOSTDOWN Errno = 112 /* Host is down */
|
||||
EHOSTUNREACH Errno = 113 /* Host is unreachable */
|
||||
EALREADY Errno = 114 /* Socket already connected */
|
||||
EINPROGRESS Errno = 115 /* Connection already in progress */
|
||||
ESTALE Errno = 116
|
||||
ENOTSUP Errno = EOPNOTSUPP /* Not supported */
|
||||
ENOMEDIUM Errno = 123 /* No medium (in tape drive) */
|
||||
ECANCELED Errno = 125 /* Operation canceled. */
|
||||
ELBIN Errno = 2048 /* Inode is remote (not really error) */
|
||||
EFTYPE Errno = 2049 /* Inappropriate file type or format */
|
||||
ENMFILE Errno = 2050 /* No more files */
|
||||
EPROCLIM Errno = 2051
|
||||
ENOSHARE Errno = 2052 /* No such host or network path */
|
||||
ECASECLASH Errno = 2053 /* Filename exists with different case */
|
||||
EWOULDBLOCK Errno = EAGAIN /* Operation would block */
|
||||
)
|
||||
|
||||
// Do the interface allocations only once for common
|
||||
// Errno values.
|
||||
var (
|
||||
errEAGAIN error = EAGAIN
|
||||
errEINVAL error = EINVAL
|
||||
errENOENT error = ENOENT
|
||||
)
|
||||
|
||||
// errnoErr returns common boxed Errno values, to prevent
|
||||
// allocations at runtime.
|
||||
func errnoErr(e Errno) error {
|
||||
switch e {
|
||||
case 0:
|
||||
return nil
|
||||
case EAGAIN:
|
||||
return errEAGAIN
|
||||
case EINVAL:
|
||||
return errEINVAL
|
||||
case ENOENT:
|
||||
return errENOENT
|
||||
}
|
||||
return e
|
||||
}
|
||||
|
||||
var errnoByCode = map[string]Errno{
|
||||
"EPERM": EPERM,
|
||||
"ENOENT": ENOENT,
|
||||
"ESRCH": ESRCH,
|
||||
"EINTR": EINTR,
|
||||
"EIO": EIO,
|
||||
"ENXIO": ENXIO,
|
||||
"E2BIG": E2BIG,
|
||||
"ENOEXEC": ENOEXEC,
|
||||
"EBADF": EBADF,
|
||||
"ECHILD": ECHILD,
|
||||
"EAGAIN": EAGAIN,
|
||||
"ENOMEM": ENOMEM,
|
||||
"EACCES": EACCES,
|
||||
"EFAULT": EFAULT,
|
||||
"EBUSY": EBUSY,
|
||||
"EEXIST": EEXIST,
|
||||
"EXDEV": EXDEV,
|
||||
"ENODEV": ENODEV,
|
||||
"ENOTDIR": ENOTDIR,
|
||||
"EISDIR": EISDIR,
|
||||
"EINVAL": EINVAL,
|
||||
"ENFILE": ENFILE,
|
||||
"EMFILE": EMFILE,
|
||||
"ENOTTY": ENOTTY,
|
||||
"EFBIG": EFBIG,
|
||||
"ENOSPC": ENOSPC,
|
||||
"ESPIPE": ESPIPE,
|
||||
"EROFS": EROFS,
|
||||
"EMLINK": EMLINK,
|
||||
"EPIPE": EPIPE,
|
||||
"ENAMETOOLONG": ENAMETOOLONG,
|
||||
"ENOSYS": ENOSYS,
|
||||
"EDQUOT": EDQUOT,
|
||||
"EDOM": EDOM,
|
||||
"ERANGE": ERANGE,
|
||||
"EDEADLK": EDEADLK,
|
||||
"ENOLCK": ENOLCK,
|
||||
"ENOTEMPTY": ENOTEMPTY,
|
||||
"ELOOP": ELOOP,
|
||||
"ENOMSG": ENOMSG,
|
||||
"EIDRM": EIDRM,
|
||||
"ECHRNG": ECHRNG,
|
||||
"EL2NSYNC": EL2NSYNC,
|
||||
"EL3HLT": EL3HLT,
|
||||
"EL3RST": EL3RST,
|
||||
"ELNRNG": ELNRNG,
|
||||
"EUNATCH": EUNATCH,
|
||||
"ENOCSI": ENOCSI,
|
||||
"EL2HLT": EL2HLT,
|
||||
"EBADE": EBADE,
|
||||
"EBADR": EBADR,
|
||||
"EXFULL": EXFULL,
|
||||
"ENOANO": ENOANO,
|
||||
"EBADRQC": EBADRQC,
|
||||
"EBADSLT": EBADSLT,
|
||||
"EDEADLOCK": EDEADLOCK,
|
||||
"EBFONT": EBFONT,
|
||||
"ENOSTR": ENOSTR,
|
||||
"ENODATA": ENODATA,
|
||||
"ETIME": ETIME,
|
||||
"ENOSR": ENOSR,
|
||||
"ENONET": ENONET,
|
||||
"ENOPKG": ENOPKG,
|
||||
"EREMOTE": EREMOTE,
|
||||
"ENOLINK": ENOLINK,
|
||||
"EADV": EADV,
|
||||
"ESRMNT": ESRMNT,
|
||||
"ECOMM": ECOMM,
|
||||
"EPROTO": EPROTO,
|
||||
"EMULTIHOP": EMULTIHOP,
|
||||
"EDOTDOT": EDOTDOT,
|
||||
"EBADMSG": EBADMSG,
|
||||
"EOVERFLOW": EOVERFLOW,
|
||||
"ENOTUNIQ": ENOTUNIQ,
|
||||
"EBADFD": EBADFD,
|
||||
"EREMCHG": EREMCHG,
|
||||
"ELIBACC": ELIBACC,
|
||||
"ELIBBAD": ELIBBAD,
|
||||
"ELIBSCN": ELIBSCN,
|
||||
"ELIBMAX": ELIBMAX,
|
||||
"ELIBEXEC": ELIBEXEC,
|
||||
"EILSEQ": EILSEQ,
|
||||
"EUSERS": EUSERS,
|
||||
"ENOTSOCK": ENOTSOCK,
|
||||
"EDESTADDRREQ": EDESTADDRREQ,
|
||||
"EMSGSIZE": EMSGSIZE,
|
||||
"EPROTOTYPE": EPROTOTYPE,
|
||||
"ENOPROTOOPT": ENOPROTOOPT,
|
||||
"EPROTONOSUPPORT": EPROTONOSUPPORT,
|
||||
"ESOCKTNOSUPPORT": ESOCKTNOSUPPORT,
|
||||
"EOPNOTSUPP": EOPNOTSUPP,
|
||||
"EPFNOSUPPORT": EPFNOSUPPORT,
|
||||
"EAFNOSUPPORT": EAFNOSUPPORT,
|
||||
"EADDRINUSE": EADDRINUSE,
|
||||
"EADDRNOTAVAIL": EADDRNOTAVAIL,
|
||||
"ENETDOWN": ENETDOWN,
|
||||
"ENETUNREACH": ENETUNREACH,
|
||||
"ENETRESET": ENETRESET,
|
||||
"ECONNABORTED": ECONNABORTED,
|
||||
"ECONNRESET": ECONNRESET,
|
||||
"ENOBUFS": ENOBUFS,
|
||||
"EISCONN": EISCONN,
|
||||
"ENOTCONN": ENOTCONN,
|
||||
"ESHUTDOWN": ESHUTDOWN,
|
||||
"ETOOMANYREFS": ETOOMANYREFS,
|
||||
"ETIMEDOUT": ETIMEDOUT,
|
||||
"ECONNREFUSED": ECONNREFUSED,
|
||||
"EHOSTDOWN": EHOSTDOWN,
|
||||
"EHOSTUNREACH": EHOSTUNREACH,
|
||||
"EALREADY": EALREADY,
|
||||
"EINPROGRESS": EINPROGRESS,
|
||||
"ESTALE": ESTALE,
|
||||
"ENOTSUP": ENOTSUP,
|
||||
"ENOMEDIUM": ENOMEDIUM,
|
||||
"ECANCELED": ECANCELED,
|
||||
"ELBIN": ELBIN,
|
||||
"EFTYPE": EFTYPE,
|
||||
"ENMFILE": ENMFILE,
|
||||
"EPROCLIM": EPROCLIM,
|
||||
"ENOSHARE": ENOSHARE,
|
||||
"ECASECLASH": ECASECLASH,
|
||||
"EWOULDBLOCK": EWOULDBLOCK,
|
||||
}
|
||||
@@ -1,3 +0,0 @@
|
||||
package unsafe
|
||||
|
||||
// dummy
|
||||
@@ -54,6 +54,7 @@ SECTIONS
|
||||
|
||||
/DISCARD/ :
|
||||
{
|
||||
*(.ARM.exidx) /* causes 'no memory region specified' error in lld */
|
||||
*(.ARM.exidx.*) /* causes spurious 'undefined reference' errors */
|
||||
}
|
||||
}
|
||||
|
||||
+3
-3
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"build-tags": ["avr", "js", "wasm"],
|
||||
"goos": "js",
|
||||
"goarch": "wasm",
|
||||
"build-tags": ["avr", "linux", "arm"],
|
||||
"goos": "linux",
|
||||
"goarch": "arm",
|
||||
"compiler": "avr-gcc",
|
||||
"linker": "avr-gcc",
|
||||
"ldflags": [
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
{
|
||||
"build-tags": ["tinygo.arm", "js", "wasm"],
|
||||
"goos": "js",
|
||||
"goarch": "wasm",
|
||||
"compiler": "clang-7",
|
||||
"build-tags": ["cortexm", "linux", "arm"],
|
||||
"goos": "linux",
|
||||
"goarch": "arm",
|
||||
"compiler": "clang-8",
|
||||
"gc": "marksweep",
|
||||
"linker": "arm-none-eabi-ld",
|
||||
"linker": "ld.lld",
|
||||
"rtlib": "compiler-rt",
|
||||
"cflags": [
|
||||
"-Oz",
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
|
||||
MEMORY
|
||||
{
|
||||
FLASH_TEXT (rw) : ORIGIN = 0x08000000, LENGTH = 1M
|
||||
RAM (xrw) : ORIGIN = 0x20000000, LENGTH = 128K
|
||||
}
|
||||
|
||||
_stack_size = 4K;
|
||||
|
||||
INCLUDE "targets/arm.ld"
|
||||
@@ -0,0 +1,18 @@
|
||||
{
|
||||
"inherits": ["cortex-m"],
|
||||
"llvm-target": "armv7em-none-eabi",
|
||||
"build-tags": ["stm32f4disco", "stm32f407", "stm32"],
|
||||
"cflags": [
|
||||
"--target=armv7em-none-eabi",
|
||||
"-Qunused-arguments"
|
||||
],
|
||||
"ldflags": [
|
||||
"-T", "targets/stm32f407.ld"
|
||||
],
|
||||
"extra-files": [
|
||||
"src/device/stm32/stm32f407.s"
|
||||
],
|
||||
"flash": "openocd -f interface/stlink-v2.cfg -f target/stm32f4x.cfg -c 'program {hex} reset exit'",
|
||||
"ocd-daemon": ["openocd", "-f", "interface/stlink.cfg", "-f", "target/stm32f4x.cfg"],
|
||||
"gdb-initial-cmds": ["target remote :3333", "monitor halt", "load", "monitor reset", "c"]
|
||||
}
|
||||
+4
-3
@@ -3,14 +3,15 @@
|
||||
"build-tags": ["js", "wasm"],
|
||||
"goos": "js",
|
||||
"goarch": "wasm",
|
||||
"compiler": "clang-7",
|
||||
"linker": "wasm-ld-7",
|
||||
"compiler": "clang-8",
|
||||
"linker": "wasm-ld",
|
||||
"cflags": [
|
||||
"--target=wasm32",
|
||||
"-Oz"
|
||||
],
|
||||
"ldflags": [
|
||||
"-allow-undefined"
|
||||
"--allow-undefined",
|
||||
"--export-all"
|
||||
],
|
||||
"emulator": ["node", "targets/wasm_exec.js"]
|
||||
}
|
||||
|
||||
Vendored
+19
@@ -8,6 +8,11 @@ double globalDouble = 3.2;
|
||||
_Complex float globalComplexFloat = 4.1+3.3i;
|
||||
_Complex double globalComplexDouble = 4.2+3.4i;
|
||||
_Complex double globalComplexLongDouble = 4.3+3.5i;
|
||||
collection_t globalStruct = {256, -123456, 3.14, 88};
|
||||
int globalStructSize = sizeof(globalStruct);
|
||||
short globalArray[3] = {5, 6, 7};
|
||||
joined_t globalUnion;
|
||||
int globalUnionSize = sizeof(globalUnion);
|
||||
|
||||
int fortytwo() {
|
||||
return 42;
|
||||
@@ -24,3 +29,17 @@ int doCallback(int a, int b, binop_t callback) {
|
||||
void store(int value, int *ptr) {
|
||||
*ptr = value;
|
||||
}
|
||||
|
||||
void unionSetShort(short s) {
|
||||
globalUnion.s = s;
|
||||
}
|
||||
|
||||
void unionSetFloat(float f) {
|
||||
globalUnion.f = f;
|
||||
}
|
||||
|
||||
void unionSetData(short f0, short f1, short f2) {
|
||||
globalUnion.data[0] = 5;
|
||||
globalUnion.data[1] = 8;
|
||||
globalUnion.data[2] = 1;
|
||||
}
|
||||
|
||||
Vendored
+34
@@ -9,6 +9,10 @@ import "C"
|
||||
|
||||
import "unsafe"
|
||||
|
||||
func (s C.myint) Int() int {
|
||||
return int(s)
|
||||
}
|
||||
|
||||
func main() {
|
||||
println("fortytwo:", C.fortytwo())
|
||||
println("add:", C.add(C.int(3), 5))
|
||||
@@ -36,6 +40,36 @@ func main() {
|
||||
println("complex float:", C.globalComplexFloat)
|
||||
println("complex double:", C.globalComplexDouble)
|
||||
println("complex long double:", C.globalComplexLongDouble)
|
||||
|
||||
// complex types
|
||||
println("struct:", C.int(unsafe.Sizeof(C.globalStruct)) == C.globalStructSize, C.globalStruct.s, C.globalStruct.l, C.globalStruct.f)
|
||||
var _ [3]C.short = C.globalArray
|
||||
println("array:", C.globalArray[0], C.globalArray[1], C.globalArray[2])
|
||||
println("union:", C.int(unsafe.Sizeof(C.globalUnion)) == C.globalUnionSize)
|
||||
C.unionSetShort(22)
|
||||
println("union s:", C.globalUnion.s)
|
||||
C.unionSetFloat(3.14)
|
||||
println("union f:", C.globalUnion.f)
|
||||
C.unionSetData(5, 8, 1)
|
||||
println("union global data:", C.globalUnion.data[0], C.globalUnion.data[1], C.globalUnion.data[2])
|
||||
println("union field:", printUnion(C.globalUnion).f)
|
||||
|
||||
// recursive types, test using a linked list
|
||||
lastElement := &C.list_t{n: 7, next: nil}
|
||||
list := &C.list_t{n: 3, next: &C.struct_list_t{n: 6, next: (*C.struct_list_t)(lastElement)}}
|
||||
for list != nil {
|
||||
println("n in chain:", list.n)
|
||||
list = (*C.list_t)(list.next)
|
||||
}
|
||||
}
|
||||
|
||||
func printUnion(union C.joined_t) C.joined_t {
|
||||
println("union local data: ", union.data[0], union.data[1], union.data[2])
|
||||
union.s = -33
|
||||
println("union s method:", union.s.Int(), union.data[0] == 5)
|
||||
union.f = 6.28
|
||||
println("union f:", union.f)
|
||||
return union
|
||||
}
|
||||
|
||||
//export mul
|
||||
|
||||
Vendored
+27
@@ -5,6 +5,28 @@ int doCallback(int a, int b, binop_t cb);
|
||||
typedef int * intPointer;
|
||||
void store(int value, int *ptr);
|
||||
|
||||
typedef struct collection {
|
||||
short s;
|
||||
long l;
|
||||
float f;
|
||||
unsigned char c;
|
||||
} collection_t;
|
||||
|
||||
// linked list
|
||||
typedef struct list_t {
|
||||
int n;
|
||||
struct list_t *next;
|
||||
} list_t;
|
||||
|
||||
typedef union joined {
|
||||
myint s;
|
||||
float f;
|
||||
short data[3];
|
||||
} joined_t;
|
||||
void unionSetShort(short s);
|
||||
void unionSetFloat(float f);
|
||||
void unionSetData(short f0, short f1, short f2);
|
||||
|
||||
// test globals
|
||||
extern int global;
|
||||
extern _Bool globalBool;
|
||||
@@ -14,6 +36,11 @@ extern double globalDouble;
|
||||
extern _Complex float globalComplexFloat;
|
||||
extern _Complex double globalComplexDouble;
|
||||
extern _Complex double globalComplexLongDouble;
|
||||
extern collection_t globalStruct;
|
||||
extern int globalStructSize;
|
||||
extern short globalArray[3];
|
||||
extern joined_t globalUnion;
|
||||
extern int globalUnionSize;
|
||||
|
||||
// test duplicate definitions
|
||||
int add(int a, int b);
|
||||
|
||||
Vendored
+13
@@ -14,3 +14,16 @@ double: +3.200000e+000
|
||||
complex float: (+4.100000e+000+3.300000e+000i)
|
||||
complex double: (+4.200000e+000+3.400000e+000i)
|
||||
complex long double: (+4.300000e+000+3.500000e+000i)
|
||||
struct: true 256 -123456 +3.140000e+000
|
||||
array: 5 6 7
|
||||
union: true
|
||||
union s: 22
|
||||
union f: +3.140000e+000
|
||||
union global data: 5 8 1
|
||||
union local data: 5 8 1
|
||||
union s method: -33 false
|
||||
union f: +6.280000e+000
|
||||
union field: +6.280000e+000
|
||||
n in chain: 3
|
||||
n in chain: 6
|
||||
n in chain: 7
|
||||
|
||||
Vendored
+18
@@ -43,6 +43,10 @@ func main() {
|
||||
}
|
||||
println("sum(100):", sum)
|
||||
|
||||
// Test select
|
||||
go selectDeadlock()
|
||||
go selectNoOp()
|
||||
|
||||
// Allow goroutines to exit.
|
||||
time.Sleep(time.Microsecond)
|
||||
}
|
||||
@@ -93,3 +97,17 @@ func iterator(ch chan int, top int) {
|
||||
}
|
||||
close(ch)
|
||||
}
|
||||
|
||||
func selectDeadlock() {
|
||||
println("deadlocking")
|
||||
select {}
|
||||
println("unreachable")
|
||||
}
|
||||
|
||||
func selectNoOp() {
|
||||
println("select no-op")
|
||||
select {
|
||||
default:
|
||||
}
|
||||
println("after no-op")
|
||||
}
|
||||
|
||||
Vendored
+3
@@ -19,3 +19,6 @@ sum: 25
|
||||
sum: 29
|
||||
sum: 33
|
||||
sum(100): 4950
|
||||
deadlocking
|
||||
select no-op
|
||||
after no-op
|
||||
|
||||
Vendored
+3
@@ -2,6 +2,7 @@ package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math/rand"
|
||||
"os"
|
||||
)
|
||||
|
||||
@@ -9,4 +10,6 @@ func main() {
|
||||
fmt.Println("stdin: ", os.Stdin.Fd())
|
||||
fmt.Println("stdout:", os.Stdout.Fd())
|
||||
fmt.Println("stderr:", os.Stderr.Fd())
|
||||
|
||||
fmt.Println("pseudorandom number:", rand.Int31())
|
||||
}
|
||||
|
||||
Vendored
+1
@@ -1,3 +1,4 @@
|
||||
stdin: 0
|
||||
stdout: 1
|
||||
stderr: 2
|
||||
pseudorandom number: 1298498081
|
||||
|
||||
+18
@@ -28,6 +28,14 @@ type s4 struct {
|
||||
d byte
|
||||
}
|
||||
|
||||
// same struct, different type
|
||||
type s4b struct {
|
||||
a byte
|
||||
b byte
|
||||
c byte
|
||||
d byte
|
||||
}
|
||||
|
||||
type s5 struct {
|
||||
a struct {
|
||||
aa byte
|
||||
@@ -70,6 +78,16 @@ func test3(s s3) {
|
||||
|
||||
func test4(s s4) {
|
||||
println("test4", s.a, s.b, s.c, s.d)
|
||||
test4b(s4b(s))
|
||||
test4bp((*s4b)(&s))
|
||||
}
|
||||
|
||||
func test4b(s s4b) {
|
||||
println("test4b", s.a, s.b, s.c, s.d)
|
||||
}
|
||||
|
||||
func test4bp(s *s4b) {
|
||||
println("test4bp", s.a, s.b, s.c, s.d)
|
||||
}
|
||||
|
||||
func test5(s s5) {
|
||||
@@ -3,6 +3,8 @@ test1 1
|
||||
test2 1 2
|
||||
test3 1 2 3
|
||||
test4 1 2 3 4
|
||||
test4b 1 2 3 4
|
||||
test4bp 1 2 3 4
|
||||
test5 1 2 3
|
||||
test6 foo 3 5
|
||||
test7 (0:nil) 8
|
||||
@@ -15,7 +15,7 @@ import (
|
||||
// ConvertELFFileToUF2File converts an ELF file to a UF2 file.
|
||||
func ConvertELFFileToUF2File(infile, outfile string) error {
|
||||
// Read the .text segment.
|
||||
_, data, err := ExtractTextSegment(infile)
|
||||
_, data, err := ExtractROM(infile)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
+1
-1
@@ -2,4 +2,4 @@ package main
|
||||
|
||||
// version of this package.
|
||||
// Update this value before release of new version of software.
|
||||
const version = "0.4.0"
|
||||
const version = "0.5.0"
|
||||
|
||||
Reference in New Issue
Block a user