mirror of
https://github.com/tinygo-org/tinygo.git
synced 2026-07-26 14:48:40 +00:00
Compare commits
202 Commits
v0.1
..
gc-precise
| Author | SHA1 | Date | |
|---|---|---|---|
| a1e69bbc13 | |||
| 1b4d71bd3d | |||
| 43b3bb6e83 | |||
| e0cf74e638 | |||
| 6f6afb0515 | |||
| 397b90753c | |||
| 3c2639ad55 | |||
| 371c468e8e | |||
| 0a40219680 | |||
| c981f14e61 | |||
| 763b9d7d10 | |||
| 55fc7b904a | |||
| 17c42810d0 | |||
| 064d001550 | |||
| a4cd3bb77c | |||
| 8d3f19bc84 | |||
| d90f1947d9 | |||
| 11567c62d4 | |||
| 4619207f99 | |||
| 99587fe073 | |||
| eb0ce8a298 | |||
| 4ae4ef5e12 | |||
| d7460b945e | |||
| 638bc17eeb | |||
| 1113f9ec0c | |||
| 019331e8af | |||
| 4c8c048c49 | |||
| 08ee1916f5 | |||
| 141a70f401 | |||
| a79edf416c | |||
| 2511aefac0 | |||
| 4978065c9c | |||
| 78a26fec13 | |||
| 9cad8bd0c8 | |||
| 9a54ee4241 | |||
| 46d5ea8cf6 | |||
| fb952a722a | |||
| 7e46c1766d | |||
| 1f0595438e | |||
| d594342642 | |||
| 99da328453 | |||
| 387e1340bf | |||
| b1ed8a46b7 | |||
| 35af33ead7 | |||
| 4bd1b9e53d | |||
| 80ee343e6d | |||
| 1d59a960bc | |||
| 5ca2e1322c | |||
| 5b0b35f9e4 | |||
| 9a3d0683b3 | |||
| d155e31b64 | |||
| 45cacda7b3 | |||
| c25fe609a9 | |||
| 6d23809218 | |||
| 024eceb476 | |||
| 0fd90c49cc | |||
| d1efffe96b | |||
| 8e7ea92d44 | |||
| 2f2d62cc0c | |||
| d396abb690 | |||
| b815d3f760 | |||
| 9c46ac4eed | |||
| b2e96fc35a | |||
| 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 | |||
| b1744db2c8 | |||
| bd6a7b69ce | |||
| 051ad07755 | |||
| 09e85b7859 | |||
| 622d0ebde6 | |||
| b7cdf8cd0c | |||
| cfc1a66e8d | |||
| 4ad9bd8643 | |||
| 2a1dd98661 | |||
| 2c03192691 | |||
| 9d6df2b4c7 | |||
| 5939729c45 | |||
| c7b91da8c4 | |||
| c7fdb6741f | |||
| b837c94366 | |||
| 26e7e93478 | |||
| 8e99c3313b | |||
| 28987ae061 | |||
| b594f212fb | |||
| 41e093d7bb | |||
| 665c3bdaa6 | |||
| ea3d232c84 | |||
| 4f932b6e66 | |||
| 3538ba943c | |||
| 543696eafc | |||
| 6e5ae83302 | |||
| 9b4071237f | |||
| 1c68da89af | |||
| 4424fe087d | |||
| 34939ab422 | |||
| c56b2a45fa | |||
| b1c70d85f7 | |||
| 714d98354c | |||
| 6e8df2fc40 | |||
| 902f40867f | |||
| 5438f16fcb | |||
| 7f027ddd33 | |||
| acaf096586 | |||
| 942d4903ce | |||
| 0b212cf2f6 | |||
| 2d5bc836f5 | |||
| 856e5fa179 | |||
| 07733ca056 | |||
| 92d9b780b5 | |||
| da345e8723 | |||
| fab38a0749 | |||
| 0a3dbbd1cb | |||
| 4c29f0fdb6 | |||
| fbc2099ee3 | |||
| 95d895646a | |||
| 35fb594f8f | |||
| 01f6aff422 | |||
| 7dd5839f47 | |||
| 403fee7e06 | |||
| 7657238c24 | |||
| 3cba36f2ba | |||
| 93d5269fef | |||
| 4b477fad55 | |||
| 6360e318a7 | |||
| 0757eb5919 | |||
| 013a71aa3d | |||
| f0091b31b5 | |||
| 709a296150 | |||
| f7b2a2c977 | |||
| 6ae4b43eb2 | |||
| bece6b9648 | |||
| 553f00bdb8 | |||
| a789108926 | |||
| d63ce0646c | |||
| 003211b4ff | |||
| e6720d7ddc | |||
| dfef168139 | |||
| 101f2e519b | |||
| eb34afde4b | |||
| 63f2a3dfe9 | |||
| fb23e9c212 | |||
| 222c4c75b1 | |||
| 5a509f5bfe | |||
| 2e46275c45 | |||
| 1d34f868da | |||
| f0904779a5 | |||
| 70f1064f36 |
@@ -0,0 +1,290 @@
|
||||
version: 2.1
|
||||
|
||||
commands:
|
||||
submodules:
|
||||
steps:
|
||||
- run:
|
||||
name: "Pull submodules"
|
||||
command: git submodule update --init
|
||||
apt-dependencies:
|
||||
parameters:
|
||||
llvm:
|
||||
type: string
|
||||
steps:
|
||||
- run:
|
||||
name: "Install apt dependencies"
|
||||
command: |
|
||||
echo 'deb http://apt.llvm.org/stretch/ llvm-toolchain-stretch<<parameters.llvm>> main' | sudo tee /etc/apt/sources.list.d/llvm.list
|
||||
wget -O - https://apt.llvm.org/llvm-snapshot.gpg.key|sudo apt-key add -
|
||||
sudo apt-get update
|
||||
sudo apt-get install \
|
||||
python3 \
|
||||
llvm<<parameters.llvm>>-dev \
|
||||
clang<<parameters.llvm>> \
|
||||
libclang<<parameters.llvm>>-dev \
|
||||
lld<<parameters.llvm>> \
|
||||
gcc-arm-linux-gnueabihf \
|
||||
libc6-dev-armel-cross \
|
||||
gcc-aarch64-linux-gnu \
|
||||
libc6-dev-arm64-cross \
|
||||
qemu-system-arm \
|
||||
qemu-user \
|
||||
gcc-avr \
|
||||
avr-libc
|
||||
install-node:
|
||||
steps:
|
||||
- run:
|
||||
name: "Install node.js"
|
||||
command: |
|
||||
wget https://nodejs.org/dist/v10.15.1/node-v10.15.1-linux-x64.tar.xz
|
||||
sudo tar -C /usr/local -xf node-v10.15.1-linux-x64.tar.xz
|
||||
sudo ln -s /usr/local/node-v10.15.1-linux-x64/bin/node /usr/bin/node
|
||||
rm node-v10.15.1-linux-x64.tar.xz
|
||||
dep:
|
||||
steps:
|
||||
- run:
|
||||
name: "Install Go dependencies"
|
||||
command: |
|
||||
curl https://raw.githubusercontent.com/golang/dep/master/install.sh | sh
|
||||
dep ensure --vendor-only
|
||||
llvm-source-linux:
|
||||
steps:
|
||||
- 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
|
||||
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 -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=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
|
||||
- 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 -size short -o test.elf -target=circuitplay-express examples/i2s
|
||||
- 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: <<parameters.llvm>>
|
||||
- install-node
|
||||
- restore_cache:
|
||||
keys:
|
||||
- go-cache-{{ checksum "Gopkg.lock" }}-{{ .Environment.CIRCLE_PREVIOUS_BUILD_NUM }}
|
||||
- go-cache-{{ checksum "Gopkg.lock" }}
|
||||
- llvm-source-linux
|
||||
- dep
|
||||
- run: go install .
|
||||
- run: go test -v
|
||||
- run: make gen-device -j4
|
||||
- smoketest
|
||||
- save_cache:
|
||||
key: go-cache-{{ checksum "Gopkg.lock" }}-{{ .Environment.CIRCLE_BUILD_NUM }}
|
||||
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 \
|
||||
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" }}
|
||||
- llvm-source-linux
|
||||
- restore_cache:
|
||||
keys:
|
||||
- llvm-build-8-linux-v4
|
||||
- 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-linux-v4
|
||||
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-v3
|
||||
- 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-v3
|
||||
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-llvm8-go111
|
||||
- test-llvm8-go112
|
||||
- build-linux
|
||||
- build-macos
|
||||
@@ -10,3 +10,5 @@ src/device/stm32/*.s
|
||||
src/device/sam/*.go
|
||||
src/device/sam/*.s
|
||||
vendor
|
||||
llvm
|
||||
llvm-build
|
||||
|
||||
+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
|
||||
|
||||
-34
@@ -1,34 +0,0 @@
|
||||
language: go
|
||||
|
||||
go:
|
||||
- "1.11"
|
||||
|
||||
before_install:
|
||||
- echo "deb http://apt.llvm.org/trusty/ llvm-toolchain-trusty-7 main" | sudo tee -a /etc/apt/sources.list
|
||||
- echo "deb http://ppa.launchpad.net/ubuntu-toolchain-r/test/ubuntu trusty main" | sudo tee -a /etc/apt/sources.list
|
||||
- sudo apt-get update -qq
|
||||
- sudo apt-get install llvm-7-dev clang-7 libclang-7-dev binutils-arm-none-eabi qemu-system-arm --allow-unauthenticated -y
|
||||
- sudo ln -s /usr/bin/clang-7 /usr/local/bin/cc # work around missing -no-pie in old GCC version
|
||||
|
||||
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 -o blinky1.nrf.elf -target=pca10040 examples/blinky1
|
||||
- tinygo build -o blinky2.nrf.elf -target=pca10040 examples/blinky2
|
||||
- tinygo build -o blinky2 examples/blinky2
|
||||
- tinygo build -o test.nrf.elf -target=pca10040 examples/test
|
||||
- tinygo build -o blinky1.nrf51.elf -target=microbit examples/echo
|
||||
- tinygo build -o test.nrf.elf -target=nrf52840-mdk examples/blinky1
|
||||
- tinygo build -o blinky1.nrf51d.elf -target=pca10031 examples/blinky1
|
||||
- tinygo build -o blinky1.stm32.elf -target=bluepill examples/blinky1
|
||||
- tinygo build -o blinky1.avr.o -target=arduino examples/blinky1 # TODO: avr-as/avr-gcc doesn't work
|
||||
- tinygo build -o blinky1.reel.elf -target=reelboard examples/blinky1
|
||||
- tinygo build -o blinky2.reel.elf -target=reelboard examples/blinky2
|
||||
- tinygo build -o blinky1.pca10056.elf -target=pca10056 examples/blinky1
|
||||
- tinygo build -o blinky2.pca10056.elf -target=pca10056 examples/blinky2
|
||||
- tinygo build -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
|
||||
|
||||
|
||||
@@ -0,0 +1,98 @@
|
||||
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**
|
||||
- switch to the hardfloat ABI on ARM, which is more widely used
|
||||
- avoid a dependency on `objcopy` (`arm-none-eabi-objcopy` etc.)
|
||||
- fix a bug in `make([]T, n)` where `n` is 64-bits on a 32-bit platform
|
||||
- adapt to a change in the AVR backend in LLVM 8
|
||||
- directly support the .uf2 firmware format as used on Adafruit boards
|
||||
- fix a bug when calling `panic()` at init time outside of the main package
|
||||
- implement nil checks, which results in a ~5% increase in code size
|
||||
- inline slice bounds checking, which results in a ~1% decrease in code size
|
||||
- **targets**
|
||||
- `samd21`: fix a bug in port B pins
|
||||
- `samd21`: implement SPI peripheral
|
||||
- `samd21`: implement ADC peripheral
|
||||
- `stm32`: fix a bug in timekeeping
|
||||
- `wasm`: fix a bug in `wasm_exec.js` that caused corruption in linear memory
|
||||
when running on Node.js.
|
||||
|
||||
0.3.0
|
||||
---
|
||||
- **compiler**
|
||||
- remove old `-initinterp` flag
|
||||
- add support for macOS
|
||||
- **cgo**
|
||||
- add support for bool/float/complex types
|
||||
- **standard library**
|
||||
- `device/arm`: add support to disable/enable hardware interrupts
|
||||
- `machine`: add CPU frequency for nrf-based boards
|
||||
- `syscall`: add support for darwin/amd64
|
||||
- **targets**
|
||||
- `circuitplay_express`: add support for this board
|
||||
- `microbit`: add regular pin constants
|
||||
- `samd21`: fix time function for goroutine support
|
||||
- `samd21`: add support for USB-CDC (serial over USB)
|
||||
- `samd21`: add support for pins in port B
|
||||
- `samd21`: add support for pullup and pulldown pins
|
||||
- `wasm`: add support for Safari in example
|
||||
|
||||
|
||||
0.2.0
|
||||
---
|
||||
- **command line**
|
||||
- add version subcommand
|
||||
- **compiler**
|
||||
- fix a bug in floating point comparisons with NaN values
|
||||
- fix a bug when calling `panic` in package initialization code
|
||||
- add support for comparing `complex64` and `complex128`
|
||||
- **cgo**
|
||||
- add support for external globals
|
||||
- add support for pointers and function pointers
|
||||
- **standard library**
|
||||
- `fmt`: initial support, `fmt.Println` works
|
||||
- `math`: support for most/all functions
|
||||
- `os`: initial support (only stdin/stdout/stderr)
|
||||
- `reflect`: initial support
|
||||
- `syscall`: add support for amd64, arm, and arm64
|
||||
@@ -0,0 +1,48 @@
|
||||
# How to contribute
|
||||
|
||||
Thank you for your interest in improving TinyGo.
|
||||
|
||||
We would like your help to make this project better, so we appreciate any contributions. See if one of the following descriptions matches your situation:
|
||||
|
||||
### New to TinyGo
|
||||
|
||||
We'd love to get your feedback on getting started with TinyGo. Run into any difficulty, confusion, or anything else? You are not alone. We want to know about your experience, so we can help the next people. Please open a Github issue with your questions, or you can also get in touch directly with us on our Slack channel at [https://gophers.slack.com/messages/CDJD3SUP6](https://gophers.slack.com/messages/CDJD3SUP6).
|
||||
|
||||
### Something in TinyGo is not working as you expect
|
||||
|
||||
Please open a Github issue with your problem, and we will be happy to assist.
|
||||
|
||||
### Something in Go that you want/need does not appear to be in TinyGo
|
||||
|
||||
We probably have not implemented it yet. Please take a look at our [Roadmap](https://github.com/tinygo-org/tinygo/wiki/Roadmap). Your pull request adding the functionality to TinyGo would be greatly appreciated.
|
||||
|
||||
A long tail of small (and large) language features haven't been implemented yet. In almost all cases, the compiler will show a `todo:` error from `compiler/compiler.go` when you try to use it. You can try implementing it, or open a bug report with a small code sample that fails to compile.
|
||||
|
||||
### Some specific hardware you want to use does not appear to be in TinyGo
|
||||
|
||||
As above, we probably have not implemented it yet. Your contribution adding the hardware support to TinyGo would be greatly appreciated.
|
||||
|
||||
Lots of targets/boards are still unsupported. Adding an architecture often requires a few compiler changes, but if the architecture is supported you can try implementing support for a new chip or board in `src/runtime`. For details, see [this wiki entry on adding archs/chips/boards](https://github.com/tinygo-org/tinygo/wiki/Adding-a-new-board).
|
||||
|
||||
Microcontrollers have lots of peripherals (I2C, SPI, ADC, etc.) and many don't have an implementation yet in the `machine` package. Adding support for new peripherals is very useful.
|
||||
|
||||
## How to use our Github repository
|
||||
|
||||
The `master` branch of this repo will always have the latest released version of TinyGo. All of the active development work for the next release will take place in the `dev` branch. TinyGo will use semantic versioning and will create a tag/release for each release.
|
||||
|
||||
Here is how to contribute back some code or documentation:
|
||||
|
||||
- Fork repo
|
||||
- Create a feature branch off of the `dev` branch
|
||||
- Make some useful change
|
||||
- Make sure the tests still pass
|
||||
- Submit a pull request against the `dev` branch.
|
||||
- Be kind
|
||||
|
||||
## How to run tests
|
||||
|
||||
To run the tests:
|
||||
|
||||
```
|
||||
make test
|
||||
```
|
||||
+13
-7
@@ -1,15 +1,21 @@
|
||||
# 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
|
||||
apt-get install -y llvm-8-dev libclang-8-dev git
|
||||
|
||||
RUN wget -O- https://raw.githubusercontent.com/golang/dep/master/install.sh | sh
|
||||
|
||||
COPY . /go/src/github.com/tinygo-org/tinygo
|
||||
|
||||
# remove submodules directories and re-init them to fix any hard-coded paths
|
||||
# after copying the tinygo directory in the previous step.
|
||||
RUN cd /go/src/github.com/tinygo-org/tinygo/ && \
|
||||
rm -rf ./lib/* && \
|
||||
git submodule update --init --recursive --force
|
||||
|
||||
RUN cd /go/src/github.com/tinygo-org/tinygo/ && \
|
||||
dep ensure --vendor-only && \
|
||||
go install /go/src/github.com/tinygo-org/tinygo/
|
||||
@@ -22,9 +28,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
|
||||
@@ -56,7 +62,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 && \
|
||||
@@ -71,7 +77,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
+24
-5
@@ -3,7 +3,23 @@
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
digest = "1:84316faef4ea12d34dde3b3e6dab682715a23b1c2bb8ab82cec9ab619766e214"
|
||||
digest = "1:06519a2ec1d59040eaccec40206f9d0b59dc662db2a032f974d6d6b9a2bcb839"
|
||||
name = "github.com/blakesmith/ar"
|
||||
packages = ["."]
|
||||
pruneopts = "UT"
|
||||
revision = "8bd4349a67f2533b078dbc524689d15dba0f4659"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
digest = "1:00b45e06c7843541372fc17d982242bd6adfc2fc382b6f2e9ef9ce53d87a50b9"
|
||||
name = "github.com/marcinbor85/gohex"
|
||||
packages = ["."]
|
||||
pruneopts = "UT"
|
||||
revision = "7a43cd876e46e0f6ddc553f10f91731a78e6e949"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
digest = "1:ba70784a3deee74c0ca3c87bcac3c2f93d3b2d27d8f237b768c358b45ba47da8"
|
||||
name = "golang.org/x/tools"
|
||||
packages = [
|
||||
"go/ast/astutil",
|
||||
@@ -11,20 +27,23 @@
|
||||
"go/types/typeutil",
|
||||
]
|
||||
pruneopts = "UT"
|
||||
revision = "3e7aa9e59977626dc60433e9aeadf1bb63d28295"
|
||||
revision = "8dcc6e70cdefe9a82236b6e195e4f4e2108fcb9f"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
digest = "1:3611159788efdd4e0cfae18b6ebcccbad25a2815968b0e4323b42647d201031a"
|
||||
branch = "llvm8"
|
||||
digest = "1:bf5539bdf6b3cc3ec1e45926db05d81180da11ce722fa1edcce3f0b4e1967da5"
|
||||
name = "tinygo.org/x/go-llvm"
|
||||
packages = ["."]
|
||||
pruneopts = "UT"
|
||||
revision = "f420620d1a0f54417a5712260153fe861780d030"
|
||||
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",
|
||||
"tinygo.org/x/go-llvm",
|
||||
]
|
||||
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
[[constraint]]
|
||||
branch = "master"
|
||||
branch = "llvm8"
|
||||
name = "tinygo.org/x/go-llvm"
|
||||
|
||||
[[constraint]]
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
Copyright (c) 2018 Ayke van Laethem. All rights reserved.
|
||||
Copyright (c) 2018-2019 TinyGo Authors. All rights reserved.
|
||||
|
||||
TinyGo includes portions of the Go standard library.
|
||||
Copyright (c) 2009-2019 The Go Authors. All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are
|
||||
|
||||
@@ -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 cgo 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/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,23 +57,45 @@ 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
|
||||
|
||||
# Build the Go compiler.
|
||||
tinygo:
|
||||
@mkdir -p build
|
||||
go build -o build/tinygo .
|
||||
|
||||
static:
|
||||
# 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 -DLLVM_ENABLE_TERMINFO=OFF -DLLVM_ENABLE_ZLIB=OFF
|
||||
|
||||
# Build LLVM.
|
||||
llvm-build: llvm-build/build.ninja
|
||||
cd llvm-build; ninja
|
||||
|
||||
|
||||
# Build the Go compiler.
|
||||
build/tinygo:
|
||||
@if [ ! -f llvm-build/bin/llvm-config ]; then echo "Fetch and build LLVM first by running:\n make llvm-source\n make llvm-build"; exit 1; fi
|
||||
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/clang/include
|
||||
@mkdir -p build/release/tinygo/lib/CMSIS/CMSIS
|
||||
@mkdir -p build/release/tinygo/lib/compiler-rt/lib
|
||||
@mkdir -p build/release/tinygo/lib/nrfx
|
||||
@mkdir -p build/release/tinygo/pkg/armv6m-none-eabi
|
||||
@mkdir -p build/release/tinygo/pkg/armv7m-none-eabi
|
||||
@mkdir -p build/release/tinygo/pkg/armv7em-none-eabi
|
||||
@echo copying source files
|
||||
@cp -p build/tinygo build/release/tinygo/bin
|
||||
@cp -p $(abspath $(CLANG_SRC))/lib/Headers/*.h build/release/tinygo/lib/clang/include
|
||||
@cp -rp lib/CMSIS/CMSIS/Include build/release/tinygo/lib/CMSIS/CMSIS
|
||||
@cp -rp lib/CMSIS/README.md build/release/tinygo/lib/CMSIS
|
||||
@cp -rp lib/compiler-rt/lib/builtins build/release/tinygo/lib/compiler-rt/lib
|
||||
@@ -136,15 +108,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,145 +1,71 @@
|
||||
# TinyGo - Go compiler for microcontrollers
|
||||
# TinyGo - Go compiler for small places
|
||||
|
||||
[](https://travis-ci.com/tinygo-org/tinygo)
|
||||
[](https://circleci.com/gh/tinygo-org/tinygo/tree/dev)
|
||||
|
||||
> We never expected Go to be an embedded language and so it's got serious
|
||||
> problems [...].
|
||||
TinyGo is a Go compiler intended for use in small places such as microcontrollers, WebAssembly (WASM), and command-line tools.
|
||||
|
||||
-- Rob Pike, [GopherCon 2014 Opening Keynote](https://www.youtube.com/watch?v=VoS7DsT1rdM&feature=youtu.be&t=2799)
|
||||
It reuses libraries used by the [Go language tools](https://golang.org/pkg/go/) alongside [LLVM](http://llvm.org) to provide an alternative way to compile programs written in the Go programming language.
|
||||
|
||||
TinyGo is a project to bring Go to microcontrollers and small systems with a
|
||||
single processor core. It is similar to [emgo](https://github.com/ziutek/emgo)
|
||||
but a major difference is that I want to keep the Go memory model (which implies
|
||||
garbage collection of some sort). Another difference is that TinyGo uses LLVM
|
||||
internally instead of emitting C, which hopefully leads to smaller and more
|
||||
efficient code and certainly leads to more flexibility.
|
||||
|
||||
My original reasoning was: if [Python](https://micropython.org/) can run on
|
||||
microcontrollers, then certainly [Go](https://golang.org/) should be able to and
|
||||
run on even lower level micros.
|
||||
|
||||
Example program (blinky):
|
||||
Here is an example program that blinks the built-in LED when run directly on any supported board with onboard LED:
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"machine"
|
||||
"time"
|
||||
"machine"
|
||||
"time"
|
||||
)
|
||||
|
||||
func main() {
|
||||
led := machine.GPIO{machine.LED}
|
||||
led.Configure(machine.GPIOConfig{Mode: machine.GPIO_OUTPUT})
|
||||
for {
|
||||
led.Low()
|
||||
time.Sleep(time.Millisecond * 1000)
|
||||
led := machine.GPIO{machine.LED}
|
||||
led.Configure(machine.GPIOConfig{Mode: machine.GPIO_OUTPUT})
|
||||
for {
|
||||
led.Low()
|
||||
time.Sleep(time.Millisecond * 1000)
|
||||
|
||||
led.High()
|
||||
time.Sleep(time.Millisecond * 1000)
|
||||
}
|
||||
led.High()
|
||||
time.Sleep(time.Millisecond * 1000)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Currently supported features:
|
||||
The above program can be compiled and run without modification on an Arduino Uno, an Adafruit ItsyBitsy M0, or any of the supported boards that have a built-in LED, just by setting the correct TinyGo compiler target. For example, this compiles and flashes an Arduino Uno:
|
||||
|
||||
* control flow
|
||||
* many (but not all) basic types: most ints, floats, strings, structs
|
||||
* function calling
|
||||
* interfaces for basic types (with type switches and asserts)
|
||||
* goroutines (very initial support)
|
||||
* function pointers (non-blocking)
|
||||
* interface methods
|
||||
* standard library (but most packages won't work due to missing language
|
||||
features)
|
||||
* slices (partially)
|
||||
* maps (very rough, unfinished)
|
||||
* defer
|
||||
* closures
|
||||
* bound methods
|
||||
* complex numbers (except for arithmetic)
|
||||
* channels (with some limitations)
|
||||
|
||||
Not yet supported:
|
||||
|
||||
* select
|
||||
* complex arithmetic
|
||||
* garbage collection
|
||||
* recover
|
||||
* introspection (if it ever gets implemented)
|
||||
* ...
|
||||
```shell
|
||||
tinygo flash -target arduino examples/blinky1
|
||||
```
|
||||
|
||||
## Installation
|
||||
|
||||
See the [getting started instructions](https://tinygo.org/getting-started/).
|
||||
See the [getting started instructions](https://tinygo.org/getting-started/) for information on how to install TinyGo, as well as how to run the TinyGo compiler using our Docker container.
|
||||
|
||||
### Running with Docker
|
||||
## Supported boards/targets
|
||||
|
||||
A docker container exists for easy access to the `tinygo` CLI:
|
||||
You can compile TinyGo programs for microcontrollers, WebAssembly and Linux.
|
||||
|
||||
```sh
|
||||
$ docker run --rm -v $(pwd):/src tinygo/tinygo tinygo build -o /src/wasm.wasm -target wasm examples/wasm
|
||||
```
|
||||
The following microcontroller boards are currently supported:
|
||||
|
||||
Note that you cannot run `tinygo flash` from inside the docker container,
|
||||
so it is less useful for microcontroller development.
|
||||
* [Adafruit Circuit Playground Express](https://www.adafruit.com/product/3333)
|
||||
* [Adafruit ItsyBitsy M0](https://www.adafruit.com/product/3727)
|
||||
* [Arduino Uno](https://store.arduino.cc/arduino-uno-rev3)
|
||||
* [BBC:Microbit](https://microbit.org/)
|
||||
* [ST Micro STM32F103XX "Bluepill"](http://wiki.stm32duino.com/index.php?title=Blue_Pill)
|
||||
* [Digispark](http://digistump.com/products/1)
|
||||
* [Nordic Semiconductor PCA10031](https://www.nordicsemi.com/eng/Products/nRF51-Dongle)
|
||||
* [Nordic Semiconductor PCA10040](https://www.nordicsemi.com/eng/Products/Bluetooth-low-energy/nRF52-DK)
|
||||
* [Nordic Semiconductor PCA10056](https://www.nordicsemi.com/Software-and-Tools/Development-Kits/nRF52840-DK)
|
||||
* [Makerdiary nRF52840-MDK](https://wiki.makerdiary.com/nrf52840-mdk/)
|
||||
* [Phytec reel board](https://www.phytec.eu/product-eu/internet-of-things/reelboard/)
|
||||
|
||||
## Supported targets
|
||||
For more information, see [this list of boards](https://tinygo.org/microcontrollers/). Pull requests for additional support are welcome!
|
||||
|
||||
The following architectures/systems are currently supported:
|
||||
## Currently supported features:
|
||||
|
||||
* ARM (Cortex-M)
|
||||
* AVR (Arduino Uno)
|
||||
* Linux
|
||||
* WebAssembly
|
||||
|
||||
For more information, see [this list of targets and
|
||||
boards](https://tinygo.org/targets/). Pull requests for
|
||||
broader support are welcome!
|
||||
|
||||
## Analysis and optimizations
|
||||
|
||||
The goal is to reduce code size (and increase performance) by performing all
|
||||
kinds of whole-program analysis passes. The official Go compiler doesn't do a
|
||||
whole lot of analysis (except for escape analysis) because it needs to be fast,
|
||||
but embedded programs are necessarily smaller so it becomes practical. And I
|
||||
think especially program size can be reduced by a large margin when actually
|
||||
trying to optimize for it.
|
||||
|
||||
Implemented compiler passes:
|
||||
|
||||
* Analyse which functions are blocking. Blocking functions are functions that
|
||||
call sleep, chan send, etc. Its parents are also blocking.
|
||||
* Analyse whether the scheduler is needed. It is only needed when there are
|
||||
`go` statements for blocking functions.
|
||||
* Analyse whether a given type switch or type assert is possible with
|
||||
[type-based alias analysis](https://en.wikipedia.org/wiki/Alias_analysis#Type-based_alias_analysis).
|
||||
I would like to use flow-based alias analysis in the future, if feasible.
|
||||
* Do basic dead code elimination of functions. This pass makes later passes
|
||||
better and probably improves compile time as well.
|
||||
|
||||
## Scope
|
||||
|
||||
Goals:
|
||||
|
||||
* Have very small binary sizes. Don't pay for what you don't use.
|
||||
* Support for most common microcontroller boards.
|
||||
* Be usable on the web using WebAssembly.
|
||||
* Good CGo support, with no more overhead than a regular function call.
|
||||
* Support most standard library packages and compile most Go code without
|
||||
modification.
|
||||
|
||||
Non-goals:
|
||||
|
||||
* Using more than one core.
|
||||
* Be efficient while using zillions of goroutines. However, good goroutine
|
||||
support is certainly a goal.
|
||||
* Be as fast as `gc`. However, LLVM will probably be better at optimizing
|
||||
certain things so TinyGo might actually turn out to be faster for number
|
||||
crunching.
|
||||
* Be able to compile every Go program out there.
|
||||
For a description of currently supported Go language features, please see [https://tinygo.org/lang-support/](https://tinygo.org/lang-support/).
|
||||
|
||||
## Documentation
|
||||
|
||||
Documentation is currently maintained on a dedicated web site located at [https://tinygo.org/](https://tinygo.org/).
|
||||
Documentation is located on our web site at [https://tinygo.org/](https://tinygo.org/).
|
||||
|
||||
You can find the web site code at [https://github.com/tinygo-org/tinygo-site](https://github.com/tinygo-org/tinygo-site).
|
||||
|
||||
@@ -154,26 +80,37 @@ should arrive fairly quickly (under 1 min): https://invite.slack.golangbridge.or
|
||||
|
||||
## Contributing
|
||||
|
||||
Patches are welcome!
|
||||
Your contributions are welcome!
|
||||
|
||||
If you want to contribute, here are some suggestions:
|
||||
Please take a look at our [CONTRIBUTING.md](./CONTRIBUTING.md) document for details.
|
||||
|
||||
* A long tail of small (and large) language features haven't been implemented
|
||||
yet. In almost all cases, the compiler will show a `todo:` error from
|
||||
`compiler/compiler.go` when you try to use it. You can try implementing it,
|
||||
or open a bug report with a small code sample that fails to compile.
|
||||
* Lots of targets/boards are still unsupported. Adding an architecture often
|
||||
requires a few compiler changes, but if the architecture is supported you
|
||||
can try implementing support for a new chip or board in `src/runtime`. For
|
||||
details, see [this wiki entry on adding
|
||||
archs/chips/boards](https://github.com/tinygo-org/tinygo/wiki/Adding-a-new-board).
|
||||
* Microcontrollers have lots of peripherals and many don't have an
|
||||
implementation yet in the `machine` package. Adding support for new
|
||||
peripherals is very useful.
|
||||
* Just raising bugs for things you'd like to see implemented is also a form of
|
||||
contributing! It helps prioritization.
|
||||
## Project Scope
|
||||
|
||||
Goals:
|
||||
|
||||
* Have very small binary sizes. Don't pay for what you don't use.
|
||||
* Support for most common microcontroller boards.
|
||||
* Be usable on the web using WebAssembly.
|
||||
* Good CGo support, with no more overhead than a regular function call.
|
||||
* Support most standard library packages and compile most Go code without modification.
|
||||
|
||||
Non-goals:
|
||||
|
||||
* Using more than one core.
|
||||
* Be efficient while using zillions of goroutines. However, good goroutine support is certainly a goal.
|
||||
* Be as fast as `gc`. However, LLVM will probably be better at optimizing certain things so TinyGo might actually turn out to be faster for number crunching.
|
||||
* Be able to compile every Go program out there.
|
||||
|
||||
## Why this project exists
|
||||
|
||||
> We never expected Go to be an embedded language and so its got serious problems...
|
||||
|
||||
-- Rob Pike, [GopherCon 2014 Opening Keynote](https://www.youtube.com/watch?v=VoS7DsT1rdM&feature=youtu.be&t=2799)
|
||||
|
||||
TinyGo is a project to bring Go to microcontrollers and small systems with a single processor core. It is similar to [emgo](https://github.com/ziutek/emgo) but a major difference is that we want to keep the Go memory model (which implies garbage collection of some sort). Another difference is that TinyGo uses LLVM internally instead of emitting C, which hopefully leads to smaller and more efficient code and certainly leads to more flexibility.
|
||||
|
||||
The original reasoning was: if [Python](https://micropython.org/) can run on microcontrollers, then certainly [Go](https://golang.org/) should be able to run on even lower level micros.
|
||||
|
||||
## License
|
||||
|
||||
This project is licensed under the BSD 3-clause license, just like the
|
||||
[Go project](https://golang.org/LICENSE) itself.
|
||||
This project is licensed under the BSD 3-clause license, just like the [Go project](https://golang.org/LICENSE) itself.
|
||||
|
||||
+5
-3
@@ -9,9 +9,11 @@ import (
|
||||
|
||||
// Get the cache directory, usually ~/.cache/tinygo
|
||||
func cacheDir() string {
|
||||
home := getHomeDir()
|
||||
dir := filepath.Join(home, ".cache", "tinygo")
|
||||
return dir
|
||||
dir, err := os.UserCacheDir()
|
||||
if err != nil {
|
||||
panic("could not find cache dir: " + err.Error())
|
||||
}
|
||||
return filepath.Join(dir, "tinygo")
|
||||
}
|
||||
|
||||
// Return the newest timestamp of all the file paths passed in. Used to check
|
||||
|
||||
+46
-14
@@ -1,11 +1,15 @@
|
||||
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.
|
||||
@@ -187,13 +191,13 @@ func loadBuiltins(target string) (path string, err error) {
|
||||
srcs[i] = filepath.Join(builtinsDir, name)
|
||||
}
|
||||
|
||||
if path, err := cacheLoad(outfile, commands["clang"], srcs); path != "" || err != nil {
|
||||
if path, err := cacheLoad(outfile, commands["clang"][0], srcs); path != "" || err != nil {
|
||||
return path, err
|
||||
}
|
||||
|
||||
var cachepath string
|
||||
err = compileBuiltins(target, func(path string) error {
|
||||
path, err := cacheStore(path, outfile, commands["clang"], srcs)
|
||||
path, err := cacheStore(path, outfile, commands["clang"][0], srcs)
|
||||
cachepath = path
|
||||
return err
|
||||
})
|
||||
@@ -235,28 +239,56 @@ func compileBuiltins(target string, callback func(path string) error) error {
|
||||
// Note: -fdebug-prefix-map is necessary to make the output archive
|
||||
// reproducible. Otherwise the temporary directory is stored in the
|
||||
// archive itself, which varies each run.
|
||||
cmd := exec.Command(commands["clang"], "-c", "-Oz", "-g", "-Werror", "-Wall", "-std=c11", "-fshort-enums", "-nostdlibinc", "-ffunction-sections", "-fdata-sections", "--target="+target, "-fdebug-prefix-map="+dir+"="+remapDir, "-o", objpath, srcpath)
|
||||
cmd.Stdout = os.Stdout
|
||||
cmd.Stderr = os.Stderr
|
||||
cmd.Dir = dir
|
||||
err = cmd.Run()
|
||||
err := execCommand(commands["clang"], "-c", "-Oz", "-g", "-Werror", "-Wall", "-std=c11", "-fshort-enums", "-nostdlibinc", "-ffunction-sections", "-fdata-sections", "--target="+target, "-fdebug-prefix-map="+dir+"="+remapDir, "-o", objpath, srcpath)
|
||||
if err != nil {
|
||||
return &commandError{"failed to build", srcpath, err}
|
||||
}
|
||||
}
|
||||
|
||||
// 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,
|
||||
// because after it returns the temporary directory will be removed.
|
||||
arfile.Close()
|
||||
return callback(arpath)
|
||||
}
|
||||
|
||||
+636
@@ -0,0 +1,636 @@
|
||||
// Package cgo implements CGo by modifying a loaded AST. It does this by parsing
|
||||
// the `import "C"` statements found in the source code with libclang and
|
||||
// generating stub function and global declarations.
|
||||
//
|
||||
// There are a few advantages to modifying the AST directly instead of doing CGo
|
||||
// as a preprocessing step, with the main advantage being that debug information
|
||||
// is kept intact as much as possible.
|
||||
package cgo
|
||||
|
||||
// This file extracts the `import "C"` statement from the source and modifies
|
||||
// the AST for CGo. It does not use libclang directly: see libclang.go for the C
|
||||
// source file parsing.
|
||||
|
||||
import (
|
||||
"go/ast"
|
||||
"go/token"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"golang.org/x/tools/go/ast/astutil"
|
||||
)
|
||||
|
||||
// cgoPackage holds all CGo-related information of a package.
|
||||
type cgoPackage struct {
|
||||
generated *ast.File
|
||||
generatedPos token.Pos
|
||||
errors []error
|
||||
dir string
|
||||
fset *token.FileSet
|
||||
tokenFiles map[string]*token.File
|
||||
missingSymbols map[string]struct{}
|
||||
constants map[string]constantInfo
|
||||
functions map[string]*functionInfo
|
||||
globals map[string]globalInfo
|
||||
typedefs map[string]*typedefInfo
|
||||
elaboratedTypes map[string]*elaboratedTypeInfo
|
||||
}
|
||||
|
||||
// constantInfo stores some information about a CGo constant found by libclang
|
||||
// and declared in the Go AST.
|
||||
type constantInfo struct {
|
||||
expr *ast.BasicLit
|
||||
pos token.Pos
|
||||
}
|
||||
|
||||
// functionInfo stores some information about a CGo function found by libclang
|
||||
// and declared in the AST.
|
||||
type functionInfo struct {
|
||||
args []paramInfo
|
||||
results *ast.FieldList
|
||||
pos token.Pos
|
||||
}
|
||||
|
||||
// paramInfo is a parameter of a CGo function (see functionInfo).
|
||||
type paramInfo struct {
|
||||
name string
|
||||
typeExpr ast.Expr
|
||||
}
|
||||
|
||||
// typedefInfo contains information about a single typedef in C.
|
||||
type typedefInfo struct {
|
||||
typeExpr ast.Expr
|
||||
pos token.Pos
|
||||
}
|
||||
|
||||
// elaboratedTypeInfo contains some information about an elaborated type
|
||||
// (struct, union) found in the C AST.
|
||||
type elaboratedTypeInfo struct {
|
||||
typeExpr ast.Expr
|
||||
pos token.Pos
|
||||
}
|
||||
|
||||
// globalInfo contains information about a declared global variable in C.
|
||||
type globalInfo struct {
|
||||
typeExpr ast.Expr
|
||||
pos token.Pos
|
||||
}
|
||||
|
||||
// cgoAliases list type aliases between Go and C, for types that are equivalent
|
||||
// in both languages. See addTypeAliases.
|
||||
var cgoAliases = map[string]string{
|
||||
"C.int8_t": "int8",
|
||||
"C.int16_t": "int16",
|
||||
"C.int32_t": "int32",
|
||||
"C.int64_t": "int64",
|
||||
"C.uint8_t": "uint8",
|
||||
"C.uint16_t": "uint16",
|
||||
"C.uint32_t": "uint32",
|
||||
"C.uint64_t": "uint64",
|
||||
"C.uintptr_t": "uintptr",
|
||||
}
|
||||
|
||||
// builtinAliases are handled specially because they only exist on the Go side
|
||||
// of CGo, not on the CGo side (they're prefixed with "_Cgo_" there).
|
||||
var builtinAliases = map[string]struct{}{
|
||||
"char": struct{}{},
|
||||
"schar": struct{}{},
|
||||
"uchar": struct{}{},
|
||||
"short": struct{}{},
|
||||
"ushort": struct{}{},
|
||||
"int": struct{}{},
|
||||
"uint": struct{}{},
|
||||
"long": struct{}{},
|
||||
"ulong": struct{}{},
|
||||
"longlong": struct{}{},
|
||||
"ulonglong": struct{}{},
|
||||
}
|
||||
|
||||
// cgoTypes lists some C types with ambiguous sizes that must be retrieved
|
||||
// somehow from C. This is done by adding some typedefs to get the size of each
|
||||
// type.
|
||||
const cgoTypes = `
|
||||
typedef char _Cgo_char;
|
||||
typedef signed char _Cgo_schar;
|
||||
typedef unsigned char _Cgo_uchar;
|
||||
typedef short _Cgo_short;
|
||||
typedef unsigned short _Cgo_ushort;
|
||||
typedef int _Cgo_int;
|
||||
typedef unsigned int _Cgo_uint;
|
||||
typedef long _Cgo_long;
|
||||
typedef unsigned long _Cgo_ulong;
|
||||
typedef long long _Cgo_longlong;
|
||||
typedef unsigned long long _Cgo_ulonglong;
|
||||
`
|
||||
|
||||
// Process extracts `import "C"` statements from the AST, parses the comment
|
||||
// with libclang, and modifies the AST to use this information. It returns a
|
||||
// newly created *ast.File that should be added to the list of to-be-parsed
|
||||
// files. If there is one or more error, it returns these in the []error slice
|
||||
// but still modifies the AST.
|
||||
func Process(files []*ast.File, dir string, fset *token.FileSet, cflags []string) (*ast.File, []error) {
|
||||
p := &cgoPackage{
|
||||
dir: dir,
|
||||
fset: fset,
|
||||
tokenFiles: map[string]*token.File{},
|
||||
missingSymbols: map[string]struct{}{},
|
||||
constants: map[string]constantInfo{},
|
||||
functions: map[string]*functionInfo{},
|
||||
globals: map[string]globalInfo{},
|
||||
typedefs: map[string]*typedefInfo{},
|
||||
elaboratedTypes: map[string]*elaboratedTypeInfo{},
|
||||
}
|
||||
|
||||
// Add a new location for the following file.
|
||||
generatedTokenPos := p.fset.AddFile(dir+"/!cgo.go", -1, 0)
|
||||
generatedTokenPos.SetLines([]int{0})
|
||||
p.generatedPos = generatedTokenPos.Pos(0)
|
||||
|
||||
// Construct a new in-memory AST for CGo declarations of this package.
|
||||
unsafeImport := &ast.ImportSpec{
|
||||
Path: &ast.BasicLit{
|
||||
ValuePos: p.generatedPos,
|
||||
Kind: token.STRING,
|
||||
Value: "\"unsafe\"",
|
||||
},
|
||||
EndPos: p.generatedPos,
|
||||
}
|
||||
p.generated = &ast.File{
|
||||
Package: p.generatedPos,
|
||||
Name: &ast.Ident{
|
||||
NamePos: p.generatedPos,
|
||||
Name: files[0].Name.Name,
|
||||
},
|
||||
Decls: []ast.Decl{
|
||||
&ast.GenDecl{
|
||||
TokPos: p.generatedPos,
|
||||
Tok: token.IMPORT,
|
||||
Specs: []ast.Spec{
|
||||
unsafeImport,
|
||||
},
|
||||
},
|
||||
},
|
||||
Imports: []*ast.ImportSpec{unsafeImport},
|
||||
}
|
||||
|
||||
// Find all C.* symbols.
|
||||
for _, f := range files {
|
||||
astutil.Apply(f, p.findMissingCGoNames, nil)
|
||||
}
|
||||
for name := range builtinAliases {
|
||||
p.missingSymbols["_Cgo_"+name] = struct{}{}
|
||||
}
|
||||
|
||||
// Find `import "C"` statements in the file.
|
||||
for _, f := range files {
|
||||
for i := 0; i < len(f.Decls); i++ {
|
||||
decl := f.Decls[i]
|
||||
genDecl, ok := decl.(*ast.GenDecl)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
if len(genDecl.Specs) != 1 {
|
||||
continue
|
||||
}
|
||||
spec, ok := genDecl.Specs[0].(*ast.ImportSpec)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
path, err := strconv.Unquote(spec.Path.Value)
|
||||
if err != nil {
|
||||
panic("could not parse import path: " + err.Error())
|
||||
}
|
||||
if path != "C" {
|
||||
continue
|
||||
}
|
||||
cgoComment := genDecl.Doc.Text()
|
||||
|
||||
pos := genDecl.Pos()
|
||||
if genDecl.Doc != nil {
|
||||
pos = genDecl.Doc.Pos()
|
||||
}
|
||||
position := fset.PositionFor(pos, true)
|
||||
p.parseFragment(cgoComment+cgoTypes, cflags, position.Filename, position.Line)
|
||||
|
||||
// Remove this import declaration.
|
||||
f.Decls = append(f.Decls[:i], f.Decls[i+1:]...)
|
||||
i--
|
||||
}
|
||||
|
||||
// Print the AST, for debugging.
|
||||
//ast.Print(fset, f)
|
||||
}
|
||||
|
||||
// Declare functions found by libclang.
|
||||
p.addFuncDecls()
|
||||
|
||||
// Declare stub function pointer values found by libclang.
|
||||
p.addFuncPtrDecls()
|
||||
|
||||
// Declare globals found by libclang.
|
||||
p.addConstDecls()
|
||||
|
||||
// Declare globals found by libclang.
|
||||
p.addVarDecls()
|
||||
|
||||
// Forward C types to Go types (like C.uint32_t -> uint32).
|
||||
p.addTypeAliases()
|
||||
|
||||
// Add type declarations for C types, declared using typedef in C.
|
||||
p.addTypedefs()
|
||||
|
||||
// Add elaborated types for C structs and unions.
|
||||
p.addElaboratedTypes()
|
||||
|
||||
// Patch the AST to use the declared types and functions.
|
||||
for _, f := range files {
|
||||
astutil.Apply(f, p.walker, nil)
|
||||
}
|
||||
|
||||
// Print the newly generated in-memory AST, for debugging.
|
||||
//ast.Print(fset, p.generated)
|
||||
|
||||
return p.generated, p.errors
|
||||
}
|
||||
|
||||
// addFuncDecls adds the C function declarations found by libclang in the
|
||||
// comment above the `import "C"` statement.
|
||||
func (p *cgoPackage) addFuncDecls() {
|
||||
names := make([]string, 0, len(p.functions))
|
||||
for name := range p.functions {
|
||||
names = append(names, name)
|
||||
}
|
||||
sort.Strings(names)
|
||||
for _, name := range names {
|
||||
fn := p.functions[name]
|
||||
obj := &ast.Object{
|
||||
Kind: ast.Fun,
|
||||
Name: "C." + name,
|
||||
}
|
||||
args := make([]*ast.Field, len(fn.args))
|
||||
decl := &ast.FuncDecl{
|
||||
Name: &ast.Ident{
|
||||
NamePos: fn.pos,
|
||||
Name: "C." + name,
|
||||
Obj: obj,
|
||||
},
|
||||
Type: &ast.FuncType{
|
||||
Func: fn.pos,
|
||||
Params: &ast.FieldList{
|
||||
Opening: fn.pos,
|
||||
List: args,
|
||||
Closing: fn.pos,
|
||||
},
|
||||
Results: fn.results,
|
||||
},
|
||||
}
|
||||
obj.Decl = decl
|
||||
for i, arg := range fn.args {
|
||||
args[i] = &ast.Field{
|
||||
Names: []*ast.Ident{
|
||||
&ast.Ident{
|
||||
NamePos: fn.pos,
|
||||
Name: arg.name,
|
||||
Obj: &ast.Object{
|
||||
Kind: ast.Var,
|
||||
Name: arg.name,
|
||||
Decl: decl,
|
||||
},
|
||||
},
|
||||
},
|
||||
Type: arg.typeExpr,
|
||||
}
|
||||
}
|
||||
p.generated.Decls = append(p.generated.Decls, decl)
|
||||
}
|
||||
}
|
||||
|
||||
// addFuncPtrDecls creates stub declarations of function pointer values. These
|
||||
// values will later be replaced with the real values in the compiler.
|
||||
// It adds code like the following to the AST:
|
||||
//
|
||||
// var (
|
||||
// C.add unsafe.Pointer
|
||||
// C.mul unsafe.Pointer
|
||||
// // ...
|
||||
// )
|
||||
func (p *cgoPackage) addFuncPtrDecls() {
|
||||
if len(p.functions) == 0 {
|
||||
return
|
||||
}
|
||||
gen := &ast.GenDecl{
|
||||
TokPos: token.NoPos,
|
||||
Tok: token.VAR,
|
||||
Lparen: token.NoPos,
|
||||
Rparen: token.NoPos,
|
||||
}
|
||||
names := make([]string, 0, len(p.functions))
|
||||
for name := range p.functions {
|
||||
names = append(names, name)
|
||||
}
|
||||
sort.Strings(names)
|
||||
for _, name := range names {
|
||||
fn := p.functions[name]
|
||||
obj := &ast.Object{
|
||||
Kind: ast.Typ,
|
||||
Name: "C." + name + "$funcaddr",
|
||||
}
|
||||
valueSpec := &ast.ValueSpec{
|
||||
Names: []*ast.Ident{&ast.Ident{
|
||||
NamePos: fn.pos,
|
||||
Name: "C." + name + "$funcaddr",
|
||||
Obj: obj,
|
||||
}},
|
||||
Type: &ast.SelectorExpr{
|
||||
X: &ast.Ident{
|
||||
NamePos: fn.pos,
|
||||
Name: "unsafe",
|
||||
},
|
||||
Sel: &ast.Ident{
|
||||
NamePos: fn.pos,
|
||||
Name: "Pointer",
|
||||
},
|
||||
},
|
||||
}
|
||||
obj.Decl = valueSpec
|
||||
gen.Specs = append(gen.Specs, valueSpec)
|
||||
}
|
||||
p.generated.Decls = append(p.generated.Decls, gen)
|
||||
}
|
||||
|
||||
// addConstDecls declares external C constants in the Go source.
|
||||
// It adds code like the following to the AST:
|
||||
//
|
||||
// const (
|
||||
// C.CONST_INT = 5
|
||||
// C.CONST_FLOAT = 5.8
|
||||
// // ...
|
||||
// )
|
||||
func (p *cgoPackage) addConstDecls() {
|
||||
if len(p.constants) == 0 {
|
||||
return
|
||||
}
|
||||
gen := &ast.GenDecl{
|
||||
TokPos: token.NoPos,
|
||||
Tok: token.CONST,
|
||||
Lparen: token.NoPos,
|
||||
Rparen: token.NoPos,
|
||||
}
|
||||
names := make([]string, 0, len(p.constants))
|
||||
for name := range p.constants {
|
||||
names = append(names, name)
|
||||
}
|
||||
sort.Strings(names)
|
||||
for _, name := range names {
|
||||
constVal := p.constants[name]
|
||||
obj := &ast.Object{
|
||||
Kind: ast.Con,
|
||||
Name: "C." + name,
|
||||
}
|
||||
valueSpec := &ast.ValueSpec{
|
||||
Names: []*ast.Ident{&ast.Ident{
|
||||
NamePos: constVal.pos,
|
||||
Name: "C." + name,
|
||||
Obj: obj,
|
||||
}},
|
||||
Values: []ast.Expr{constVal.expr},
|
||||
}
|
||||
obj.Decl = valueSpec
|
||||
gen.Specs = append(gen.Specs, valueSpec)
|
||||
}
|
||||
p.generated.Decls = append(p.generated.Decls, gen)
|
||||
}
|
||||
|
||||
// addVarDecls declares external C globals in the Go source.
|
||||
// It adds code like the following to the AST:
|
||||
//
|
||||
// var (
|
||||
// C.globalInt int
|
||||
// C.globalBool bool
|
||||
// // ...
|
||||
// )
|
||||
func (p *cgoPackage) addVarDecls() {
|
||||
if len(p.globals) == 0 {
|
||||
return
|
||||
}
|
||||
gen := &ast.GenDecl{
|
||||
TokPos: token.NoPos,
|
||||
Tok: token.VAR,
|
||||
Lparen: token.NoPos,
|
||||
Rparen: token.NoPos,
|
||||
}
|
||||
names := make([]string, 0, len(p.globals))
|
||||
for name := range p.globals {
|
||||
names = append(names, name)
|
||||
}
|
||||
sort.Strings(names)
|
||||
for _, name := range names {
|
||||
global := p.globals[name]
|
||||
obj := &ast.Object{
|
||||
Kind: ast.Var,
|
||||
Name: "C." + name,
|
||||
}
|
||||
valueSpec := &ast.ValueSpec{
|
||||
Names: []*ast.Ident{&ast.Ident{
|
||||
NamePos: global.pos,
|
||||
Name: "C." + name,
|
||||
Obj: obj,
|
||||
}},
|
||||
Type: global.typeExpr,
|
||||
}
|
||||
obj.Decl = valueSpec
|
||||
gen.Specs = append(gen.Specs, valueSpec)
|
||||
}
|
||||
p.generated.Decls = append(p.generated.Decls, gen)
|
||||
}
|
||||
|
||||
// addTypeAliases aliases some built-in Go types with their equivalent C types.
|
||||
// It adds code like the following to the AST:
|
||||
//
|
||||
// type (
|
||||
// C.int8_t = int8
|
||||
// C.int16_t = int16
|
||||
// // ...
|
||||
// )
|
||||
func (p *cgoPackage) addTypeAliases() {
|
||||
aliasKeys := make([]string, 0, len(cgoAliases))
|
||||
for key := range cgoAliases {
|
||||
aliasKeys = append(aliasKeys, key)
|
||||
}
|
||||
sort.Strings(aliasKeys)
|
||||
gen := &ast.GenDecl{
|
||||
TokPos: token.NoPos,
|
||||
Tok: token.TYPE,
|
||||
Lparen: token.NoPos,
|
||||
Rparen: token.NoPos,
|
||||
}
|
||||
for _, typeName := range aliasKeys {
|
||||
goTypeName := cgoAliases[typeName]
|
||||
obj := &ast.Object{
|
||||
Kind: ast.Typ,
|
||||
Name: typeName,
|
||||
}
|
||||
typeSpec := &ast.TypeSpec{
|
||||
Name: &ast.Ident{
|
||||
NamePos: token.NoPos,
|
||||
Name: typeName,
|
||||
Obj: obj,
|
||||
},
|
||||
Assign: p.generatedPos,
|
||||
Type: &ast.Ident{
|
||||
NamePos: token.NoPos,
|
||||
Name: goTypeName,
|
||||
},
|
||||
}
|
||||
obj.Decl = typeSpec
|
||||
gen.Specs = append(gen.Specs, typeSpec)
|
||||
}
|
||||
p.generated.Decls = append(p.generated.Decls, gen)
|
||||
}
|
||||
|
||||
func (p *cgoPackage) addTypedefs() {
|
||||
if len(p.typedefs) == 0 {
|
||||
return
|
||||
}
|
||||
gen := &ast.GenDecl{
|
||||
TokPos: token.NoPos,
|
||||
Tok: token.TYPE,
|
||||
}
|
||||
names := make([]string, 0, len(p.typedefs))
|
||||
for name := range p.typedefs {
|
||||
names = append(names, name)
|
||||
}
|
||||
sort.Strings(names)
|
||||
for _, name := range names {
|
||||
typedef := p.typedefs[name]
|
||||
typeName := "C." + name
|
||||
isAlias := true
|
||||
if strings.HasPrefix(name, "_Cgo_") {
|
||||
typeName = "C." + name[len("_Cgo_"):]
|
||||
isAlias = false // C.short etc. should not be aliased to the equivalent Go type (not portable)
|
||||
}
|
||||
if _, ok := cgoAliases[typeName]; ok {
|
||||
// This is a type that also exists in Go (defined in stdint.h).
|
||||
continue
|
||||
}
|
||||
obj := &ast.Object{
|
||||
Kind: ast.Typ,
|
||||
Name: typeName,
|
||||
}
|
||||
typeSpec := &ast.TypeSpec{
|
||||
Name: &ast.Ident{
|
||||
NamePos: typedef.pos,
|
||||
Name: typeName,
|
||||
Obj: obj,
|
||||
},
|
||||
Type: typedef.typeExpr,
|
||||
}
|
||||
if isAlias {
|
||||
typeSpec.Assign = typedef.pos
|
||||
}
|
||||
obj.Decl = typeSpec
|
||||
gen.Specs = append(gen.Specs, typeSpec)
|
||||
}
|
||||
p.generated.Decls = append(p.generated.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 (p *cgoPackage) addElaboratedTypes() {
|
||||
if len(p.elaboratedTypes) == 0 {
|
||||
return
|
||||
}
|
||||
gen := &ast.GenDecl{
|
||||
TokPos: token.NoPos,
|
||||
Tok: token.TYPE,
|
||||
}
|
||||
names := make([]string, 0, len(p.elaboratedTypes))
|
||||
for name := range p.elaboratedTypes {
|
||||
names = append(names, name)
|
||||
}
|
||||
sort.Strings(names)
|
||||
for _, name := range names {
|
||||
typ := p.elaboratedTypes[name]
|
||||
typeName := "C." + name
|
||||
obj := &ast.Object{
|
||||
Kind: ast.Typ,
|
||||
Name: typeName,
|
||||
}
|
||||
typeSpec := &ast.TypeSpec{
|
||||
Name: &ast.Ident{
|
||||
NamePos: typ.pos,
|
||||
Name: typeName,
|
||||
Obj: obj,
|
||||
},
|
||||
Type: typ.typeExpr,
|
||||
}
|
||||
obj.Decl = typeSpec
|
||||
gen.Specs = append(gen.Specs, typeSpec)
|
||||
}
|
||||
p.generated.Decls = append(p.generated.Decls, gen)
|
||||
}
|
||||
|
||||
// findMissingCGoNames traverses the AST and finds all C.something names. Only
|
||||
// these symbols are extracted from the parsed C AST and converted to the Go
|
||||
// equivalent.
|
||||
func (p *cgoPackage) findMissingCGoNames(cursor *astutil.Cursor) bool {
|
||||
switch node := cursor.Node().(type) {
|
||||
case *ast.SelectorExpr:
|
||||
x, ok := node.X.(*ast.Ident)
|
||||
if !ok {
|
||||
return true
|
||||
}
|
||||
if x.Name == "C" {
|
||||
name := node.Sel.Name
|
||||
if _, ok := builtinAliases[name]; ok {
|
||||
name = "_Cgo_" + name
|
||||
}
|
||||
p.missingSymbols[name] = struct{}{}
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// walker replaces all "C".<something> expressions to literal "C.<something>"
|
||||
// expressions. Such expressions are impossible to write in Go (a dot cannot be
|
||||
// used in the middle of a name) so in practice all C identifiers live in a
|
||||
// separate namespace (no _Cgo_ hacks like in gc).
|
||||
func (p *cgoPackage) walker(cursor *astutil.Cursor) bool {
|
||||
switch node := cursor.Node().(type) {
|
||||
case *ast.CallExpr:
|
||||
fun, ok := node.Fun.(*ast.SelectorExpr)
|
||||
if !ok {
|
||||
return true
|
||||
}
|
||||
x, ok := fun.X.(*ast.Ident)
|
||||
if !ok {
|
||||
return true
|
||||
}
|
||||
if _, ok := p.functions[fun.Sel.Name]; ok && x.Name == "C" {
|
||||
node.Fun = &ast.Ident{
|
||||
NamePos: x.NamePos,
|
||||
Name: "C." + fun.Sel.Name,
|
||||
}
|
||||
}
|
||||
case *ast.SelectorExpr:
|
||||
x, ok := node.X.(*ast.Ident)
|
||||
if !ok {
|
||||
return true
|
||||
}
|
||||
if x.Name == "C" {
|
||||
name := "C." + node.Sel.Name
|
||||
if _, ok := p.functions[node.Sel.Name]; ok {
|
||||
name += "$funcaddr"
|
||||
}
|
||||
cursor.Replace(&ast.Ident{
|
||||
NamePos: x.NamePos,
|
||||
Name: name,
|
||||
})
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
+624
@@ -0,0 +1,624 @@
|
||||
package cgo
|
||||
|
||||
// This file parses a fragment of C with libclang and stores the result for AST
|
||||
// modification. It does not touch the AST itself.
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"go/ast"
|
||||
"go/scanner"
|
||||
"go/token"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
/*
|
||||
#include <clang-c/Index.h> // if this fails, install libclang-8-dev
|
||||
#include <stdlib.h>
|
||||
#include <stdint.h>
|
||||
|
||||
// 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);
|
||||
CXSourceLocation tinygo_clang_getCursorLocation(GoCXCursor c);
|
||||
CXSourceRange tinygo_clang_getCursorExtent(GoCXCursor c);
|
||||
CXTranslationUnit tinygo_clang_Cursor_getTranslationUnit(GoCXCursor c);
|
||||
|
||||
int tinygo_clang_globals_visitor(GoCXCursor c, GoCXCursor parent, CXClientData client_data);
|
||||
int tinygo_clang_struct_visitor(GoCXCursor c, GoCXCursor parent, CXClientData client_data);
|
||||
*/
|
||||
import "C"
|
||||
|
||||
// storedRefs stores references to types, used for clang_visitChildren.
|
||||
var storedRefs refMap
|
||||
|
||||
var diagnosticSeverity = [...]string{
|
||||
C.CXDiagnostic_Ignored: "ignored",
|
||||
C.CXDiagnostic_Note: "note",
|
||||
C.CXDiagnostic_Warning: "warning",
|
||||
C.CXDiagnostic_Error: "error",
|
||||
C.CXDiagnostic_Fatal: "fatal",
|
||||
}
|
||||
|
||||
func (p *cgoPackage) parseFragment(fragment string, cflags []string, posFilename string, posLine int) {
|
||||
index := C.clang_createIndex(0, 0)
|
||||
defer C.clang_disposeIndex(index)
|
||||
|
||||
// pretend to be a .c file
|
||||
filenameC := C.CString(posFilename + "!cgo.c")
|
||||
defer C.free(unsafe.Pointer(filenameC))
|
||||
|
||||
// fix up error locations
|
||||
fragment = fmt.Sprintf("# %d %#v\n", posLine+1, posFilename) + fragment
|
||||
|
||||
fragmentC := C.CString(fragment)
|
||||
defer C.free(unsafe.Pointer(fragmentC))
|
||||
|
||||
unsavedFile := C.struct_CXUnsavedFile{
|
||||
Filename: filenameC,
|
||||
Length: C.ulong(len(fragment)),
|
||||
Contents: fragmentC,
|
||||
}
|
||||
|
||||
// convert Go slice of strings to C array of strings.
|
||||
cmdargsC := C.malloc(C.size_t(len(cflags)) * C.size_t(unsafe.Sizeof(uintptr(0))))
|
||||
defer C.free(cmdargsC)
|
||||
cmdargs := (*[1 << 16]*C.char)(cmdargsC)
|
||||
for i, cflag := range cflags {
|
||||
s := C.CString(cflag)
|
||||
cmdargs[i] = s
|
||||
defer C.free(unsafe.Pointer(s))
|
||||
}
|
||||
|
||||
var unit C.CXTranslationUnit
|
||||
errCode := C.clang_parseTranslationUnit2(
|
||||
index,
|
||||
filenameC,
|
||||
(**C.char)(cmdargsC), C.int(len(cflags)), // command line args
|
||||
&unsavedFile, 1, // unsaved files
|
||||
C.CXTranslationUnit_DetailedPreprocessingRecord,
|
||||
&unit)
|
||||
if errCode != 0 {
|
||||
panic("loader: failed to parse source with libclang")
|
||||
}
|
||||
defer C.clang_disposeTranslationUnit(unit)
|
||||
|
||||
if numDiagnostics := int(C.clang_getNumDiagnostics(unit)); numDiagnostics != 0 {
|
||||
addDiagnostic := func(diagnostic C.CXDiagnostic) {
|
||||
spelling := getString(C.clang_getDiagnosticSpelling(diagnostic))
|
||||
severity := diagnosticSeverity[C.clang_getDiagnosticSeverity(diagnostic)]
|
||||
location := C.clang_getDiagnosticLocation(diagnostic)
|
||||
var libclangFilename C.CXString
|
||||
var line C.unsigned
|
||||
var column C.unsigned
|
||||
C.clang_getPresumedLocation(location, &libclangFilename, &line, &column)
|
||||
filename := getString(libclangFilename)
|
||||
if filepath.IsAbs(filename) {
|
||||
// Relative paths for readability, like other Go parser errors.
|
||||
relpath, err := filepath.Rel(p.dir, filename)
|
||||
if err == nil {
|
||||
filename = relpath
|
||||
}
|
||||
}
|
||||
p.errors = append(p.errors, &scanner.Error{
|
||||
Pos: token.Position{
|
||||
Filename: filename,
|
||||
Offset: 0, // not provided by clang_getPresumedLocation
|
||||
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
|
||||
}
|
||||
|
||||
ref := storedRefs.Put(p)
|
||||
defer storedRefs.Remove(ref)
|
||||
cursor := C.tinygo_clang_getTranslationUnitCursor(unit)
|
||||
C.tinygo_clang_visitChildren(cursor, C.CXCursorVisitor(C.tinygo_clang_globals_visitor), C.CXClientData(ref))
|
||||
}
|
||||
|
||||
//export tinygo_clang_globals_visitor
|
||||
func tinygo_clang_globals_visitor(c, parent C.GoCXCursor, client_data C.CXClientData) C.int {
|
||||
p := storedRefs.Get(unsafe.Pointer(client_data)).(*cgoPackage)
|
||||
kind := C.tinygo_clang_getCursorKind(c)
|
||||
pos := p.getCursorPosition(c)
|
||||
switch kind {
|
||||
case C.CXCursor_FunctionDecl:
|
||||
name := getString(C.tinygo_clang_getCursorSpelling(c))
|
||||
if _, required := p.missingSymbols[name]; !required {
|
||||
return C.CXChildVisit_Continue
|
||||
}
|
||||
cursorType := C.tinygo_clang_getCursorType(c)
|
||||
if C.clang_isFunctionTypeVariadic(cursorType) != 0 {
|
||||
return C.CXChildVisit_Continue // not supported
|
||||
}
|
||||
numArgs := int(C.tinygo_clang_Cursor_getNumArguments(c))
|
||||
fn := &functionInfo{
|
||||
pos: pos,
|
||||
}
|
||||
p.functions[name] = fn
|
||||
for i := 0; i < numArgs; i++ {
|
||||
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)
|
||||
}
|
||||
fn.args = append(fn.args, paramInfo{
|
||||
name: argName,
|
||||
typeExpr: p.makeASTType(argType, pos),
|
||||
})
|
||||
}
|
||||
resultType := C.tinygo_clang_getCursorResultType(c)
|
||||
if resultType.kind != C.CXType_Void {
|
||||
fn.results = &ast.FieldList{
|
||||
List: []*ast.Field{
|
||||
&ast.Field{
|
||||
Type: p.makeASTType(resultType, pos),
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
case C.CXCursor_StructDecl:
|
||||
typ := C.tinygo_clang_getCursorType(c)
|
||||
name := getString(C.tinygo_clang_getCursorSpelling(c))
|
||||
if _, required := p.missingSymbols["struct_"+name]; !required {
|
||||
return C.CXChildVisit_Continue
|
||||
}
|
||||
p.makeASTType(typ, pos)
|
||||
case C.CXCursor_TypedefDecl:
|
||||
typedefType := C.tinygo_clang_getCursorType(c)
|
||||
name := getString(C.clang_getTypedefName(typedefType))
|
||||
if _, required := p.missingSymbols[name]; !required {
|
||||
return C.CXChildVisit_Continue
|
||||
}
|
||||
p.makeASTType(typedefType, pos)
|
||||
case C.CXCursor_VarDecl:
|
||||
name := getString(C.tinygo_clang_getCursorSpelling(c))
|
||||
if _, required := p.missingSymbols[name]; !required {
|
||||
return C.CXChildVisit_Continue
|
||||
}
|
||||
cursorType := C.tinygo_clang_getCursorType(c)
|
||||
p.globals[name] = globalInfo{
|
||||
typeExpr: p.makeASTType(cursorType, pos),
|
||||
pos: pos,
|
||||
}
|
||||
case C.CXCursor_MacroDefinition:
|
||||
name := getString(C.tinygo_clang_getCursorSpelling(c))
|
||||
if _, required := p.missingSymbols[name]; !required {
|
||||
return C.CXChildVisit_Continue
|
||||
}
|
||||
sourceRange := C.tinygo_clang_getCursorExtent(c)
|
||||
start := C.clang_getRangeStart(sourceRange)
|
||||
end := C.clang_getRangeEnd(sourceRange)
|
||||
var file, endFile C.CXFile
|
||||
var startOffset, endOffset C.unsigned
|
||||
C.clang_getExpansionLocation(start, &file, nil, nil, &startOffset)
|
||||
if file == nil {
|
||||
panic("could not find file where macro is defined")
|
||||
}
|
||||
C.clang_getExpansionLocation(end, &endFile, nil, nil, &endOffset)
|
||||
if file != endFile {
|
||||
panic("expected start and end location of a #define to be in the same file")
|
||||
}
|
||||
if startOffset > endOffset {
|
||||
panic("startOffset > endOffset")
|
||||
}
|
||||
|
||||
// read file contents and extract the relevant byte range
|
||||
tu := C.tinygo_clang_Cursor_getTranslationUnit(c)
|
||||
var size C.size_t
|
||||
sourcePtr := C.clang_getFileContents(tu, file, &size)
|
||||
if endOffset >= C.uint(size) {
|
||||
panic("endOffset lies after end of file")
|
||||
}
|
||||
source := string(((*[1 << 28]byte)(unsafe.Pointer(sourcePtr)))[startOffset:endOffset:endOffset])
|
||||
if !strings.HasPrefix(source, name) {
|
||||
panic(fmt.Sprintf("expected #define value to start with %#v, got %#v", name, source))
|
||||
}
|
||||
value := strings.TrimSpace(source[len(name):])
|
||||
for len(value) != 0 && value[0] == '(' && value[len(value)-1] == ')' {
|
||||
value = strings.TrimSpace(value[1 : len(value)-1])
|
||||
}
|
||||
if len(value) == 0 {
|
||||
// Pretend it doesn't exist at all.
|
||||
return C.CXChildVisit_Continue
|
||||
}
|
||||
// For information about integer literals:
|
||||
// https://en.cppreference.com/w/cpp/language/integer_literal
|
||||
if value[0] == '"' {
|
||||
// string constant
|
||||
p.constants[name] = constantInfo{&ast.BasicLit{pos, token.STRING, value}, pos}
|
||||
return C.CXChildVisit_Continue
|
||||
}
|
||||
if value[0] == '\'' {
|
||||
// char constant
|
||||
p.constants[name] = constantInfo{&ast.BasicLit{pos, token.CHAR, value}, pos}
|
||||
return C.CXChildVisit_Continue
|
||||
}
|
||||
// assume it's a number (int or float)
|
||||
value = strings.Replace(value, "'", "", -1) // remove ' chars
|
||||
value = strings.TrimRight(value, "lu") // remove llu suffixes etc.
|
||||
// find the first non-number
|
||||
nonnum := byte(0)
|
||||
for i := 0; i < len(value); i++ {
|
||||
if value[i] < '0' || value[i] > '9' {
|
||||
nonnum = value[i]
|
||||
break
|
||||
}
|
||||
}
|
||||
// determine number type based on the first non-number
|
||||
switch nonnum {
|
||||
case 0:
|
||||
// no non-number found, must be an integer
|
||||
p.constants[name] = constantInfo{&ast.BasicLit{pos, token.INT, value}, pos}
|
||||
case 'x', 'X':
|
||||
// hex integer constant
|
||||
// TODO: may also be a floating point number per C++17.
|
||||
p.constants[name] = constantInfo{&ast.BasicLit{pos, token.INT, value}, pos}
|
||||
case '.', 'e':
|
||||
// float constant
|
||||
value = strings.TrimRight(value, "fFlL")
|
||||
p.constants[name] = constantInfo{&ast.BasicLit{pos, token.FLOAT, value}, pos}
|
||||
default:
|
||||
// unknown type, ignore
|
||||
}
|
||||
}
|
||||
return C.CXChildVisit_Continue
|
||||
}
|
||||
|
||||
func getString(clangString C.CXString) (s string) {
|
||||
rawString := C.clang_getCString(clangString)
|
||||
s = C.GoString(rawString)
|
||||
C.clang_disposeString(clangString)
|
||||
return
|
||||
}
|
||||
|
||||
// getCursorPosition returns a usable token.Pos from a libclang cursor. If the
|
||||
// file for this cursor has not been seen before, it is read from libclang
|
||||
// (which already has the file in memory) and added to the token.FileSet.
|
||||
func (p *cgoPackage) getCursorPosition(cursor C.GoCXCursor) token.Pos {
|
||||
location := C.tinygo_clang_getCursorLocation(cursor)
|
||||
var file C.CXFile
|
||||
var line C.unsigned
|
||||
var column C.unsigned
|
||||
var offset C.unsigned
|
||||
C.clang_getExpansionLocation(location, &file, &line, &column, &offset)
|
||||
if line == 0 || file == nil {
|
||||
// Invalid token.
|
||||
return token.NoPos
|
||||
}
|
||||
filename := getString(C.clang_getFileName(file))
|
||||
if _, ok := p.tokenFiles[filename]; !ok {
|
||||
// File has not been seen before in this package, add line information
|
||||
// now by reading the file from libclang.
|
||||
tu := C.tinygo_clang_Cursor_getTranslationUnit(cursor)
|
||||
var size C.size_t
|
||||
sourcePtr := C.clang_getFileContents(tu, file, &size)
|
||||
source := ((*[1 << 28]byte)(unsafe.Pointer(sourcePtr)))[:size:size]
|
||||
lines := []int{0}
|
||||
for i := 0; i < len(source)-1; i++ {
|
||||
if source[i] == '\n' {
|
||||
lines = append(lines, i+1)
|
||||
}
|
||||
}
|
||||
f := p.fset.AddFile(filename, -1, int(size))
|
||||
f.SetLines(lines)
|
||||
p.tokenFiles[filename] = f
|
||||
}
|
||||
return p.tokenFiles[filename].Pos(int(offset))
|
||||
}
|
||||
|
||||
// makeASTType return the ast.Expr for the given libclang type. In other words,
|
||||
// it converts a libclang type to a type in the Go AST.
|
||||
func (p *cgoPackage) makeASTType(typ C.CXType, pos token.Pos) ast.Expr {
|
||||
var typeName string
|
||||
switch typ.kind {
|
||||
case C.CXType_Char_S, C.CXType_Char_U:
|
||||
typeName = "C.char"
|
||||
case C.CXType_SChar:
|
||||
typeName = "C.schar"
|
||||
case C.CXType_UChar:
|
||||
typeName = "C.uchar"
|
||||
case C.CXType_Short:
|
||||
typeName = "C.short"
|
||||
case C.CXType_UShort:
|
||||
typeName = "C.ushort"
|
||||
case C.CXType_Int:
|
||||
typeName = "C.int"
|
||||
case C.CXType_UInt:
|
||||
typeName = "C.uint"
|
||||
case C.CXType_Long:
|
||||
typeName = "C.long"
|
||||
case C.CXType_ULong:
|
||||
typeName = "C.ulong"
|
||||
case C.CXType_LongLong:
|
||||
typeName = "C.longlong"
|
||||
case C.CXType_ULongLong:
|
||||
typeName = "C.ulonglong"
|
||||
case C.CXType_Bool:
|
||||
typeName = "bool"
|
||||
case C.CXType_Float, C.CXType_Double, C.CXType_LongDouble:
|
||||
switch C.clang_Type_getSizeOf(typ) {
|
||||
case 4:
|
||||
typeName = "float32"
|
||||
case 8:
|
||||
typeName = "float64"
|
||||
default:
|
||||
// Don't do anything, rely on the fallback code to show a somewhat
|
||||
// sensible error message like "undeclared name: C.long double".
|
||||
}
|
||||
case C.CXType_Complex:
|
||||
switch C.clang_Type_getSizeOf(typ) {
|
||||
case 8:
|
||||
typeName = "complex64"
|
||||
case 16:
|
||||
typeName = "complex128"
|
||||
}
|
||||
case C.CXType_Pointer:
|
||||
pointeeType := C.clang_getPointeeType(typ)
|
||||
if pointeeType.kind == C.CXType_Void {
|
||||
// void* type is translated to Go as unsafe.Pointer
|
||||
return &ast.SelectorExpr{
|
||||
X: &ast.Ident{
|
||||
NamePos: pos,
|
||||
Name: "unsafe",
|
||||
},
|
||||
Sel: &ast.Ident{
|
||||
NamePos: pos,
|
||||
Name: "Pointer",
|
||||
},
|
||||
}
|
||||
}
|
||||
return &ast.StarExpr{
|
||||
Star: pos,
|
||||
X: p.makeASTType(pointeeType, pos),
|
||||
}
|
||||
case C.CXType_ConstantArray:
|
||||
return &ast.ArrayType{
|
||||
Lbrack: pos,
|
||||
Len: &ast.BasicLit{
|
||||
ValuePos: pos,
|
||||
Kind: token.INT,
|
||||
Value: strconv.FormatInt(int64(C.clang_getArraySize(typ)), 10),
|
||||
},
|
||||
Elt: p.makeASTType(C.clang_getElementType(typ), pos),
|
||||
}
|
||||
case C.CXType_FunctionProto:
|
||||
// Be compatible with gc, which uses the *[0]byte type for function
|
||||
// pointer types.
|
||||
// Return type [0]byte because this is a function type, not a pointer to
|
||||
// this function type.
|
||||
return &ast.ArrayType{
|
||||
Lbrack: pos,
|
||||
Len: &ast.BasicLit{
|
||||
ValuePos: pos,
|
||||
Kind: token.INT,
|
||||
Value: "0",
|
||||
},
|
||||
Elt: &ast.Ident{
|
||||
NamePos: pos,
|
||||
Name: "byte",
|
||||
},
|
||||
}
|
||||
case C.CXType_Typedef:
|
||||
name := getString(C.clang_getTypedefName(typ))
|
||||
if _, ok := p.typedefs[name]; !ok {
|
||||
p.typedefs[name] = nil // don't recurse
|
||||
c := C.tinygo_clang_getTypeDeclaration(typ)
|
||||
underlyingType := C.tinygo_clang_getTypedefDeclUnderlyingType(c)
|
||||
expr := p.makeASTType(underlyingType, pos)
|
||||
if strings.HasPrefix(name, "_Cgo_") {
|
||||
expr := expr.(*ast.Ident)
|
||||
typeSize := C.clang_Type_getSizeOf(underlyingType)
|
||||
switch expr.Name {
|
||||
case "C.char":
|
||||
if typeSize != 1 {
|
||||
// This happens for some very special purpose architectures
|
||||
// (DSPs etc.) that are not currently targeted.
|
||||
// https://www.embecosm.com/2017/04/18/non-8-bit-char-support-in-clang-and-llvm/
|
||||
panic("unknown char width")
|
||||
}
|
||||
switch underlyingType.kind {
|
||||
case C.CXType_Char_S:
|
||||
expr.Name = "int8"
|
||||
case C.CXType_Char_U:
|
||||
expr.Name = "uint8"
|
||||
}
|
||||
case "C.schar", "C.short", "C.int", "C.long", "C.longlong":
|
||||
switch typeSize {
|
||||
case 1:
|
||||
expr.Name = "int8"
|
||||
case 2:
|
||||
expr.Name = "int16"
|
||||
case 4:
|
||||
expr.Name = "int32"
|
||||
case 8:
|
||||
expr.Name = "int64"
|
||||
}
|
||||
case "C.uchar", "C.ushort", "C.uint", "C.ulong", "C.ulonglong":
|
||||
switch typeSize {
|
||||
case 1:
|
||||
expr.Name = "uint8"
|
||||
case 2:
|
||||
expr.Name = "uint16"
|
||||
case 4:
|
||||
expr.Name = "uint32"
|
||||
case 8:
|
||||
expr.Name = "uint64"
|
||||
}
|
||||
}
|
||||
}
|
||||
p.typedefs[name] = &typedefInfo{
|
||||
typeExpr: expr,
|
||||
pos: pos,
|
||||
}
|
||||
}
|
||||
return &ast.Ident{
|
||||
NamePos: pos,
|
||||
Name: "C." + name,
|
||||
}
|
||||
case C.CXType_Elaborated:
|
||||
underlying := C.clang_Type_getNamedType(typ)
|
||||
switch underlying.kind {
|
||||
case C.CXType_Record:
|
||||
return p.makeASTType(underlying, pos)
|
||||
default:
|
||||
panic("unknown elaborated type")
|
||||
}
|
||||
case C.CXType_Record:
|
||||
cursor := C.tinygo_clang_getTypeDeclaration(typ)
|
||||
name := getString(C.tinygo_clang_getCursorSpelling(cursor))
|
||||
var cgoName string
|
||||
switch C.tinygo_clang_getCursorKind(cursor) {
|
||||
case C.CXCursor_StructDecl:
|
||||
cgoName = "struct_" + name
|
||||
case C.CXCursor_UnionDecl:
|
||||
cgoName = "union_" + name
|
||||
default:
|
||||
panic("unknown record declaration")
|
||||
}
|
||||
if _, ok := p.elaboratedTypes[cgoName]; !ok {
|
||||
p.elaboratedTypes[cgoName] = nil // predeclare (to avoid endless recursion)
|
||||
fieldList := &ast.FieldList{
|
||||
Opening: pos,
|
||||
Closing: pos,
|
||||
}
|
||||
ref := storedRefs.Put(struct {
|
||||
fieldList *ast.FieldList
|
||||
pkg *cgoPackage
|
||||
}{fieldList, p})
|
||||
defer storedRefs.Remove(ref)
|
||||
C.tinygo_clang_visitChildren(cursor, C.CXCursorVisitor(C.tinygo_clang_struct_visitor), C.CXClientData(ref))
|
||||
switch C.tinygo_clang_getCursorKind(cursor) {
|
||||
case C.CXCursor_StructDecl:
|
||||
p.elaboratedTypes[cgoName] = &elaboratedTypeInfo{
|
||||
typeExpr: &ast.StructType{
|
||||
Struct: pos,
|
||||
Fields: fieldList,
|
||||
},
|
||||
pos: pos,
|
||||
}
|
||||
case C.CXCursor_UnionDecl:
|
||||
if len(fieldList.List) > 1 {
|
||||
// Insert a special field at the front (of zero width) as a
|
||||
// marker that this is struct is actually a union. This is done
|
||||
// by giving the field a name that cannot be expressed directly
|
||||
// in Go.
|
||||
// Other parts of the compiler look at the first element in a
|
||||
// struct (of size > 2) to know whether this is a union.
|
||||
// Note that we don't have to insert it for single-element
|
||||
// unions as they're basically equivalent to a struct.
|
||||
unionMarker := &ast.Field{
|
||||
Type: &ast.StructType{
|
||||
Struct: pos,
|
||||
},
|
||||
}
|
||||
unionMarker.Names = []*ast.Ident{
|
||||
&ast.Ident{
|
||||
NamePos: pos,
|
||||
Name: "C union",
|
||||
Obj: &ast.Object{
|
||||
Kind: ast.Var,
|
||||
Name: "C union",
|
||||
Decl: unionMarker,
|
||||
},
|
||||
},
|
||||
}
|
||||
fieldList.List = append([]*ast.Field{unionMarker}, fieldList.List...)
|
||||
}
|
||||
p.elaboratedTypes[cgoName] = &elaboratedTypeInfo{
|
||||
typeExpr: &ast.StructType{
|
||||
Struct: pos,
|
||||
Fields: fieldList,
|
||||
},
|
||||
pos: pos,
|
||||
}
|
||||
default:
|
||||
panic("unreachable")
|
||||
}
|
||||
}
|
||||
return &ast.Ident{
|
||||
NamePos: pos,
|
||||
Name: "C." + cgoName,
|
||||
}
|
||||
}
|
||||
if typeName == "" {
|
||||
// Fallback, probably incorrect but at least the error points to an odd
|
||||
// type name.
|
||||
typeName = "C." + getString(C.clang_getTypeSpelling(typ))
|
||||
}
|
||||
return &ast.Ident{
|
||||
NamePos: pos,
|
||||
Name: typeName,
|
||||
}
|
||||
}
|
||||
|
||||
//export tinygo_clang_struct_visitor
|
||||
func tinygo_clang_struct_visitor(c, parent C.GoCXCursor, client_data C.CXClientData) C.int {
|
||||
passed := storedRefs.Get(unsafe.Pointer(client_data)).(struct {
|
||||
fieldList *ast.FieldList
|
||||
pkg *cgoPackage
|
||||
})
|
||||
fieldList := passed.fieldList
|
||||
p := passed.pkg
|
||||
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: p.makeASTType(typ, p.getCursorPosition(c)),
|
||||
}
|
||||
field.Names = []*ast.Ident{
|
||||
&ast.Ident{
|
||||
NamePos: p.getCursorPosition(c),
|
||||
Name: name,
|
||||
Obj: &ast.Object{
|
||||
Kind: ast.Var,
|
||||
Name: name,
|
||||
Decl: field,
|
||||
},
|
||||
},
|
||||
}
|
||||
fieldList.List = append(fieldList.List, field)
|
||||
return C.CXChildVisit_Continue
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
// +build !byollvm
|
||||
|
||||
package cgo
|
||||
|
||||
/*
|
||||
#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-8/lib -lclang
|
||||
#cgo darwin LDFLAGS: -L/usr/local/opt/llvm/lib -lclang -lffi
|
||||
*/
|
||||
import "C"
|
||||
@@ -0,0 +1,58 @@
|
||||
|
||||
// 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);
|
||||
}
|
||||
|
||||
CXSourceLocation tinygo_clang_getCursorLocation(CXCursor c) {
|
||||
return clang_getCursorLocation(c);
|
||||
}
|
||||
|
||||
CXSourceRange tinygo_clang_getCursorExtent(CXCursor c) {
|
||||
return clang_getCursorExtent(c);
|
||||
}
|
||||
|
||||
CXTranslationUnit tinygo_clang_Cursor_getTranslationUnit(CXCursor c) {
|
||||
return clang_Cursor_getTranslationUnit(c);
|
||||
}
|
||||
+46
@@ -0,0 +1,46 @@
|
||||
package cgo
|
||||
|
||||
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)
|
||||
}
|
||||
+33
@@ -0,0 +1,33 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"os"
|
||||
"os/exec"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Commands used by the compilation process might have different file names
|
||||
// across operating systems and distributions.
|
||||
var commands = map[string][]string{
|
||||
"clang": {"clang-8"},
|
||||
"ld.lld": {"ld.lld-8", "ld.lld"},
|
||||
"wasm-ld": {"wasm-ld-8", "wasm-ld"},
|
||||
}
|
||||
|
||||
func execCommand(cmdNames []string, args ...string) error {
|
||||
for _, cmdName := range cmdNames {
|
||||
cmd := exec.Command(cmdName, args...)
|
||||
cmd.Stdout = os.Stdout
|
||||
cmd.Stderr = os.Stderr
|
||||
err := cmd.Run()
|
||||
if err != nil {
|
||||
if err, ok := err.(*exec.Error); ok && err.Err == exec.ErrNotFound {
|
||||
// this command was not found, try the next
|
||||
continue
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
return errors.New("none of these commands were found in your $PATH: " + strings.Join(cmdNames, " "))
|
||||
}
|
||||
@@ -0,0 +1,149 @@
|
||||
package compiler
|
||||
|
||||
// This file implements functions that do certain safety checks that are
|
||||
// required by the Go programming language.
|
||||
|
||||
import (
|
||||
"go/types"
|
||||
|
||||
"tinygo.org/x/go-llvm"
|
||||
)
|
||||
|
||||
// emitLookupBoundsCheck emits a bounds check before doing a lookup into a
|
||||
// slice. This is required by the Go language spec: an index out of bounds must
|
||||
// cause a panic.
|
||||
func (c *Compiler) emitLookupBoundsCheck(frame *Frame, arrayLen, index llvm.Value, indexType types.Type) {
|
||||
if frame.fn.IsNoBounds() {
|
||||
// The //go:nobounds pragma was added to the function to avoid bounds
|
||||
// checking.
|
||||
return
|
||||
}
|
||||
|
||||
if index.Type().IntTypeWidth() < arrayLen.Type().IntTypeWidth() {
|
||||
// Sometimes, the index can be e.g. an uint8 or int8, and we have to
|
||||
// correctly extend that type.
|
||||
if indexType.(*types.Basic).Info()&types.IsUnsigned == 0 {
|
||||
index = c.builder.CreateZExt(index, arrayLen.Type(), "")
|
||||
} else {
|
||||
index = c.builder.CreateSExt(index, arrayLen.Type(), "")
|
||||
}
|
||||
} else if index.Type().IntTypeWidth() > arrayLen.Type().IntTypeWidth() {
|
||||
// The index is bigger than the array length type, so extend it.
|
||||
arrayLen = c.builder.CreateZExt(arrayLen, index.Type(), "")
|
||||
}
|
||||
|
||||
faultBlock := c.ctx.AddBasicBlock(frame.fn.LLVMFn, "lookup.outofbounds")
|
||||
nextBlock := c.ctx.AddBasicBlock(frame.fn.LLVMFn, "lookup.next")
|
||||
frame.blockExits[frame.currentBlock] = nextBlock // adjust outgoing block for phi nodes
|
||||
|
||||
// Now do the bounds check: index >= arrayLen
|
||||
outOfBounds := c.builder.CreateICmp(llvm.IntUGE, index, arrayLen, "")
|
||||
c.builder.CreateCondBr(outOfBounds, faultBlock, nextBlock)
|
||||
|
||||
// Fail: this is a nil pointer, exit with a panic.
|
||||
c.builder.SetInsertPointAtEnd(faultBlock)
|
||||
c.createRuntimeCall("lookuppanic", nil, "")
|
||||
c.builder.CreateUnreachable()
|
||||
|
||||
// Ok: this is a valid pointer.
|
||||
c.builder.SetInsertPointAtEnd(nextBlock)
|
||||
}
|
||||
|
||||
// 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
|
||||
// checking.
|
||||
return
|
||||
}
|
||||
|
||||
// Extend the capacity integer to be at least as wide as low and high.
|
||||
capacityType := capacity.Type()
|
||||
if low.Type().IntTypeWidth() > capacityType.IntTypeWidth() {
|
||||
capacityType = low.Type()
|
||||
}
|
||||
if high.Type().IntTypeWidth() > capacityType.IntTypeWidth() {
|
||||
capacityType = high.Type()
|
||||
}
|
||||
if capacityType != capacity.Type() {
|
||||
capacity = c.builder.CreateZExt(capacity, capacityType, "")
|
||||
}
|
||||
|
||||
// Extend low and high to be the same size as capacity.
|
||||
if low.Type().IntTypeWidth() < capacityType.IntTypeWidth() {
|
||||
if lowType.Info()&types.IsUnsigned != 0 {
|
||||
low = c.builder.CreateZExt(low, capacityType, "")
|
||||
} else {
|
||||
low = c.builder.CreateSExt(low, capacityType, "")
|
||||
}
|
||||
}
|
||||
if high.Type().IntTypeWidth() < capacityType.IntTypeWidth() {
|
||||
if highType.Info()&types.IsUnsigned != 0 {
|
||||
high = c.builder.CreateZExt(high, capacityType, "")
|
||||
} else {
|
||||
high = c.builder.CreateSExt(high, capacityType, "")
|
||||
}
|
||||
}
|
||||
|
||||
faultBlock := c.ctx.AddBasicBlock(frame.fn.LLVMFn, "slice.outofbounds")
|
||||
nextBlock := c.ctx.AddBasicBlock(frame.fn.LLVMFn, "slice.next")
|
||||
frame.blockExits[frame.currentBlock] = nextBlock // adjust outgoing block for phi nodes
|
||||
|
||||
// Now do the bounds check: low > high || high > capacity
|
||||
outOfBounds1 := c.builder.CreateICmp(llvm.IntUGT, low, high, "slice.lowhigh")
|
||||
outOfBounds2 := c.builder.CreateICmp(llvm.IntUGT, high, capacity, "slice.highcap")
|
||||
outOfBounds := c.builder.CreateOr(outOfBounds1, outOfBounds2, "slice.outofbounds")
|
||||
c.builder.CreateCondBr(outOfBounds, faultBlock, nextBlock)
|
||||
|
||||
// Fail: this is a nil pointer, exit with a panic.
|
||||
c.builder.SetInsertPointAtEnd(faultBlock)
|
||||
c.createRuntimeCall("slicepanic", nil, "")
|
||||
c.builder.CreateUnreachable()
|
||||
|
||||
// Ok: this is a valid pointer.
|
||||
c.builder.SetInsertPointAtEnd(nextBlock)
|
||||
}
|
||||
|
||||
// emitNilCheck checks whether the given pointer is nil, and panics if it is. It
|
||||
// has no effect in well-behaved programs, but makes sure no uncaught nil
|
||||
// pointer dereferences exist in valid Go code.
|
||||
func (c *Compiler) emitNilCheck(frame *Frame, ptr llvm.Value, blockPrefix string) {
|
||||
// Check whether this is a nil pointer.
|
||||
faultBlock := c.ctx.AddBasicBlock(frame.fn.LLVMFn, blockPrefix+".nil")
|
||||
nextBlock := c.ctx.AddBasicBlock(frame.fn.LLVMFn, blockPrefix+".next")
|
||||
frame.blockExits[frame.currentBlock] = nextBlock // adjust outgoing block for phi nodes
|
||||
|
||||
// Compare against nil.
|
||||
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.
|
||||
c.builder.SetInsertPointAtEnd(faultBlock)
|
||||
c.createRuntimeCall("nilpanic", nil, "")
|
||||
c.builder.CreateUnreachable()
|
||||
|
||||
// Ok: this is a valid pointer.
|
||||
c.builder.SetInsertPointAtEnd(nextBlock)
|
||||
}
|
||||
+41
-4
@@ -55,6 +55,25 @@ func (c *Compiler) expandFormalParamType(t llvm.Type) []llvm.Type {
|
||||
}
|
||||
}
|
||||
|
||||
// Expand an argument type to a list of offsets from the start of the object.
|
||||
// Used together with expandFormalParam to get the offset of each value from the
|
||||
// start of the non-expanded value.
|
||||
func (c *Compiler) expandFormalParamOffsets(t llvm.Type) []uint64 {
|
||||
switch t.TypeKind() {
|
||||
case llvm.StructTypeKind:
|
||||
fields := c.flattenAggregateTypeOffsets(t)
|
||||
if len(fields) <= MaxFieldsPerParam {
|
||||
return fields
|
||||
} else {
|
||||
// failed to lower
|
||||
return []uint64{0}
|
||||
}
|
||||
default:
|
||||
// TODO: split small arrays
|
||||
return []uint64{0}
|
||||
}
|
||||
}
|
||||
|
||||
// Equivalent of expandFormalParamType for parameter values.
|
||||
func (c *Compiler) expandFormalParam(v llvm.Value) []llvm.Value {
|
||||
switch v.Type().TypeKind() {
|
||||
@@ -92,6 +111,27 @@ func (c *Compiler) flattenAggregateType(t llvm.Type) []llvm.Type {
|
||||
}
|
||||
}
|
||||
|
||||
// Return the offsets from the start of the object if this object type were
|
||||
// flattened like in flattenAggregate. Used together with flattenAggregate to
|
||||
// know the start indices of each value in the non-flattened object.
|
||||
func (c *Compiler) flattenAggregateTypeOffsets(t llvm.Type) []uint64 {
|
||||
switch t.TypeKind() {
|
||||
case llvm.StructTypeKind:
|
||||
fields := make([]uint64, 0, t.StructElementTypesCount())
|
||||
for fieldIndex, field := range t.StructElementTypes() {
|
||||
suboffsets := c.flattenAggregateTypeOffsets(field)
|
||||
offset := c.targetData.ElementOffset(t, fieldIndex)
|
||||
for i := range suboffsets {
|
||||
suboffsets[i] += offset
|
||||
}
|
||||
fields = append(fields, suboffsets...)
|
||||
}
|
||||
return fields
|
||||
default:
|
||||
return []uint64{0}
|
||||
}
|
||||
}
|
||||
|
||||
// Break down a struct into its elementary types for argument passing. The value
|
||||
// equivalent of flattenAggregateType
|
||||
func (c *Compiler) flattenAggregate(v llvm.Value) []llvm.Value {
|
||||
@@ -123,10 +163,7 @@ func (c *Compiler) collapseFormalParamInternal(t llvm.Type, fields []llvm.Value)
|
||||
switch t.TypeKind() {
|
||||
case llvm.StructTypeKind:
|
||||
if len(c.flattenAggregateType(t)) <= MaxFieldsPerParam {
|
||||
value, err := c.getZeroValue(t)
|
||||
if err != nil {
|
||||
panic("could not get zero value of struct: " + err.Error())
|
||||
}
|
||||
value := c.getZeroValue(t)
|
||||
for i, subtyp := range t.StructElementTypes() {
|
||||
structField, remaining := c.collapseFormalParamInternal(subtyp, fields)
|
||||
fields = remaining
|
||||
|
||||
+37
-49
@@ -12,16 +12,6 @@ import (
|
||||
|
||||
// emitMakeChan returns a new channel value for the given channel type.
|
||||
func (c *Compiler) emitMakeChan(expr *ssa.MakeChan) (llvm.Value, error) {
|
||||
valueType, err := c.getLLVMType(expr.Type().(*types.Chan).Elem())
|
||||
if err != nil {
|
||||
return llvm.Value{}, err
|
||||
}
|
||||
if c.targetData.TypeAllocSize(valueType) > c.targetData.TypeAllocSize(c.intType) {
|
||||
// Values bigger than int overflow the data part of the coroutine.
|
||||
// TODO: make the coroutine data part big enough to hold these bigger
|
||||
// values.
|
||||
return llvm.Value{}, c.makeError(expr.Pos(), "todo: channel with values bigger than int")
|
||||
}
|
||||
chanType := c.mod.GetTypeByName("runtime.channel")
|
||||
size := c.targetData.TypeAllocSize(chanType)
|
||||
sizeValue := llvm.ConstInt(c.uintptrType, size, false)
|
||||
@@ -32,66 +22,64 @@ func (c *Compiler) emitMakeChan(expr *ssa.MakeChan) (llvm.Value, error) {
|
||||
|
||||
// emitChanSend emits a pseudo chan send operation. It is lowered to the actual
|
||||
// channel send operation during goroutine lowering.
|
||||
func (c *Compiler) emitChanSend(frame *Frame, instr *ssa.Send) error {
|
||||
valueType, err := c.getLLVMType(instr.Chan.Type().(*types.Chan).Elem())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
ch, err := c.parseExpr(frame, instr.Chan)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
chanValue, err := c.parseExpr(frame, instr.X)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
func (c *Compiler) emitChanSend(frame *Frame, instr *ssa.Send) {
|
||||
valueType := c.getLLVMType(instr.X.Type())
|
||||
ch := c.getValue(frame, instr.Chan)
|
||||
chanValue := c.getValue(frame, instr.X)
|
||||
valueSize := llvm.ConstInt(c.uintptrType, c.targetData.TypeAllocSize(chanValue.Type()), false)
|
||||
coroutine := c.createRuntimeCall("getCoroutine", nil, "")
|
||||
|
||||
// store value-to-send
|
||||
c.builder.SetInsertPointBefore(coroutine.InstructionParent().Parent().EntryBasicBlock().FirstInstruction())
|
||||
valueAlloca := c.builder.CreateAlloca(valueType, "chan.value")
|
||||
c.builder.SetInsertPointBefore(coroutine)
|
||||
c.builder.SetInsertPointAtEnd(coroutine.InstructionParent())
|
||||
c.builder.CreateStore(chanValue, valueAlloca)
|
||||
valueAllocaCast := c.builder.CreateBitCast(valueAlloca, c.i8ptrType, "chan.value.i8ptr")
|
||||
c.createRuntimeCall("chanSendStub", []llvm.Value{llvm.Undef(c.i8ptrType), ch, valueAllocaCast, valueSize}, "")
|
||||
return nil
|
||||
|
||||
// Do the send.
|
||||
c.createRuntimeCall("chanSend", []llvm.Value{coroutine, ch, valueAllocaCast, valueSize}, "")
|
||||
|
||||
// Make sure CoroSplit includes the alloca in the coroutine frame.
|
||||
// This is a bit dirty, but it works (at least in LLVM 8).
|
||||
valueSizeI64 := llvm.ConstInt(c.ctx.Int64Type(), c.targetData.TypeAllocSize(chanValue.Type()), false)
|
||||
c.builder.CreateCall(c.getLifetimeEndFunc(), []llvm.Value{valueSizeI64, valueAllocaCast}, "")
|
||||
}
|
||||
|
||||
// emitChanRecv emits a pseudo chan receive operation. It is lowered to the
|
||||
// actual channel receive operation during goroutine lowering.
|
||||
func (c *Compiler) emitChanRecv(frame *Frame, unop *ssa.UnOp) (llvm.Value, error) {
|
||||
valueType, err := c.getLLVMType(unop.X.Type().(*types.Chan).Elem())
|
||||
if err != nil {
|
||||
return llvm.Value{}, err
|
||||
}
|
||||
func (c *Compiler) emitChanRecv(frame *Frame, unop *ssa.UnOp) llvm.Value {
|
||||
valueType := c.getLLVMType(unop.X.Type().(*types.Chan).Elem())
|
||||
valueSize := llvm.ConstInt(c.uintptrType, c.targetData.TypeAllocSize(valueType), false)
|
||||
ch, err := c.parseExpr(frame, unop.X)
|
||||
if err != nil {
|
||||
return llvm.Value{}, err
|
||||
}
|
||||
ch := c.getValue(frame, unop.X)
|
||||
coroutine := c.createRuntimeCall("getCoroutine", nil, "")
|
||||
|
||||
// Allocate memory to receive into.
|
||||
c.builder.SetInsertPointBefore(coroutine.InstructionParent().Parent().EntryBasicBlock().FirstInstruction())
|
||||
valueAlloca := c.builder.CreateAlloca(valueType, "chan.value")
|
||||
c.builder.SetInsertPointBefore(coroutine)
|
||||
c.builder.SetInsertPointAtEnd(coroutine.InstructionParent())
|
||||
valueAllocaCast := c.builder.CreateBitCast(valueAlloca, c.i8ptrType, "chan.value.i8ptr")
|
||||
valueOk := c.builder.CreateAlloca(c.ctx.Int1Type(), "chan.comma-ok.alloca")
|
||||
c.createRuntimeCall("chanRecvStub", []llvm.Value{llvm.Undef(c.i8ptrType), ch, valueAllocaCast, valueOk, valueSize}, "")
|
||||
|
||||
// Do the receive.
|
||||
c.createRuntimeCall("chanRecv", []llvm.Value{coroutine, ch, valueAllocaCast, valueSize}, "")
|
||||
received := c.builder.CreateLoad(valueAlloca, "chan.received")
|
||||
if unop.CommaOk {
|
||||
commaOk := c.builder.CreateLoad(valueOk, "chan.comma-ok")
|
||||
commaOk := c.createRuntimeCall("getTaskPromiseData", []llvm.Value{coroutine}, "chan.commaOk.wide")
|
||||
commaOk = c.builder.CreateTrunc(commaOk, c.ctx.Int1Type(), "chan.commaOk")
|
||||
tuple := llvm.Undef(c.ctx.StructType([]llvm.Type{valueType, c.ctx.Int1Type()}, false))
|
||||
tuple = c.builder.CreateInsertValue(tuple, received, 0, "")
|
||||
tuple = c.builder.CreateInsertValue(tuple, commaOk, 1, "")
|
||||
return tuple, nil
|
||||
return tuple
|
||||
} else {
|
||||
return received, nil
|
||||
return received
|
||||
}
|
||||
}
|
||||
|
||||
// emitChanClose closes the given channel.
|
||||
func (c *Compiler) emitChanClose(frame *Frame, param ssa.Value) error {
|
||||
valueType, err := c.getLLVMType(param.Type().(*types.Chan).Elem())
|
||||
func (c *Compiler) emitChanClose(frame *Frame, param ssa.Value) {
|
||||
valueType := c.getLLVMType(param.Type().(*types.Chan).Elem())
|
||||
valueSize := llvm.ConstInt(c.uintptrType, c.targetData.TypeAllocSize(valueType), false)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
ch, err := c.parseExpr(frame, param)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
ch := c.getValue(frame, param)
|
||||
c.createRuntimeCall("chanClose", []llvm.Value{ch, valueSize}, "")
|
||||
return nil
|
||||
}
|
||||
|
||||
+925
-1363
File diff suppressed because it is too large
Load Diff
+19
-53
@@ -36,7 +36,7 @@ func (c *Compiler) deferInitFunc(frame *Frame) {
|
||||
|
||||
// emitDefer emits a single defer instruction, to be run when this function
|
||||
// returns.
|
||||
func (c *Compiler) emitDefer(frame *Frame, instr *ssa.Defer) error {
|
||||
func (c *Compiler) emitDefer(frame *Frame, instr *ssa.Defer) {
|
||||
// The pointer to the previous defer struct, which we will replace to
|
||||
// make a linked list.
|
||||
next := c.builder.CreateLoad(frame.deferPtr, "defer.next")
|
||||
@@ -56,18 +56,12 @@ func (c *Compiler) emitDefer(frame *Frame, instr *ssa.Defer) error {
|
||||
|
||||
// Collect all values to be put in the struct (starting with
|
||||
// runtime._defer fields, followed by the call parameters).
|
||||
itf, err := c.parseExpr(frame, instr.Call.Value) // interface
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
itf := c.getValue(frame, instr.Call.Value) // interface
|
||||
receiverValue := c.builder.CreateExtractValue(itf, 1, "invoke.func.receiver")
|
||||
values = []llvm.Value{callback, next, receiverValue}
|
||||
valueTypes = append(valueTypes, c.i8ptrType)
|
||||
for _, arg := range instr.Call.Args {
|
||||
val, err := c.parseExpr(frame, arg)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
val := c.getValue(frame, arg)
|
||||
values = append(values, val)
|
||||
valueTypes = append(valueTypes, val.Type())
|
||||
}
|
||||
@@ -86,10 +80,7 @@ func (c *Compiler) emitDefer(frame *Frame, instr *ssa.Defer) error {
|
||||
// runtime._defer fields).
|
||||
values = []llvm.Value{callback, next}
|
||||
for _, param := range instr.Call.Args {
|
||||
llvmParam, err := c.parseExpr(frame, param)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
llvmParam := c.getValue(frame, param)
|
||||
values = append(values, llvmParam)
|
||||
valueTypes = append(valueTypes, llvmParam.Type())
|
||||
}
|
||||
@@ -101,10 +92,7 @@ func (c *Compiler) emitDefer(frame *Frame, instr *ssa.Defer) error {
|
||||
// pointer.
|
||||
// TODO: ignore this closure entirely and put pointers to the free
|
||||
// variables directly in the defer struct, avoiding a memory allocation.
|
||||
closure, err := c.parseExpr(frame, instr.Call.Value)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
closure := c.getValue(frame, instr.Call.Value)
|
||||
context := c.builder.CreateExtractValue(closure, 0, "")
|
||||
|
||||
// Get the callback number.
|
||||
@@ -120,10 +108,7 @@ func (c *Compiler) emitDefer(frame *Frame, instr *ssa.Defer) error {
|
||||
// context pointer).
|
||||
values = []llvm.Value{callback, next}
|
||||
for _, param := range instr.Call.Args {
|
||||
llvmParam, err := c.parseExpr(frame, param)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
llvmParam := c.getValue(frame, param)
|
||||
values = append(values, llvmParam)
|
||||
valueTypes = append(valueTypes, llvmParam.Type())
|
||||
}
|
||||
@@ -131,15 +116,13 @@ func (c *Compiler) emitDefer(frame *Frame, instr *ssa.Defer) error {
|
||||
valueTypes = append(valueTypes, context.Type())
|
||||
|
||||
} else {
|
||||
return c.makeError(instr.Pos(), "todo: defer on uncommon function call type")
|
||||
c.addError(instr.Pos(), "todo: defer on uncommon function call type")
|
||||
return
|
||||
}
|
||||
|
||||
// Make a struct out of the collected values to put in the defer frame.
|
||||
deferFrameType := c.ctx.StructType(valueTypes, false)
|
||||
deferFrame, err := c.getZeroValue(deferFrameType)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
deferFrame := c.getZeroValue(deferFrameType)
|
||||
for i, value := range values {
|
||||
deferFrame = c.builder.CreateInsertValue(deferFrame, value, i, "")
|
||||
}
|
||||
@@ -151,11 +134,10 @@ func (c *Compiler) emitDefer(frame *Frame, instr *ssa.Defer) error {
|
||||
// Push it on top of the linked list by replacing deferPtr.
|
||||
allocaCast := c.builder.CreateBitCast(alloca, next.Type(), "defer.alloca.cast")
|
||||
c.builder.CreateStore(allocaCast, frame.deferPtr)
|
||||
return nil
|
||||
}
|
||||
|
||||
// emitRunDefers emits code to run all deferred functions.
|
||||
func (c *Compiler) emitRunDefers(frame *Frame) error {
|
||||
func (c *Compiler) emitRunDefers(frame *Frame) {
|
||||
// Add a loop like the following:
|
||||
// for stack != nil {
|
||||
// _stack := stack
|
||||
@@ -190,13 +172,13 @@ func (c *Compiler) emitRunDefers(frame *Frame) error {
|
||||
// stack = stack.next
|
||||
// switch stack.callback {
|
||||
c.builder.SetInsertPointAtEnd(loop)
|
||||
nextStackGEP := c.builder.CreateGEP(deferData, []llvm.Value{
|
||||
nextStackGEP := c.builder.CreateInBoundsGEP(deferData, []llvm.Value{
|
||||
llvm.ConstInt(c.ctx.Int32Type(), 0, false),
|
||||
llvm.ConstInt(c.ctx.Int32Type(), 1, false), // .next field
|
||||
}, "stack.next.gep")
|
||||
nextStack := c.builder.CreateLoad(nextStackGEP, "stack.next")
|
||||
c.builder.CreateStore(nextStack, frame.deferPtr)
|
||||
gep := c.builder.CreateGEP(deferData, []llvm.Value{
|
||||
gep := c.builder.CreateInBoundsGEP(deferData, []llvm.Value{
|
||||
llvm.ConstInt(c.ctx.Int32Type(), 0, false),
|
||||
llvm.ConstInt(c.ctx.Int32Type(), 0, false), // .callback field
|
||||
}, "callback.gep")
|
||||
@@ -220,11 +202,7 @@ func (c *Compiler) emitRunDefers(frame *Frame) error {
|
||||
// Get the real defer struct type and cast to it.
|
||||
valueTypes := []llvm.Type{c.uintptrType, llvm.PointerType(c.mod.GetTypeByName("runtime._defer"), 0), c.i8ptrType}
|
||||
for _, arg := range callback.Args {
|
||||
llvmType, err := c.getLLVMType(arg.Type())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
valueTypes = append(valueTypes, llvmType)
|
||||
valueTypes = append(valueTypes, c.getLLVMType(arg.Type()))
|
||||
}
|
||||
deferFrameType := c.ctx.StructType(valueTypes, false)
|
||||
deferFramePtr := c.builder.CreateBitCast(deferData, llvm.PointerType(deferFrameType, 0), "deferFrame")
|
||||
@@ -233,7 +211,7 @@ func (c *Compiler) emitRunDefers(frame *Frame) error {
|
||||
forwardParams := []llvm.Value{}
|
||||
zero := llvm.ConstInt(c.ctx.Int32Type(), 0, false)
|
||||
for i := 2; i < len(valueTypes); i++ {
|
||||
gep := c.builder.CreateGEP(deferFramePtr, []llvm.Value{zero, llvm.ConstInt(c.ctx.Int32Type(), uint64(i), false)}, "gep")
|
||||
gep := c.builder.CreateInBoundsGEP(deferFramePtr, []llvm.Value{zero, llvm.ConstInt(c.ctx.Int32Type(), uint64(i), false)}, "gep")
|
||||
forwardParam := c.builder.CreateLoad(gep, "param")
|
||||
forwardParams = append(forwardParams, forwardParam)
|
||||
}
|
||||
@@ -246,10 +224,7 @@ func (c *Compiler) emitRunDefers(frame *Frame) error {
|
||||
// Parent coroutine handle.
|
||||
forwardParams = append(forwardParams, llvm.Undef(c.i8ptrType))
|
||||
|
||||
fnPtr, _, err := c.getInvokeCall(frame, callback)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
fnPtr, _ := c.getInvokeCall(frame, callback)
|
||||
c.createCall(fnPtr, forwardParams, "")
|
||||
|
||||
case *ir.Function:
|
||||
@@ -258,11 +233,7 @@ func (c *Compiler) emitRunDefers(frame *Frame) error {
|
||||
// Get the real defer struct type and cast to it.
|
||||
valueTypes := []llvm.Type{c.uintptrType, llvm.PointerType(c.mod.GetTypeByName("runtime._defer"), 0)}
|
||||
for _, param := range callback.Params {
|
||||
llvmType, err := c.getLLVMType(param.Type())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
valueTypes = append(valueTypes, llvmType)
|
||||
valueTypes = append(valueTypes, c.getLLVMType(param.Type()))
|
||||
}
|
||||
deferFrameType := c.ctx.StructType(valueTypes, false)
|
||||
deferFramePtr := c.builder.CreateBitCast(deferData, llvm.PointerType(deferFrameType, 0), "deferFrame")
|
||||
@@ -271,7 +242,7 @@ func (c *Compiler) emitRunDefers(frame *Frame) error {
|
||||
forwardParams := []llvm.Value{}
|
||||
zero := llvm.ConstInt(c.ctx.Int32Type(), 0, false)
|
||||
for i := range callback.Params {
|
||||
gep := c.builder.CreateGEP(deferFramePtr, []llvm.Value{zero, llvm.ConstInt(c.ctx.Int32Type(), uint64(i+2), false)}, "gep")
|
||||
gep := c.builder.CreateInBoundsGEP(deferFramePtr, []llvm.Value{zero, llvm.ConstInt(c.ctx.Int32Type(), uint64(i+2), false)}, "gep")
|
||||
forwardParam := c.builder.CreateLoad(gep, "param")
|
||||
forwardParams = append(forwardParams, forwardParam)
|
||||
}
|
||||
@@ -292,11 +263,7 @@ func (c *Compiler) emitRunDefers(frame *Frame) error {
|
||||
valueTypes := []llvm.Type{c.uintptrType, llvm.PointerType(c.mod.GetTypeByName("runtime._defer"), 0)}
|
||||
params := fn.Signature.Params()
|
||||
for i := 0; i < params.Len(); i++ {
|
||||
llvmType, err := c.getLLVMType(params.At(i).Type())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
valueTypes = append(valueTypes, llvmType)
|
||||
valueTypes = append(valueTypes, c.getLLVMType(params.At(i).Type()))
|
||||
}
|
||||
valueTypes = append(valueTypes, c.i8ptrType) // closure
|
||||
deferFrameType := c.ctx.StructType(valueTypes, false)
|
||||
@@ -306,7 +273,7 @@ func (c *Compiler) emitRunDefers(frame *Frame) error {
|
||||
forwardParams := []llvm.Value{}
|
||||
zero := llvm.ConstInt(c.ctx.Int32Type(), 0, false)
|
||||
for i := 2; i < len(valueTypes); i++ {
|
||||
gep := c.builder.CreateGEP(deferFramePtr, []llvm.Value{zero, llvm.ConstInt(c.ctx.Int32Type(), uint64(i), false)}, "")
|
||||
gep := c.builder.CreateInBoundsGEP(deferFramePtr, []llvm.Value{zero, llvm.ConstInt(c.ctx.Int32Type(), uint64(i), false)}, "")
|
||||
forwardParam := c.builder.CreateLoad(gep, "param")
|
||||
forwardParams = append(forwardParams, forwardParam)
|
||||
}
|
||||
@@ -334,5 +301,4 @@ func (c *Compiler) emitRunDefers(frame *Frame) error {
|
||||
|
||||
// End of loop.
|
||||
c.builder.SetInsertPointAtEnd(end)
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -12,3 +12,7 @@ func (c *Compiler) makeError(pos token.Pos, msg string) types.Error {
|
||||
Msg: msg,
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Compiler) addError(pos token.Pos, msg string) {
|
||||
c.diagnostics = append(c.diagnostics, c.makeError(pos, msg))
|
||||
}
|
||||
|
||||
@@ -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,202 @@
|
||||
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 {
|
||||
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 := c.getFuncType(sig)
|
||||
funcValue := llvm.Undef(funcValueType)
|
||||
funcValue = c.builder.CreateInsertValue(funcValue, context, 0, "")
|
||||
funcValue = c.builder.CreateInsertValue(funcValue, funcValueScalar, 1, "")
|
||||
return funcValue
|
||||
}
|
||||
|
||||
// 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 := c.getRawFuncType(sig)
|
||||
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 {
|
||||
switch c.funcImplementation() {
|
||||
case funcValueDoubleword:
|
||||
rawPtr := c.getRawFuncType(typ)
|
||||
return c.ctx.StructType([]llvm.Type{c.i8ptrType, rawPtr}, false)
|
||||
case funcValueSwitch:
|
||||
return c.mod.GetTypeByName("runtime.funcValue")
|
||||
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 {
|
||||
// Get the return type.
|
||||
var returnType llvm.Type
|
||||
switch typ.Results().Len() {
|
||||
case 0:
|
||||
// No return values.
|
||||
returnType = c.ctx.VoidType()
|
||||
case 1:
|
||||
// Just one return value.
|
||||
returnType = c.getLLVMType(typ.Results().At(0).Type())
|
||||
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++ {
|
||||
members[i] = c.getLLVMType(typ.Results().At(i).Type())
|
||||
}
|
||||
returnType = c.ctx.StructType(members, false)
|
||||
}
|
||||
|
||||
// Get the parameter types.
|
||||
var paramTypes []llvm.Type
|
||||
if typ.Recv() != nil {
|
||||
recv := c.getLLVMType(typ.Recv().Type())
|
||||
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 := c.getLLVMType(typ.Params().At(i).Type())
|
||||
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)
|
||||
}
|
||||
|
||||
// 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, len(expr.Bindings))
|
||||
for i, binding := range expr.Bindings {
|
||||
// The context stores the bound variables.
|
||||
llvmBoundVar := c.getValue(frame, binding)
|
||||
boundVars[i] = llvmBoundVar
|
||||
}
|
||||
|
||||
// Store the bound variables in a single object, allocating it on the heap
|
||||
// if necessary.
|
||||
context := c.emitPointerPack(boundVars)
|
||||
|
||||
// Create the closure.
|
||||
return c.createFuncValue(f.LLVMFn, context, f.Signature), nil
|
||||
}
|
||||
@@ -0,0 +1,104 @@
|
||||
package compiler
|
||||
|
||||
import (
|
||||
"math/big"
|
||||
|
||||
"tinygo.org/x/go-llvm"
|
||||
)
|
||||
|
||||
func (c *Compiler) addGlobalsBitmap() {
|
||||
if c.mod.NamedGlobal("runtime.trackedGlobalsStart").IsNil() {
|
||||
return // nothing to do: no GC in use
|
||||
}
|
||||
|
||||
var trackedGlobals []llvm.Value
|
||||
var trackedGlobalTypes []llvm.Type
|
||||
for global := c.mod.FirstGlobal(); !global.IsNil(); global = llvm.NextGlobal(global) {
|
||||
if global.IsDeclaration() {
|
||||
continue
|
||||
}
|
||||
typ := global.Type().ElementType()
|
||||
ptrs := c.getPointerBitmap(typ, global.Name())
|
||||
if ptrs.BitLen() == 0 {
|
||||
continue
|
||||
}
|
||||
trackedGlobals = append(trackedGlobals, global)
|
||||
trackedGlobalTypes = append(trackedGlobalTypes, typ)
|
||||
}
|
||||
|
||||
//
|
||||
globalsBundleType := c.ctx.StructType(trackedGlobalTypes, false)
|
||||
globalsBundle := llvm.AddGlobal(c.mod, globalsBundleType, "tinygo.trackedGlobals")
|
||||
globalsBundle.SetLinkage(llvm.InternalLinkage)
|
||||
globalsBundle.SetUnnamedAddr(true)
|
||||
initializer := llvm.Undef(globalsBundleType)
|
||||
for i, global := range trackedGlobals {
|
||||
initializer = llvm.ConstInsertValue(initializer, global.Initializer(), []uint32{uint32(i)})
|
||||
gep := llvm.ConstGEP(globalsBundle, []llvm.Value{
|
||||
llvm.ConstInt(c.ctx.Int32Type(), 0, false),
|
||||
llvm.ConstInt(c.ctx.Int32Type(), uint64(i), false),
|
||||
})
|
||||
global.ReplaceAllUsesWith(gep)
|
||||
global.EraseFromParentAsGlobal()
|
||||
}
|
||||
globalsBundle.SetInitializer(initializer)
|
||||
|
||||
trackedGlobalsStart := llvm.ConstPtrToInt(globalsBundle, c.uintptrType)
|
||||
c.mod.NamedGlobal("runtime.trackedGlobalsStart").SetInitializer(trackedGlobalsStart)
|
||||
|
||||
alignment := c.targetData.PrefTypeAlignment(c.i8ptrType)
|
||||
trackedGlobalsLength := llvm.ConstInt(c.uintptrType, c.targetData.TypeAllocSize(globalsBundleType)/uint64(alignment), false)
|
||||
c.mod.NamedGlobal("runtime.trackedGlobalsLength").SetInitializer(trackedGlobalsLength)
|
||||
|
||||
bitmapBytes := c.getPointerBitmap(globalsBundleType, "globals bundle").Bytes()
|
||||
bitmapValues := make([]llvm.Value, len(bitmapBytes))
|
||||
for i, b := range bitmapBytes {
|
||||
bitmapValues[len(bitmapBytes)-i-1] = llvm.ConstInt(c.ctx.Int8Type(), uint64(b), false)
|
||||
}
|
||||
bitmapArray := llvm.ConstArray(llvm.ArrayType(c.ctx.Int8Type(), len(bitmapBytes)), bitmapValues)
|
||||
bitmapNew := llvm.AddGlobal(c.mod, bitmapArray.Type(), "runtime.trackedGlobalsBitmap.tmp")
|
||||
bitmapOld := c.mod.NamedGlobal("runtime.trackedGlobalsBitmap")
|
||||
bitmapOld.ReplaceAllUsesWith(bitmapNew)
|
||||
bitmapNew.SetInitializer(bitmapArray)
|
||||
bitmapNew.SetName("runtime.trackedGlobalsBitmap")
|
||||
}
|
||||
|
||||
func (c *Compiler) getPointerBitmap(typ llvm.Type, name string) *big.Int {
|
||||
alignment := c.targetData.PrefTypeAlignment(c.i8ptrType)
|
||||
switch typ.TypeKind() {
|
||||
case llvm.IntegerTypeKind, llvm.FloatTypeKind, llvm.DoubleTypeKind:
|
||||
return big.NewInt(0)
|
||||
case llvm.PointerTypeKind:
|
||||
return big.NewInt(1)
|
||||
case llvm.StructTypeKind:
|
||||
ptrs := big.NewInt(0)
|
||||
for i, subtyp := range typ.StructElementTypes() {
|
||||
subptrs := c.getPointerBitmap(subtyp, name)
|
||||
if subptrs.BitLen() == 0 {
|
||||
continue
|
||||
}
|
||||
offset := c.targetData.ElementOffset(typ, i)
|
||||
if offset%uint64(alignment) != 0 {
|
||||
panic("precise GC: global contains unaligned pointer: " + name)
|
||||
}
|
||||
subptrs.Lsh(subptrs, uint(offset)/uint(alignment))
|
||||
ptrs.Or(ptrs, subptrs)
|
||||
}
|
||||
return ptrs
|
||||
case llvm.ArrayTypeKind:
|
||||
subtyp := typ.ElementType()
|
||||
subptrs := c.getPointerBitmap(subtyp, name)
|
||||
ptrs := big.NewInt(0)
|
||||
if subptrs.BitLen() == 0 {
|
||||
return ptrs
|
||||
}
|
||||
elementSize := c.targetData.TypeAllocSize(subtyp)
|
||||
for i := 0; i < typ.ArrayLength(); i++ {
|
||||
ptrs.Lsh(ptrs, uint(elementSize)/uint(alignment))
|
||||
ptrs.Or(ptrs, subptrs)
|
||||
}
|
||||
return ptrs
|
||||
default:
|
||||
panic("unknown type kind of global: " + name)
|
||||
}
|
||||
}
|
||||
+118
-121
@@ -10,8 +10,8 @@ package compiler
|
||||
// go foo()
|
||||
// time.Sleep(2 * time.Second)
|
||||
// println("some other operation")
|
||||
// bar()
|
||||
// println("done")
|
||||
// i := bar()
|
||||
// println("done", *i)
|
||||
// }
|
||||
//
|
||||
// func foo() {
|
||||
@@ -21,9 +21,10 @@ package compiler
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// func bar() {
|
||||
// func bar() *int {
|
||||
// time.Sleep(time.Second)
|
||||
// println("blocking operation completed)
|
||||
// return new(int)
|
||||
// }
|
||||
//
|
||||
// It is transformed by the IR generator in compiler.go into the following
|
||||
@@ -34,8 +35,8 @@ package compiler
|
||||
// fn()
|
||||
// time.Sleep(2 * time.Second)
|
||||
// println("some other operation")
|
||||
// bar() // imagine an 'await' keyword in front of this call
|
||||
// println("done")
|
||||
// i := bar() // imagine an 'await' keyword in front of this call
|
||||
// println("done", *i)
|
||||
// }
|
||||
//
|
||||
// func foo() {
|
||||
@@ -45,9 +46,10 @@ package compiler
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// func bar() {
|
||||
// func bar() *int {
|
||||
// time.Sleep(time.Second)
|
||||
// println("blocking operation completed)
|
||||
// return new(int)
|
||||
// }
|
||||
//
|
||||
// The pass in this file transforms this code even further, to the following
|
||||
@@ -59,9 +61,11 @@ package compiler
|
||||
// runtime.sleepTask(hdl, 2 * time.Second) // ask the scheduler to re-activate this coroutine at the right time
|
||||
// llvm.suspend(hdl) // suspend point
|
||||
// println("some other operation")
|
||||
// var i *int // allocate space on the stack for the return value
|
||||
// runtime.setTaskPromisePtr(hdl, &i) // store return value alloca in our coroutine promise
|
||||
// bar(hdl) // await, pass a continuation (hdl) to bar
|
||||
// llvm.suspend(hdl) // suspend point, wait for the callee to re-activate
|
||||
// println("done")
|
||||
// println("done", *i)
|
||||
// runtime.activateTask(parent) // re-activate the parent (nop, there is no parent)
|
||||
// }
|
||||
//
|
||||
@@ -142,10 +146,9 @@ func (c *Compiler) LowerGoroutines() error {
|
||||
realMain.SetLinkage(llvm.InternalLinkage)
|
||||
c.mod.NamedFunction("runtime.alloc").SetLinkage(llvm.InternalLinkage)
|
||||
c.mod.NamedFunction("runtime.free").SetLinkage(llvm.InternalLinkage)
|
||||
c.mod.NamedFunction("runtime.chanSend").SetLinkage(llvm.InternalLinkage)
|
||||
c.mod.NamedFunction("runtime.chanRecv").SetLinkage(llvm.InternalLinkage)
|
||||
c.mod.NamedFunction("runtime.sleepTask").SetLinkage(llvm.InternalLinkage)
|
||||
c.mod.NamedFunction("runtime.activateTask").SetLinkage(llvm.InternalLinkage)
|
||||
c.mod.NamedFunction("runtime.setTaskPromisePtr").SetLinkage(llvm.InternalLinkage)
|
||||
c.mod.NamedFunction("runtime.getTaskPromisePtr").SetLinkage(llvm.InternalLinkage)
|
||||
c.mod.NamedFunction("runtime.scheduler").SetLinkage(llvm.InternalLinkage)
|
||||
|
||||
return nil
|
||||
@@ -170,13 +173,17 @@ func (c *Compiler) markAsyncFunctions() (needsScheduler bool, err error) {
|
||||
if !sleep.IsNil() {
|
||||
worklist = append(worklist, sleep)
|
||||
}
|
||||
chanSendStub := c.mod.NamedFunction("runtime.chanSendStub")
|
||||
if !chanSendStub.IsNil() {
|
||||
worklist = append(worklist, chanSendStub)
|
||||
deadlockStub := c.mod.NamedFunction("runtime.deadlockStub")
|
||||
if !deadlockStub.IsNil() {
|
||||
worklist = append(worklist, deadlockStub)
|
||||
}
|
||||
chanRecvStub := c.mod.NamedFunction("runtime.chanRecvStub")
|
||||
if !chanRecvStub.IsNil() {
|
||||
worklist = append(worklist, chanRecvStub)
|
||||
chanSend := c.mod.NamedFunction("runtime.chanSend")
|
||||
if !chanSend.IsNil() {
|
||||
worklist = append(worklist, chanSend)
|
||||
}
|
||||
chanRecv := c.mod.NamedFunction("runtime.chanRecv")
|
||||
if !chanRecv.IsNil() {
|
||||
worklist = append(worklist, chanRecv)
|
||||
}
|
||||
|
||||
if len(worklist) == 0 {
|
||||
@@ -274,9 +281,6 @@ func (c *Compiler) markAsyncFunctions() (needsScheduler bool, err error) {
|
||||
coroBeginType := llvm.FunctionType(c.i8ptrType, []llvm.Type{c.ctx.TokenType(), c.i8ptrType}, false)
|
||||
coroBeginFunc := llvm.AddFunction(c.mod, "llvm.coro.begin", coroBeginType)
|
||||
|
||||
coroPromiseType := llvm.FunctionType(c.i8ptrType, []llvm.Type{c.i8ptrType, c.ctx.Int32Type(), c.ctx.Int1Type()}, false)
|
||||
coroPromiseFunc := llvm.AddFunction(c.mod, "llvm.coro.promise", coroPromiseType)
|
||||
|
||||
coroSuspendType := llvm.FunctionType(c.ctx.Int8Type(), []llvm.Type{c.ctx.TokenType(), c.ctx.Int1Type()}, false)
|
||||
coroSuspendFunc := llvm.AddFunction(c.mod, "llvm.coro.suspend", coroSuspendType)
|
||||
|
||||
@@ -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 == chanSend || f == chanRecv {
|
||||
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 == chanSend || callee == chanRecv {
|
||||
continue
|
||||
}
|
||||
asyncCalls = append(asyncCalls, inst)
|
||||
@@ -343,10 +347,18 @@ func (c *Compiler) markAsyncFunctions() (needsScheduler bool, err error) {
|
||||
// Split this basic block.
|
||||
await := c.splitBasicBlock(inst, llvm.NextBasicBlock(c.builder.GetInsertBlock()), "task.await")
|
||||
|
||||
// Set task state to TASK_STATE_CALL.
|
||||
c.builder.SetInsertPointAtEnd(inst.InstructionParent())
|
||||
// Allocate space for the return value.
|
||||
var retvalAlloca llvm.Value
|
||||
if inst.Type().TypeKind() != llvm.VoidTypeKind {
|
||||
c.builder.SetInsertPointBefore(inst.InstructionParent().Parent().EntryBasicBlock().FirstInstruction())
|
||||
retvalAlloca = c.builder.CreateAlloca(inst.Type(), "coro.retvalAlloca")
|
||||
c.builder.SetInsertPointBefore(inst)
|
||||
data := c.builder.CreateBitCast(retvalAlloca, c.i8ptrType, "")
|
||||
c.createRuntimeCall("setTaskPromisePtr", []llvm.Value{frame.taskHandle, data}, "")
|
||||
}
|
||||
|
||||
// Suspend.
|
||||
c.builder.SetInsertPointAtEnd(inst.InstructionParent())
|
||||
continuePoint := c.builder.CreateCall(coroSuspendFunc, []llvm.Value{
|
||||
llvm.ConstNull(c.ctx.TokenType()),
|
||||
llvm.ConstInt(c.ctx.Int1Type(), 0, false),
|
||||
@@ -354,44 +366,63 @@ func (c *Compiler) markAsyncFunctions() (needsScheduler bool, err error) {
|
||||
sw := c.builder.CreateSwitch(continuePoint, frame.suspendBlock, 2)
|
||||
sw.AddCase(llvm.ConstInt(c.ctx.Int8Type(), 0, false), await)
|
||||
sw.AddCase(llvm.ConstInt(c.ctx.Int8Type(), 1, false), frame.cleanupBlock)
|
||||
|
||||
if inst.Type().TypeKind() != llvm.VoidTypeKind {
|
||||
// Load the return value from the alloca. The callee has
|
||||
// written the return value to it.
|
||||
c.builder.SetInsertPointBefore(await.FirstInstruction())
|
||||
retval := c.builder.CreateLoad(retvalAlloca, "coro.retval")
|
||||
inst.ReplaceAllUsesWith(retval)
|
||||
}
|
||||
}
|
||||
|
||||
// Replace return instructions with suspend points that should
|
||||
// reactivate the parent coroutine.
|
||||
for _, inst := range returns {
|
||||
if inst.OperandsCount() == 0 {
|
||||
// These properties were added by the functionattrs pass.
|
||||
// Remove them, because now we start using the parameter.
|
||||
// https://llvm.org/docs/Passes.html#functionattrs-deduce-function-attributes
|
||||
for _, kind := range []string{"nocapture", "readnone"} {
|
||||
kindID := llvm.AttributeKindID(kind)
|
||||
f.RemoveEnumAttributeAtIndex(f.ParamsCount(), kindID)
|
||||
}
|
||||
|
||||
// Reactivate the parent coroutine. This adds it back to
|
||||
// the run queue, so it is started again by the
|
||||
// scheduler when possible (possibly right after the
|
||||
// following suspend).
|
||||
c.builder.SetInsertPointBefore(inst)
|
||||
|
||||
parentHandle := f.LastParam()
|
||||
c.createRuntimeCall("activateTask", []llvm.Value{parentHandle}, "")
|
||||
|
||||
// Suspend this coroutine.
|
||||
// It would look like this is unnecessary, but if this
|
||||
// suspend point is left out, it leads to undefined
|
||||
// behavior somehow (with the unreachable instruction).
|
||||
continuePoint := c.builder.CreateCall(coroSuspendFunc, []llvm.Value{
|
||||
llvm.ConstNull(c.ctx.TokenType()),
|
||||
llvm.ConstInt(c.ctx.Int1Type(), 1, false),
|
||||
}, "ret")
|
||||
sw := c.builder.CreateSwitch(continuePoint, frame.suspendBlock, 2)
|
||||
sw.AddCase(llvm.ConstInt(c.ctx.Int8Type(), 0, false), frame.unreachableBlock)
|
||||
sw.AddCase(llvm.ConstInt(c.ctx.Int8Type(), 1, false), frame.cleanupBlock)
|
||||
inst.EraseFromParentAsInstruction()
|
||||
} else {
|
||||
panic("todo: return value from coroutine")
|
||||
// These properties were added by the functionattrs pass. Remove
|
||||
// them, because now we start using the parameter.
|
||||
// https://llvm.org/docs/Passes.html#functionattrs-deduce-function-attributes
|
||||
for _, kind := range []string{"nocapture", "readnone"} {
|
||||
kindID := llvm.AttributeKindID(kind)
|
||||
f.RemoveEnumAttributeAtIndex(f.ParamsCount(), kindID)
|
||||
}
|
||||
|
||||
c.builder.SetInsertPointBefore(inst)
|
||||
|
||||
parentHandle := f.LastParam()
|
||||
|
||||
// Store return values.
|
||||
switch inst.OperandsCount() {
|
||||
case 0:
|
||||
// Nothing to return.
|
||||
case 1:
|
||||
// Return this value by writing to the pointer stored in the
|
||||
// parent handle. The parent coroutine has made an alloca that
|
||||
// we can write to to store our return value.
|
||||
returnValuePtr := c.createRuntimeCall("getTaskPromisePtr", []llvm.Value{parentHandle}, "coro.parentData")
|
||||
alloca := c.builder.CreateBitCast(returnValuePtr, llvm.PointerType(inst.Operand(0).Type(), 0), "coro.parentAlloca")
|
||||
c.builder.CreateStore(inst.Operand(0), alloca)
|
||||
default:
|
||||
panic("unreachable")
|
||||
}
|
||||
|
||||
// Reactivate the parent coroutine. This adds it back to the run
|
||||
// queue, so it is started again by the scheduler when possible
|
||||
// (possibly right after the following suspend).
|
||||
c.createRuntimeCall("activateTask", []llvm.Value{parentHandle}, "")
|
||||
|
||||
// Suspend this coroutine.
|
||||
// It would look like this is unnecessary, but if this
|
||||
// suspend point is left out, it leads to undefined
|
||||
// behavior somehow (with the unreachable instruction).
|
||||
continuePoint := c.builder.CreateCall(coroSuspendFunc, []llvm.Value{
|
||||
llvm.ConstNull(c.ctx.TokenType()),
|
||||
llvm.ConstInt(c.ctx.Int1Type(), 1, false),
|
||||
}, "ret")
|
||||
sw := c.builder.CreateSwitch(continuePoint, frame.suspendBlock, 2)
|
||||
sw.AddCase(llvm.ConstInt(c.ctx.Int8Type(), 0, false), frame.unreachableBlock)
|
||||
sw.AddCase(llvm.ConstInt(c.ctx.Int8Type(), 1, false), frame.cleanupBlock)
|
||||
inst.EraseFromParentAsInstruction()
|
||||
}
|
||||
|
||||
// Coroutine cleanup. Free resources associated with this coroutine.
|
||||
@@ -416,6 +447,14 @@ func (c *Compiler) markAsyncFunctions() (needsScheduler bool, err error) {
|
||||
c.builder.CreateUnreachable()
|
||||
}
|
||||
|
||||
// Replace calls to runtime.getCoroutineCall with the coroutine of this
|
||||
// frame.
|
||||
for _, getCoroutineCall := range getUses(c.mod.NamedFunction("runtime.getCoroutine")) {
|
||||
frame := asyncFuncs[getCoroutineCall.InstructionParent().Parent()]
|
||||
getCoroutineCall.ReplaceAllUsesWith(frame.taskHandle)
|
||||
getCoroutineCall.EraseFromParentAsInstruction()
|
||||
}
|
||||
|
||||
// Transform calls to time.Sleep() into coroutine suspend points.
|
||||
for _, sleepCall := range getUses(sleep) {
|
||||
// sleepCall must be a call instruction.
|
||||
@@ -439,37 +478,31 @@ func (c *Compiler) markAsyncFunctions() (needsScheduler bool, err error) {
|
||||
sleepCall.EraseFromParentAsInstruction()
|
||||
}
|
||||
|
||||
// Transform calls to runtime.chanSendStub into channel send operations.
|
||||
for _, sendOp := range getUses(chanSendStub) {
|
||||
// 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.chanSend into channel send operations.
|
||||
for _, sendOp := range getUses(chanSend) {
|
||||
// sendOp must be a call instruction.
|
||||
frame := asyncFuncs[sendOp.InstructionParent().Parent()]
|
||||
|
||||
// Send the value over the channel, or block.
|
||||
sendOp.SetOperand(0, frame.taskHandle)
|
||||
sendOp.SetOperand(sendOp.OperandsCount()-1, c.mod.NamedFunction("runtime.chanSend"))
|
||||
|
||||
// Use taskState.data to store the value to send:
|
||||
// *(*valueType)(&coroutine.promise().data) = valueToSend
|
||||
// runtime.chanSend(coroutine, ch)
|
||||
bitcast := sendOp.Operand(2)
|
||||
valueAlloca := bitcast.Operand(0)
|
||||
c.builder.SetInsertPointBefore(valueAlloca)
|
||||
promiseType := c.mod.GetTypeByName("runtime.taskState")
|
||||
promiseRaw := c.builder.CreateCall(coroPromiseFunc, []llvm.Value{
|
||||
frame.taskHandle,
|
||||
llvm.ConstInt(c.ctx.Int32Type(), uint64(c.targetData.PrefTypeAlignment(promiseType)), false),
|
||||
llvm.ConstInt(c.ctx.Int1Type(), 0, false),
|
||||
}, "task.promise.raw")
|
||||
promise := c.builder.CreateBitCast(promiseRaw, llvm.PointerType(promiseType, 0), "task.promise")
|
||||
dataPtr := c.builder.CreateGEP(promise, []llvm.Value{
|
||||
llvm.ConstInt(c.ctx.Int32Type(), 0, false),
|
||||
llvm.ConstInt(c.ctx.Int32Type(), 2, false),
|
||||
}, "task.promise.data")
|
||||
sendOp.SetOperand(2, llvm.Undef(c.i8ptrType))
|
||||
valueAlloca.ReplaceAllUsesWith(c.builder.CreateBitCast(dataPtr, valueAlloca.Type(), ""))
|
||||
bitcast.EraseFromParentAsInstruction()
|
||||
valueAlloca.EraseFromParentAsInstruction()
|
||||
|
||||
// Yield to scheduler.
|
||||
c.builder.SetInsertPointBefore(llvm.NextInstruction(sendOp))
|
||||
continuePoint := c.builder.CreateCall(coroSuspendFunc, []llvm.Value{
|
||||
@@ -482,21 +515,11 @@ func (c *Compiler) markAsyncFunctions() (needsScheduler bool, err error) {
|
||||
sw.AddCase(llvm.ConstInt(c.ctx.Int8Type(), 1, false), frame.cleanupBlock)
|
||||
}
|
||||
|
||||
// Transform calls to runtime.chanRecvStub into channel receive operations.
|
||||
for _, recvOp := range getUses(chanRecvStub) {
|
||||
// Transform calls to runtime.chanRecv into channel receive operations.
|
||||
for _, recvOp := range getUses(chanRecv) {
|
||||
// recvOp must be a call instruction.
|
||||
frame := asyncFuncs[recvOp.InstructionParent().Parent()]
|
||||
|
||||
bitcast := recvOp.Operand(2)
|
||||
commaOk := recvOp.Operand(3)
|
||||
valueAlloca := bitcast.Operand(0)
|
||||
|
||||
// Receive the value over the channel, or block.
|
||||
recvOp.SetOperand(0, frame.taskHandle)
|
||||
recvOp.SetOperand(recvOp.OperandsCount()-1, c.mod.NamedFunction("runtime.chanRecv"))
|
||||
recvOp.SetOperand(2, llvm.Undef(c.i8ptrType))
|
||||
bitcast.EraseFromParentAsInstruction()
|
||||
|
||||
// Yield to scheduler.
|
||||
c.builder.SetInsertPointBefore(llvm.NextInstruction(recvOp))
|
||||
continuePoint := c.builder.CreateCall(coroSuspendFunc, []llvm.Value{
|
||||
@@ -508,32 +531,6 @@ func (c *Compiler) markAsyncFunctions() (needsScheduler bool, err error) {
|
||||
c.builder.SetInsertPointAtEnd(recvOp.InstructionParent())
|
||||
sw.AddCase(llvm.ConstInt(c.ctx.Int8Type(), 0, false), wakeup)
|
||||
sw.AddCase(llvm.ConstInt(c.ctx.Int8Type(), 1, false), frame.cleanupBlock)
|
||||
|
||||
// The value to receive is stored in taskState.data:
|
||||
// runtime.chanRecv(coroutine, ch)
|
||||
// promise := coroutine.promise()
|
||||
// valueReceived := *(*valueType)(&promise.data)
|
||||
// ok := promise.commaOk
|
||||
c.builder.SetInsertPointBefore(wakeup.FirstInstruction())
|
||||
promiseType := c.mod.GetTypeByName("runtime.taskState")
|
||||
promiseRaw := c.builder.CreateCall(coroPromiseFunc, []llvm.Value{
|
||||
frame.taskHandle,
|
||||
llvm.ConstInt(c.ctx.Int32Type(), uint64(c.targetData.PrefTypeAlignment(promiseType)), false),
|
||||
llvm.ConstInt(c.ctx.Int1Type(), 0, false),
|
||||
}, "task.promise.raw")
|
||||
promise := c.builder.CreateBitCast(promiseRaw, llvm.PointerType(promiseType, 0), "task.promise")
|
||||
dataPtr := c.builder.CreateGEP(promise, []llvm.Value{
|
||||
llvm.ConstInt(c.ctx.Int32Type(), 0, false),
|
||||
llvm.ConstInt(c.ctx.Int32Type(), 2, false),
|
||||
}, "task.promise.data")
|
||||
valueAlloca.ReplaceAllUsesWith(c.builder.CreateBitCast(dataPtr, valueAlloca.Type(), ""))
|
||||
valueAlloca.EraseFromParentAsInstruction()
|
||||
commaOkPtr := c.builder.CreateGEP(promise, []llvm.Value{
|
||||
llvm.ConstInt(c.ctx.Int32Type(), 0, false),
|
||||
llvm.ConstInt(c.ctx.Int32Type(), 1, false),
|
||||
}, "task.promise.comma-ok")
|
||||
commaOk.ReplaceAllUsesWith(commaOkPtr)
|
||||
recvOp.SetOperand(3, llvm.Undef(commaOk.Type()))
|
||||
}
|
||||
|
||||
return true, c.lowerMakeGoroutineCalls()
|
||||
|
||||
@@ -0,0 +1,155 @@
|
||||
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())
|
||||
registers[key] = c.getValue(frame, r.Value.(*ssa.MakeInterface).X)
|
||||
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 := c.getValue(frame, arg)
|
||||
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
|
||||
}
|
||||
+96
-103
@@ -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
|
||||
@@ -104,15 +100,6 @@ func (t *typeInfo) getMethod(signature *signatureInfo) *methodInfo {
|
||||
panic("could not find method")
|
||||
}
|
||||
|
||||
// id returns the fully-qualified type name including import path, removing the
|
||||
// $type suffix.
|
||||
func (t *typeInfo) id() string {
|
||||
if !strings.HasSuffix(t.name, "$type") {
|
||||
panic("concrete type does not have $type suffix: " + t.name)
|
||||
}
|
||||
return t.name[:len(t.name)-len("$type")]
|
||||
}
|
||||
|
||||
// typeInfoSlice implements sort.Slice, sorting the most commonly used types
|
||||
// first.
|
||||
type typeInfoSlice []*typeInfo
|
||||
@@ -175,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
|
||||
@@ -203,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++
|
||||
}
|
||||
|
||||
@@ -295,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 {
|
||||
@@ -313,7 +298,7 @@ func (p *lowerInterfacesPass) run() {
|
||||
// interface value should already have returned false.
|
||||
// Replace the function pointer with undef (which will then be
|
||||
// called), indicating to the optimizer this code is unreachable.
|
||||
use.ReplaceAllUsesWith(llvm.Undef(p.i8ptrType))
|
||||
use.ReplaceAllUsesWith(llvm.Undef(p.uintptrType))
|
||||
use.EraseFromParentAsInstruction()
|
||||
} else if len(itf.types) == 1 {
|
||||
// There is only one implementation of the given type.
|
||||
@@ -323,12 +308,12 @@ func (p *lowerInterfacesPass) run() {
|
||||
// There are multiple types implementing this interface, thus there
|
||||
// are multiple possible functions to call. Delegate calling the
|
||||
// right function to a special wrapper function.
|
||||
bitcasts := getUses(use)
|
||||
if len(bitcasts) != 1 || bitcasts[0].IsABitCastInst().IsNil() {
|
||||
panic("expected exactly one bitcast use of runtime.interfaceMethod")
|
||||
inttoptrs := getUses(use)
|
||||
if len(inttoptrs) != 1 || inttoptrs[0].IsAIntToPtrInst().IsNil() {
|
||||
panic("expected exactly one inttoptr use of runtime.interfaceMethod")
|
||||
}
|
||||
bitcast := bitcasts[0]
|
||||
calls := getUses(bitcast)
|
||||
inttoptr := inttoptrs[0]
|
||||
calls := getUses(inttoptr)
|
||||
if len(calls) != 1 || calls[0].IsACallInst().IsNil() {
|
||||
panic("expected exactly one call use of runtime.interfaceMethod")
|
||||
}
|
||||
@@ -349,14 +334,14 @@ func (p *lowerInterfacesPass) run() {
|
||||
// call, after selecting the right concrete type.
|
||||
redirector := p.getInterfaceMethodFunc(itf, signature, call.Type(), paramTypes)
|
||||
|
||||
// Replace the old lookup/bitcast/call with the new call.
|
||||
// Replace the old lookup/inttoptr/call with the new call.
|
||||
p.builder.SetInsertPointBefore(call)
|
||||
retval := p.builder.CreateCall(redirector, params, "")
|
||||
if retval.Type().TypeKind() != llvm.VoidTypeKind {
|
||||
call.ReplaceAllUsesWith(retval)
|
||||
}
|
||||
call.EraseFromParentAsInstruction()
|
||||
bitcast.EraseFromParentAsInstruction()
|
||||
inttoptr.EraseFromParentAsInstruction()
|
||||
use.EraseFromParentAsInstruction()
|
||||
}
|
||||
}
|
||||
@@ -365,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()]
|
||||
@@ -415,7 +386,7 @@ func (p *lowerInterfacesPass) run() {
|
||||
for _, t := range p.types {
|
||||
typeSlice = append(typeSlice, t)
|
||||
}
|
||||
sort.Sort(typeSlice)
|
||||
sort.Sort(sort.Reverse(typeSlice))
|
||||
|
||||
// A type code must fit in 16 bits.
|
||||
if len(typeSlice) >= 1<<16 {
|
||||
@@ -423,16 +394,16 @@ func (p *lowerInterfacesPass) run() {
|
||||
}
|
||||
|
||||
// Assign a type code for each type.
|
||||
for i, t := range typeSlice {
|
||||
t.num = uint64(i + 1)
|
||||
}
|
||||
p.assignTypeCodes(typeSlice)
|
||||
|
||||
// Replace each call to runtime.makeInterface with the constant type code.
|
||||
for _, use := range makeInterfaceUses {
|
||||
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
|
||||
@@ -469,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 {
|
||||
@@ -485,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.
|
||||
@@ -553,22 +517,51 @@ func (p *lowerInterfacesPass) getSignature(name string) *signatureInfo {
|
||||
return p.signatures[name]
|
||||
}
|
||||
|
||||
// replaceInvokeWithCall replaces a runtime.interfaceMethod + bitcast with a
|
||||
// replaceInvokeWithCall replaces a runtime.interfaceMethod + inttoptr with a
|
||||
// concrete method. This can be done when only one type implements the
|
||||
// interface.
|
||||
func (p *lowerInterfacesPass) replaceInvokeWithCall(use llvm.Value, typ *typeInfo, signature *signatureInfo) {
|
||||
bitcasts := getUses(use)
|
||||
if len(bitcasts) != 1 || bitcasts[0].IsABitCastInst().IsNil() {
|
||||
panic("expected exactly one bitcast use of runtime.interfaceMethod")
|
||||
inttoptrs := getUses(use)
|
||||
if len(inttoptrs) != 1 || inttoptrs[0].IsAIntToPtrInst().IsNil() {
|
||||
panic("expected exactly one inttoptr use of runtime.interfaceMethod")
|
||||
}
|
||||
bitcast := bitcasts[0]
|
||||
inttoptr := inttoptrs[0]
|
||||
function := typ.getMethod(signature).function
|
||||
if bitcast.Type() != function.Type() {
|
||||
p.builder.SetInsertPointBefore(use)
|
||||
function = p.builder.CreateBitCast(function, bitcast.Type(), "")
|
||||
if inttoptr.Type() == function.Type() {
|
||||
// Easy case: the types are the same. Simply replace the inttoptr
|
||||
// result (which is directly called) with the actual function.
|
||||
inttoptr.ReplaceAllUsesWith(function)
|
||||
} else {
|
||||
// Harder case: the type is not actually the same. Go through each call
|
||||
// (of which there should be only one), extract the receiver params for
|
||||
// this call and replace the call with a direct call to the target
|
||||
// function.
|
||||
for _, call := range getUses(inttoptr) {
|
||||
if call.IsACallInst().IsNil() || call.CalledValue() != inttoptr {
|
||||
panic("expected the inttoptr to be called as a method, this is not a method call")
|
||||
}
|
||||
operands := make([]llvm.Value, call.OperandsCount()-1)
|
||||
for i := range operands {
|
||||
operands[i] = call.Operand(i)
|
||||
}
|
||||
paramTypes := function.Type().ElementType().ParamTypes()
|
||||
receiverParamTypes := paramTypes[:len(paramTypes)-(len(operands)-1)]
|
||||
methodParamTypes := paramTypes[len(paramTypes)-(len(operands)-1):]
|
||||
for i, methodParamType := range methodParamTypes {
|
||||
if methodParamType != operands[i+1].Type() {
|
||||
panic("expected method call param type and function param type to be the same")
|
||||
}
|
||||
}
|
||||
p.builder.SetInsertPointBefore(call)
|
||||
receiverParams := p.emitPointerUnpack(operands[0], receiverParamTypes)
|
||||
result := p.builder.CreateCall(function, append(receiverParams, operands[1:]...), "")
|
||||
if result.Type().TypeKind() != llvm.VoidTypeKind {
|
||||
call.ReplaceAllUsesWith(result)
|
||||
}
|
||||
call.EraseFromParentAsInstruction()
|
||||
}
|
||||
}
|
||||
bitcast.ReplaceAllUsesWith(function)
|
||||
bitcast.EraseFromParentAsInstruction()
|
||||
inttoptr.EraseFromParentAsInstruction()
|
||||
use.EraseFromParentAsInstruction()
|
||||
}
|
||||
|
||||
@@ -691,7 +684,7 @@ func (p *lowerInterfacesPass) createInterfaceMethodFunc(itf *interfaceInfo, sign
|
||||
|
||||
// Define all possible functions that can be called.
|
||||
for _, typ := range itf.types {
|
||||
bb := llvm.AddBasicBlock(fn, typ.id())
|
||||
bb := llvm.AddBasicBlock(fn, typ.name)
|
||||
sw.AddCase(llvm.ConstInt(p.uintptrType, typ.num, false), bb)
|
||||
|
||||
// The function we will redirect to when the interface has this type.
|
||||
|
||||
+140
-159
@@ -8,6 +8,8 @@ package compiler
|
||||
import (
|
||||
"go/token"
|
||||
"go/types"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/tinygo-org/tinygo/ir"
|
||||
"golang.org/x/tools/go/ssa"
|
||||
@@ -20,87 +22,148 @@ import (
|
||||
// value field.
|
||||
//
|
||||
// An interface value is a {typecode, value} tuple, or {i16, i8*} to be exact.
|
||||
func (c *Compiler) parseMakeInterface(val llvm.Value, typ types.Type, global string, pos token.Pos) (llvm.Value, error) {
|
||||
var itfValue llvm.Value
|
||||
size := c.targetData.TypeAllocSize(val.Type())
|
||||
if size > c.targetData.TypeAllocSize(c.i8ptrType) {
|
||||
if global != "" {
|
||||
// Allocate in a global variable.
|
||||
global := llvm.AddGlobal(c.mod, val.Type(), global+"$itfvalue")
|
||||
global.SetInitializer(val)
|
||||
global.SetLinkage(llvm.InternalLinkage)
|
||||
global.SetGlobalConstant(true)
|
||||
zero := llvm.ConstInt(c.ctx.Int32Type(), 0, false)
|
||||
itfValueRaw := llvm.ConstInBoundsGEP(global, []llvm.Value{zero, zero})
|
||||
itfValue = llvm.ConstBitCast(itfValueRaw, c.i8ptrType)
|
||||
} else {
|
||||
// Allocate on the heap and put a pointer in the interface.
|
||||
// TODO: escape analysis.
|
||||
sizeValue := llvm.ConstInt(c.uintptrType, size, false)
|
||||
alloc := c.createRuntimeCall("alloc", []llvm.Value{sizeValue}, "makeinterface.alloc")
|
||||
itfValueCast := c.builder.CreateBitCast(alloc, llvm.PointerType(val.Type(), 0), "makeinterface.cast.value")
|
||||
c.builder.CreateStore(val, itfValueCast)
|
||||
itfValue = c.builder.CreateBitCast(itfValueCast, c.i8ptrType, "makeinterface.cast.i8ptr")
|
||||
}
|
||||
} else if size == 0 {
|
||||
itfValue = llvm.ConstPointerNull(c.i8ptrType)
|
||||
} else {
|
||||
// Directly place the value in the interface.
|
||||
switch val.Type().TypeKind() {
|
||||
case llvm.IntegerTypeKind:
|
||||
itfValue = c.builder.CreateIntToPtr(val, c.i8ptrType, "makeinterface.cast.int")
|
||||
case llvm.PointerTypeKind:
|
||||
itfValue = c.builder.CreateBitCast(val, c.i8ptrType, "makeinterface.cast.ptr")
|
||||
case llvm.StructTypeKind:
|
||||
// A bitcast would be useful here, but bitcast doesn't allow
|
||||
// aggregate types. So we'll bitcast it using an alloca.
|
||||
// Hopefully this will get optimized away.
|
||||
mem := c.builder.CreateAlloca(c.i8ptrType, "makeinterface.cast.struct")
|
||||
memStructPtr := c.builder.CreateBitCast(mem, llvm.PointerType(val.Type(), 0), "makeinterface.cast.struct.cast")
|
||||
c.builder.CreateStore(val, memStructPtr)
|
||||
itfValue = c.builder.CreateLoad(mem, "makeinterface.cast.load")
|
||||
default:
|
||||
return llvm.Value{}, c.makeError(pos, "todo: makeinterface: cast small type to i8*")
|
||||
}
|
||||
}
|
||||
func (c *Compiler) parseMakeInterface(val llvm.Value, typ types.Type, pos token.Pos) llvm.Value {
|
||||
itfValue := c.emitPointerPack([]llvm.Value{val})
|
||||
itfTypeCodeGlobal := c.getTypeCode(typ)
|
||||
itfMethodSetGlobal, err := c.getTypeMethodSet(typ)
|
||||
if err != nil {
|
||||
return llvm.Value{}, nil
|
||||
itfMethodSetGlobal := c.getTypeMethodSet(typ)
|
||||
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.createRuntimeCall("makeInterface", []llvm.Value{itfTypeCodeGlobal, itfMethodSetGlobal}, "makeinterface.typecode")
|
||||
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, "")
|
||||
return itf, nil
|
||||
return itf
|
||||
}
|
||||
|
||||
// getTypeCode returns a reference to a type code.
|
||||
// It returns a pointer to an external global which should be replaced with the
|
||||
// real type in the interface lowering pass.
|
||||
func (c *Compiler) getTypeCode(typ types.Type) llvm.Value {
|
||||
global := c.mod.NamedGlobal(typ.String() + "$type")
|
||||
globalName := "type:" + getTypeCodeName(typ)
|
||||
global := c.mod.NamedGlobal(globalName)
|
||||
if global.IsNil() {
|
||||
global = llvm.AddGlobal(c.mod, c.ctx.Int8Type(), typ.String()+"$type")
|
||||
global = llvm.AddGlobal(c.mod, c.mod.GetTypeByName("runtime.typecodeID"), globalName)
|
||||
global.SetGlobalConstant(true)
|
||||
}
|
||||
return global
|
||||
}
|
||||
|
||||
// getTypeCodeName returns a name for this type that can be used in the
|
||||
// interface lowering pass to assign type codes as expected by the reflect
|
||||
// package. See getTypeCodeNum.
|
||||
func getTypeCodeName(t types.Type) string {
|
||||
name := ""
|
||||
if named, ok := t.(*types.Named); ok {
|
||||
name = "~" + named.String() + ":"
|
||||
t = t.Underlying()
|
||||
}
|
||||
switch t := t.(type) {
|
||||
case *types.Array:
|
||||
return "array:" + name + strconv.FormatInt(t.Len(), 10) + ":" + getTypeCodeName(t.Elem())
|
||||
case *types.Basic:
|
||||
var kind string
|
||||
switch t.Kind() {
|
||||
case types.Bool:
|
||||
kind = "bool"
|
||||
case types.Int:
|
||||
kind = "int"
|
||||
case types.Int8:
|
||||
kind = "int8"
|
||||
case types.Int16:
|
||||
kind = "int16"
|
||||
case types.Int32:
|
||||
kind = "int32"
|
||||
case types.Int64:
|
||||
kind = "int64"
|
||||
case types.Uint:
|
||||
kind = "uint"
|
||||
case types.Uint8:
|
||||
kind = "uint8"
|
||||
case types.Uint16:
|
||||
kind = "uint16"
|
||||
case types.Uint32:
|
||||
kind = "uint32"
|
||||
case types.Uint64:
|
||||
kind = "uint64"
|
||||
case types.Uintptr:
|
||||
kind = "uintptr"
|
||||
case types.Float32:
|
||||
kind = "float32"
|
||||
case types.Float64:
|
||||
kind = "float64"
|
||||
case types.Complex64:
|
||||
kind = "complex64"
|
||||
case types.Complex128:
|
||||
kind = "complex128"
|
||||
case types.String:
|
||||
kind = "string"
|
||||
case types.UnsafePointer:
|
||||
kind = "unsafeptr"
|
||||
default:
|
||||
panic("unknown basic type: " + t.Name())
|
||||
}
|
||||
return "basic:" + name + kind
|
||||
case *types.Chan:
|
||||
return "chan:" + name + getTypeCodeName(t.Elem())
|
||||
case *types.Interface:
|
||||
methods := make([]string, t.NumMethods())
|
||||
for i := 0; i < t.NumMethods(); i++ {
|
||||
methods[i] = getTypeCodeName(t.Method(i).Type())
|
||||
}
|
||||
return "interface:" + name + "{" + strings.Join(methods, ",") + "}"
|
||||
case *types.Map:
|
||||
keyType := getTypeCodeName(t.Key())
|
||||
elemType := getTypeCodeName(t.Elem())
|
||||
return "map:" + name + "{" + keyType + "," + elemType + "}"
|
||||
case *types.Pointer:
|
||||
return "pointer:" + name + getTypeCodeName(t.Elem())
|
||||
case *types.Signature:
|
||||
params := make([]string, t.Params().Len())
|
||||
for i := 0; i < t.Params().Len(); i++ {
|
||||
params[i] = getTypeCodeName(t.Params().At(i).Type())
|
||||
}
|
||||
results := make([]string, t.Results().Len())
|
||||
for i := 0; i < t.Results().Len(); i++ {
|
||||
results[i] = getTypeCodeName(t.Results().At(i).Type())
|
||||
}
|
||||
return "func:" + name + "{" + strings.Join(params, ",") + "}{" + strings.Join(results, ",") + "}"
|
||||
case *types.Slice:
|
||||
return "slice:" + name + getTypeCodeName(t.Elem())
|
||||
case *types.Struct:
|
||||
elems := make([]string, t.NumFields())
|
||||
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())
|
||||
}
|
||||
return "struct:" + name + "{" + strings.Join(elems, ",") + "}"
|
||||
default:
|
||||
panic("unknown type: " + t.String())
|
||||
}
|
||||
}
|
||||
|
||||
// getTypeMethodSet returns a reference (GEP) to a global method set. This
|
||||
// method set should be unreferenced after the interface lowering pass.
|
||||
func (c *Compiler) getTypeMethodSet(typ types.Type) (llvm.Value, error) {
|
||||
func (c *Compiler) getTypeMethodSet(typ types.Type) llvm.Value {
|
||||
global := c.mod.NamedGlobal(typ.String() + "$methodset")
|
||||
zero := llvm.ConstInt(c.ctx.Int32Type(), 0, false)
|
||||
if !global.IsNil() {
|
||||
// the method set already exists
|
||||
return llvm.ConstGEP(global, []llvm.Value{zero, zero}), nil
|
||||
return llvm.ConstGEP(global, []llvm.Value{zero, zero})
|
||||
}
|
||||
|
||||
ms := c.ir.Program.MethodSets.MethodSet(typ)
|
||||
if ms.Len() == 0 {
|
||||
// no methods, so can leave that one out
|
||||
return llvm.ConstPointerNull(llvm.PointerType(c.mod.GetTypeByName("runtime.interfaceMethodInfo"), 0)), nil
|
||||
return llvm.ConstPointerNull(llvm.PointerType(c.mod.GetTypeByName("runtime.interfaceMethodInfo"), 0))
|
||||
}
|
||||
|
||||
methods := make([]llvm.Value, ms.Len())
|
||||
@@ -113,13 +176,10 @@ func (c *Compiler) getTypeMethodSet(typ types.Type) (llvm.Value, error) {
|
||||
// compiler error, so panic
|
||||
panic("cannot find function: " + f.LinkName())
|
||||
}
|
||||
fn, err := c.getInterfaceInvokeWrapper(f)
|
||||
if err != nil {
|
||||
return llvm.Value{}, err
|
||||
}
|
||||
fn := c.getInterfaceInvokeWrapper(f)
|
||||
methodInfo := llvm.ConstNamedStruct(interfaceMethodInfoType, []llvm.Value{
|
||||
signatureGlobal,
|
||||
llvm.ConstBitCast(fn, c.i8ptrType),
|
||||
llvm.ConstPtrToInt(fn, c.uintptrType),
|
||||
})
|
||||
methods[i] = methodInfo
|
||||
}
|
||||
@@ -129,7 +189,7 @@ func (c *Compiler) getTypeMethodSet(typ types.Type) (llvm.Value, error) {
|
||||
global.SetInitializer(value)
|
||||
global.SetGlobalConstant(true)
|
||||
global.SetLinkage(llvm.PrivateLinkage)
|
||||
return llvm.ConstGEP(global, []llvm.Value{zero, zero}), nil
|
||||
return llvm.ConstGEP(global, []llvm.Value{zero, zero})
|
||||
}
|
||||
|
||||
// getInterfaceMethodSet returns a global variable with the method set of the
|
||||
@@ -178,19 +238,9 @@ func (c *Compiler) getMethodSignature(method *types.Func) llvm.Value {
|
||||
//
|
||||
// Type asserts on concrete types are trivial: just compare type numbers. Type
|
||||
// asserts on interfaces are more difficult, see the comments in the function.
|
||||
func (c *Compiler) parseTypeAssert(frame *Frame, expr *ssa.TypeAssert) (llvm.Value, error) {
|
||||
itf, err := c.parseExpr(frame, expr.X)
|
||||
if err != nil {
|
||||
return llvm.Value{}, err
|
||||
}
|
||||
assertedType, err := c.getLLVMType(expr.AssertedType)
|
||||
if err != nil {
|
||||
return llvm.Value{}, err
|
||||
}
|
||||
valueNil, err := c.getZeroValue(assertedType)
|
||||
if err != nil {
|
||||
return llvm.Value{}, err
|
||||
}
|
||||
func (c *Compiler) parseTypeAssert(frame *Frame, expr *ssa.TypeAssert) llvm.Value {
|
||||
itf := c.getValue(frame, expr.X)
|
||||
assertedType := c.getLLVMType(expr.AssertedType)
|
||||
|
||||
actualTypeNum := c.builder.CreateExtractValue(itf, 0, "interface.type")
|
||||
commaOk := llvm.Value{}
|
||||
@@ -243,71 +293,35 @@ func (c *Compiler) parseTypeAssert(frame *Frame, expr *ssa.TypeAssert) (llvm.Val
|
||||
// Type assert on concrete type. Extract the underlying type from
|
||||
// the interface (but only after checking it matches).
|
||||
valuePtr := c.builder.CreateExtractValue(itf, 1, "typeassert.value.ptr")
|
||||
size := c.targetData.TypeAllocSize(assertedType)
|
||||
if size > c.targetData.TypeAllocSize(c.i8ptrType) {
|
||||
// Value was stored in an allocated buffer, load it from there.
|
||||
valuePtrCast := c.builder.CreateBitCast(valuePtr, llvm.PointerType(assertedType, 0), "")
|
||||
valueOk = c.builder.CreateLoad(valuePtrCast, "typeassert.value.ok")
|
||||
} else if size == 0 {
|
||||
valueOk, err = c.getZeroValue(assertedType)
|
||||
if err != nil {
|
||||
return llvm.Value{}, err
|
||||
}
|
||||
} else {
|
||||
// Value was stored directly in the interface.
|
||||
switch assertedType.TypeKind() {
|
||||
case llvm.IntegerTypeKind:
|
||||
valueOk = c.builder.CreatePtrToInt(valuePtr, assertedType, "typeassert.value.ok")
|
||||
case llvm.PointerTypeKind:
|
||||
valueOk = c.builder.CreateBitCast(valuePtr, assertedType, "typeassert.value.ok")
|
||||
default: // struct, float, etc.
|
||||
// A bitcast would be useful here, but bitcast doesn't allow
|
||||
// aggregate types. So we'll bitcast it using an alloca.
|
||||
// Hopefully this will get optimized away.
|
||||
mem := c.builder.CreateAlloca(c.i8ptrType, "")
|
||||
c.builder.CreateStore(valuePtr, mem)
|
||||
memCast := c.builder.CreateBitCast(mem, llvm.PointerType(assertedType, 0), "")
|
||||
valueOk = c.builder.CreateLoad(memCast, "typeassert.value.ok")
|
||||
}
|
||||
}
|
||||
valueOk = c.emitPointerUnpack(valuePtr, []llvm.Type{assertedType})[0]
|
||||
}
|
||||
c.builder.CreateBr(nextBlock)
|
||||
|
||||
// Continue after the if statement.
|
||||
c.builder.SetInsertPointAtEnd(nextBlock)
|
||||
phi := c.builder.CreatePHI(assertedType, "typeassert.value")
|
||||
phi.AddIncoming([]llvm.Value{valueNil, valueOk}, []llvm.BasicBlock{prevBlock, okBlock})
|
||||
phi.AddIncoming([]llvm.Value{c.getZeroValue(assertedType), valueOk}, []llvm.BasicBlock{prevBlock, okBlock})
|
||||
|
||||
if expr.CommaOk {
|
||||
tuple := c.ctx.ConstStruct([]llvm.Value{llvm.Undef(assertedType), llvm.Undef(c.ctx.Int1Type())}, false) // create empty tuple
|
||||
tuple = c.builder.CreateInsertValue(tuple, phi, 0, "") // insert value
|
||||
tuple = c.builder.CreateInsertValue(tuple, commaOk, 1, "") // insert 'comma ok' boolean
|
||||
return tuple, nil
|
||||
return tuple
|
||||
} else {
|
||||
// This is kind of dirty as the branch above becomes mostly useless,
|
||||
// but hopefully this gets optimized away.
|
||||
c.createRuntimeCall("interfaceTypeAssert", []llvm.Value{commaOk}, "")
|
||||
return phi, nil
|
||||
return phi
|
||||
}
|
||||
}
|
||||
|
||||
// getInvokeCall creates and returns the function pointer and parameters of an
|
||||
// interface call. It can be used in a call or defer instruction.
|
||||
func (c *Compiler) getInvokeCall(frame *Frame, instr *ssa.CallCommon) (llvm.Value, []llvm.Value, error) {
|
||||
func (c *Compiler) getInvokeCall(frame *Frame, instr *ssa.CallCommon) (llvm.Value, []llvm.Value) {
|
||||
// Call an interface method with dynamic dispatch.
|
||||
itf, err := c.parseExpr(frame, instr.Value) // interface
|
||||
if err != nil {
|
||||
return llvm.Value{}, nil, err
|
||||
}
|
||||
itf := c.getValue(frame, instr.Value) // interface
|
||||
|
||||
llvmFnType, err := c.getLLVMType(instr.Method.Type())
|
||||
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]
|
||||
llvmFnType := c.getRawFuncType(instr.Method.Type().(*types.Signature))
|
||||
|
||||
typecode := c.builder.CreateExtractValue(itf, 0, "invoke.typecode")
|
||||
values := []llvm.Value{
|
||||
@@ -316,16 +330,12 @@ func (c *Compiler) getInvokeCall(frame *Frame, instr *ssa.CallCommon) (llvm.Valu
|
||||
c.getMethodSignature(instr.Method),
|
||||
}
|
||||
fn := c.createRuntimeCall("interfaceMethod", values, "invoke.func")
|
||||
fnCast := c.builder.CreateBitCast(fn, llvmFnType, "invoke.func.cast")
|
||||
fnCast := c.builder.CreateIntToPtr(fn, llvmFnType, "invoke.func.cast")
|
||||
receiverValue := c.builder.CreateExtractValue(itf, 1, "invoke.func.receiver")
|
||||
|
||||
args := []llvm.Value{receiverValue}
|
||||
for _, arg := range instr.Args {
|
||||
val, err := c.parseExpr(frame, arg)
|
||||
if err != nil {
|
||||
return llvm.Value{}, nil, err
|
||||
}
|
||||
args = append(args, val)
|
||||
args = append(args, c.getValue(frame, arg))
|
||||
}
|
||||
// Add the context parameter. An interface call never takes a context but we
|
||||
// have to supply the parameter anyway.
|
||||
@@ -333,7 +343,7 @@ func (c *Compiler) getInvokeCall(frame *Frame, instr *ssa.CallCommon) (llvm.Valu
|
||||
// Add the parent goroutine handle.
|
||||
args = append(args, llvm.Undef(c.i8ptrType))
|
||||
|
||||
return fnCast, args, nil
|
||||
return fnCast, args
|
||||
}
|
||||
|
||||
// interfaceInvokeWrapper keeps some state between getInterfaceInvokeWrapper and
|
||||
@@ -349,19 +359,16 @@ type interfaceInvokeWrapper struct {
|
||||
// the underlying value, dereferences it, and calls the real method. This
|
||||
// wrapper is only needed when the interface value actually doesn't fit in a
|
||||
// pointer and a pointer to the value must be created.
|
||||
func (c *Compiler) getInterfaceInvokeWrapper(f *ir.Function) (llvm.Value, error) {
|
||||
func (c *Compiler) getInterfaceInvokeWrapper(f *ir.Function) llvm.Value {
|
||||
wrapperName := f.LinkName() + "$invoke"
|
||||
wrapper := c.mod.NamedFunction(wrapperName)
|
||||
if !wrapper.IsNil() {
|
||||
// Wrapper already created. Return it directly.
|
||||
return wrapper, nil
|
||||
return wrapper
|
||||
}
|
||||
|
||||
// Get the expanded receiver type.
|
||||
receiverType, err := c.getLLVMType(f.Params[0].Type())
|
||||
if err != nil {
|
||||
return llvm.Value{}, err
|
||||
}
|
||||
receiverType := c.getLLVMType(f.Params[0].Type())
|
||||
expandedReceiverType := c.expandFormalParamType(receiverType)
|
||||
|
||||
// Does this method even need any wrapping?
|
||||
@@ -370,7 +377,7 @@ func (c *Compiler) getInterfaceInvokeWrapper(f *ir.Function) (llvm.Value, error)
|
||||
// Casting a function signature to a different signature and calling it
|
||||
// with a receiver pointer bitcasted to *i8 (as done in calls on an
|
||||
// interface) is hopefully a safe (defined) operation.
|
||||
return f.LLVMFn, nil
|
||||
return f.LLVMFn
|
||||
}
|
||||
|
||||
// create wrapper function
|
||||
@@ -383,12 +390,12 @@ func (c *Compiler) getInterfaceInvokeWrapper(f *ir.Function) (llvm.Value, error)
|
||||
wrapper: wrapper,
|
||||
receiverType: receiverType,
|
||||
})
|
||||
return wrapper, nil
|
||||
return wrapper
|
||||
}
|
||||
|
||||
// createInterfaceInvokeWrapper finishes the work of getInterfaceInvokeWrapper,
|
||||
// see that function for details.
|
||||
func (c *Compiler) createInterfaceInvokeWrapper(state interfaceInvokeWrapper) error {
|
||||
func (c *Compiler) createInterfaceInvokeWrapper(state interfaceInvokeWrapper) {
|
||||
wrapper := state.wrapper
|
||||
fn := state.fn
|
||||
receiverType := state.receiverType
|
||||
@@ -398,10 +405,7 @@ func (c *Compiler) createInterfaceInvokeWrapper(state interfaceInvokeWrapper) er
|
||||
// add debug info if needed
|
||||
if c.Debug {
|
||||
pos := c.ir.Program.Fset.Position(fn.Pos())
|
||||
difunc, err := c.attachDebugInfoRaw(fn, wrapper, "$invoke", pos.Filename, pos.Line)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
difunc := c.attachDebugInfoRaw(fn, wrapper, "$invoke", pos.Filename, pos.Line)
|
||||
c.builder.SetCurrentDebugLocation(uint(pos.Line), uint(pos.Column), difunc, llvm.Metadata{})
|
||||
}
|
||||
|
||||
@@ -409,28 +413,7 @@ func (c *Compiler) createInterfaceInvokeWrapper(state interfaceInvokeWrapper) er
|
||||
block := c.ctx.AddBasicBlock(wrapper, "entry")
|
||||
c.builder.SetInsertPointAtEnd(block)
|
||||
|
||||
var receiverPtr llvm.Value
|
||||
if c.targetData.TypeAllocSize(receiverType) > c.targetData.TypeAllocSize(c.i8ptrType) {
|
||||
// The receiver is passed in using a pointer. We have to load it here
|
||||
// and pass it by value to the real function.
|
||||
|
||||
// Load the underlying value.
|
||||
receiverPtrType := llvm.PointerType(receiverType, 0)
|
||||
receiverPtr = c.builder.CreateBitCast(wrapper.Param(0), receiverPtrType, "receiver.ptr")
|
||||
} else {
|
||||
// The value is stored in the interface, but it is of type struct which
|
||||
// is expanded to multiple parameters (e.g. {i8, i8}). So we have to
|
||||
// receive the struct as parameter, expand it, and pass it on to the
|
||||
// real function.
|
||||
|
||||
// Cast the passed-in i8* to the struct value (using an alloca) and
|
||||
// extract its values.
|
||||
alloca := c.builder.CreateAlloca(c.i8ptrType, "receiver.alloca")
|
||||
c.builder.CreateStore(wrapper.Param(0), alloca)
|
||||
receiverPtr = c.builder.CreateBitCast(alloca, llvm.PointerType(receiverType, 0), "receiver.ptr")
|
||||
}
|
||||
|
||||
receiverValue := c.builder.CreateLoad(receiverPtr, "receiver")
|
||||
receiverValue := c.emitPointerUnpack(wrapper.Param(0), []llvm.Type{receiverType})[0]
|
||||
params := append(c.expandFormalParam(receiverValue), wrapper.Params()[1:]...)
|
||||
if fn.LLVMFn.Type().ElementType().ReturnType().TypeKind() == llvm.VoidTypeKind {
|
||||
c.builder.CreateCall(fn.LLVMFn, params, "")
|
||||
@@ -439,6 +422,4 @@ func (c *Compiler) createInterfaceInvokeWrapper(state interfaceInvokeWrapper) er
|
||||
ret := c.builder.CreateCall(fn.LLVMFn, params, "ret")
|
||||
c.builder.CreateRet(ret)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -22,6 +22,47 @@ func getUses(value llvm.Value) []llvm.Value {
|
||||
return uses
|
||||
}
|
||||
|
||||
// createEntryBlockAlloca creates a new alloca in the entry block, even though
|
||||
// the IR builder is located elsewhere. It assumes that the insert point is
|
||||
// after the last instruction in the current block. Also, it adds lifetime
|
||||
// information to the IR signalling that the alloca won't be used before this
|
||||
// point.
|
||||
//
|
||||
// This is useful for creating temporary allocas for intrinsics. Don't forget to
|
||||
// end the lifetime after you're done with it.
|
||||
func (c *Compiler) createEntryBlockAlloca(t llvm.Type, name string) (alloca, bitcast, size llvm.Value) {
|
||||
currentBlock := c.builder.GetInsertBlock()
|
||||
c.builder.SetInsertPointBefore(currentBlock.Parent().EntryBasicBlock().FirstInstruction())
|
||||
alloca = c.builder.CreateAlloca(t, name)
|
||||
c.builder.SetInsertPointAtEnd(currentBlock)
|
||||
bitcast = c.builder.CreateBitCast(alloca, c.i8ptrType, name+".bitcast")
|
||||
size = llvm.ConstInt(c.ctx.Int64Type(), c.targetData.TypeAllocSize(t), false)
|
||||
c.builder.CreateCall(c.getLifetimeStartFunc(), []llvm.Value{size, bitcast}, "")
|
||||
return
|
||||
}
|
||||
|
||||
// getLifetimeStartFunc returns the llvm.lifetime.start intrinsic and creates it
|
||||
// first if it doesn't exist yet.
|
||||
func (c *Compiler) getLifetimeStartFunc() llvm.Value {
|
||||
fn := c.mod.NamedFunction("llvm.lifetime.start.p0i8")
|
||||
if fn.IsNil() {
|
||||
fnType := llvm.FunctionType(c.ctx.VoidType(), []llvm.Type{c.ctx.Int64Type(), c.i8ptrType}, false)
|
||||
fn = llvm.AddFunction(c.mod, "llvm.lifetime.start.p0i8", fnType)
|
||||
}
|
||||
return fn
|
||||
}
|
||||
|
||||
// getLifetimeEndFunc returns the llvm.lifetime.end intrinsic and creates it
|
||||
// first if it doesn't exist yet.
|
||||
func (c *Compiler) getLifetimeEndFunc() llvm.Value {
|
||||
fn := c.mod.NamedFunction("llvm.lifetime.end.p0i8")
|
||||
if fn.IsNil() {
|
||||
fnType := llvm.FunctionType(c.ctx.VoidType(), []llvm.Type{c.ctx.Int64Type(), c.i8ptrType}, false)
|
||||
fn = llvm.AddFunction(c.mod, "llvm.lifetime.end.p0i8", fnType)
|
||||
}
|
||||
return fn
|
||||
}
|
||||
|
||||
// splitBasicBlock splits a LLVM basic block into two parts. All instructions
|
||||
// after afterInst are moved into a new basic block (created right after the
|
||||
// current one) with the given name.
|
||||
|
||||
+29
-20
@@ -10,12 +10,14 @@ import (
|
||||
)
|
||||
|
||||
func (c *Compiler) emitMapLookup(keyType, valueType types.Type, m, key llvm.Value, commaOk bool, pos token.Pos) (llvm.Value, error) {
|
||||
llvmValueType, err := c.getLLVMType(valueType)
|
||||
if err != nil {
|
||||
return llvm.Value{}, err
|
||||
}
|
||||
mapValueAlloca := c.builder.CreateAlloca(llvmValueType, "hashmap.value")
|
||||
mapValuePtr := c.builder.CreateBitCast(mapValueAlloca, c.i8ptrType, "hashmap.valueptr")
|
||||
llvmValueType := c.getLLVMType(valueType)
|
||||
|
||||
// Allocate the memory for the resulting type. Do not zero this memory: it
|
||||
// will be zeroed by the hashmap get implementation if the key is not
|
||||
// present in the map.
|
||||
mapValueAlloca, mapValuePtr, mapValueSize := c.createEntryBlockAlloca(llvmValueType, "hashmap.value")
|
||||
|
||||
// Do the lookup. How it is done depends on the key type.
|
||||
var commaOkValue llvm.Value
|
||||
if t, ok := keyType.(*types.Basic); ok && t.Info()&types.IsString != 0 {
|
||||
// key is a string
|
||||
@@ -23,15 +25,24 @@ func (c *Compiler) emitMapLookup(keyType, valueType types.Type, m, key llvm.Valu
|
||||
commaOkValue = c.createRuntimeCall("hashmapStringGet", params, "")
|
||||
} else if hashmapIsBinaryKey(keyType) {
|
||||
// key can be compared with runtime.memequal
|
||||
keyAlloca := c.builder.CreateAlloca(key.Type(), "hashmap.key")
|
||||
c.builder.CreateStore(key, keyAlloca)
|
||||
keyPtr := c.builder.CreateBitCast(keyAlloca, c.i8ptrType, "hashmap.keyptr")
|
||||
params := []llvm.Value{m, keyPtr, mapValuePtr}
|
||||
// Store the key in an alloca, in the entry block to avoid dynamic stack
|
||||
// growth.
|
||||
mapKeyAlloca, mapKeyPtr, mapKeySize := c.createEntryBlockAlloca(key.Type(), "hashmap.key")
|
||||
c.builder.CreateStore(key, mapKeyAlloca)
|
||||
// Fetch the value from the hashmap.
|
||||
params := []llvm.Value{m, mapKeyPtr, mapValuePtr}
|
||||
commaOkValue = c.createRuntimeCall("hashmapBinaryGet", params, "")
|
||||
c.builder.CreateCall(c.getLifetimeEndFunc(), []llvm.Value{mapKeySize, mapKeyPtr}, "")
|
||||
} else {
|
||||
// Not trivially comparable using memcmp.
|
||||
return llvm.Value{}, c.makeError(pos, "only strings, bools, ints or structs of bools/ints are supported as map keys, but got: "+keyType.String())
|
||||
}
|
||||
|
||||
// Load the resulting value from the hashmap. The value is set to the zero
|
||||
// value if the key doesn't exist in the hashmap.
|
||||
mapValue := c.builder.CreateLoad(mapValueAlloca, "")
|
||||
c.builder.CreateCall(c.getLifetimeEndFunc(), []llvm.Value{mapValueSize, mapValuePtr}, "")
|
||||
|
||||
if commaOk {
|
||||
tuple := llvm.Undef(c.ctx.StructType([]llvm.Type{llvmValueType, c.ctx.Int1Type()}, false))
|
||||
tuple = c.builder.CreateInsertValue(tuple, mapValue, 0, "")
|
||||
@@ -42,27 +53,25 @@ func (c *Compiler) emitMapLookup(keyType, valueType types.Type, m, key llvm.Valu
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Compiler) emitMapUpdate(keyType types.Type, m, key, value llvm.Value, pos token.Pos) error {
|
||||
valueAlloca := c.builder.CreateAlloca(value.Type(), "hashmap.value")
|
||||
func (c *Compiler) emitMapUpdate(keyType types.Type, m, key, value llvm.Value, pos token.Pos) {
|
||||
valueAlloca, valuePtr, valueSize := c.createEntryBlockAlloca(value.Type(), "hashmap.value")
|
||||
c.builder.CreateStore(value, valueAlloca)
|
||||
valuePtr := c.builder.CreateBitCast(valueAlloca, c.i8ptrType, "hashmap.valueptr")
|
||||
keyType = keyType.Underlying()
|
||||
if t, ok := keyType.(*types.Basic); ok && t.Info()&types.IsString != 0 {
|
||||
// key is a string
|
||||
params := []llvm.Value{m, key, valuePtr}
|
||||
c.createRuntimeCall("hashmapStringSet", params, "")
|
||||
return nil
|
||||
} else if hashmapIsBinaryKey(keyType) {
|
||||
// key can be compared with runtime.memequal
|
||||
keyAlloca := c.builder.CreateAlloca(key.Type(), "hashmap.key")
|
||||
keyAlloca, keyPtr, keySize := c.createEntryBlockAlloca(key.Type(), "hashmap.key")
|
||||
c.builder.CreateStore(key, keyAlloca)
|
||||
keyPtr := c.builder.CreateBitCast(keyAlloca, c.i8ptrType, "hashmap.keyptr")
|
||||
params := []llvm.Value{m, keyPtr, valuePtr}
|
||||
c.createRuntimeCall("hashmapBinarySet", params, "")
|
||||
return nil
|
||||
c.builder.CreateCall(c.getLifetimeEndFunc(), []llvm.Value{keySize, keyPtr}, "")
|
||||
} else {
|
||||
return c.makeError(pos, "only strings, bools, ints or structs of bools/ints are supported as map keys, but got: "+keyType.String())
|
||||
c.addError(pos, "only strings, bools, ints or structs of bools/ints are supported as map keys, but got: "+keyType.String())
|
||||
}
|
||||
c.builder.CreateCall(c.getLifetimeEndFunc(), []llvm.Value{valueSize, valuePtr}, "")
|
||||
}
|
||||
|
||||
func (c *Compiler) emitMapDelete(keyType types.Type, m, key llvm.Value, pos token.Pos) error {
|
||||
@@ -73,11 +82,11 @@ func (c *Compiler) emitMapDelete(keyType types.Type, m, key llvm.Value, pos toke
|
||||
c.createRuntimeCall("hashmapStringDelete", params, "")
|
||||
return nil
|
||||
} else if hashmapIsBinaryKey(keyType) {
|
||||
keyAlloca := c.builder.CreateAlloca(key.Type(), "hashmap.key")
|
||||
keyAlloca, keyPtr, keySize := c.createEntryBlockAlloca(key.Type(), "hashmap.key")
|
||||
c.builder.CreateStore(key, keyAlloca)
|
||||
keyPtr := c.builder.CreateBitCast(keyAlloca, c.i8ptrType, "hashmap.keyptr")
|
||||
params := []llvm.Value{m, keyPtr}
|
||||
c.createRuntimeCall("hashmapBinaryDelete", params, "")
|
||||
c.builder.CreateCall(c.getLifetimeEndFunc(), []llvm.Value{keySize, keyPtr}, "")
|
||||
return nil
|
||||
} else {
|
||||
return c.makeError(pos, "only strings, bools, ints or structs of bools/ints are supported as map keys, but got: "+keyType.String())
|
||||
|
||||
+53
-13
@@ -18,6 +18,10 @@ func (c *Compiler) Optimize(optLevel, sizeLevel int, inlinerThreshold uint) erro
|
||||
}
|
||||
builder.AddCoroutinePassesToExtensionPoints()
|
||||
|
||||
if c.PanicStrategy == "trap" {
|
||||
c.replacePanicsWithTrap() // -panic=trap
|
||||
}
|
||||
|
||||
// Run function passes for each function.
|
||||
funcPasses := llvm.NewFunctionPassManagerForModule(c.mod)
|
||||
defer funcPasses.Dispose()
|
||||
@@ -33,6 +37,7 @@ func (c *Compiler) Optimize(optLevel, sizeLevel int, inlinerThreshold uint) erro
|
||||
goPasses := llvm.NewPassManager()
|
||||
defer goPasses.Dispose()
|
||||
goPasses.AddGlobalOptimizerPass()
|
||||
goPasses.AddGlobalDCEPass()
|
||||
goPasses.AddConstantPropagationPass()
|
||||
goPasses.AddAggressiveDCEPass()
|
||||
goPasses.AddFunctionAttrsPass()
|
||||
@@ -43,6 +48,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 +59,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 +82,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
|
||||
@@ -92,9 +115,35 @@ func (c *Compiler) Optimize(optLevel, sizeLevel int, inlinerThreshold uint) erro
|
||||
builder.Populate(modPasses)
|
||||
modPasses.Run(c.mod)
|
||||
|
||||
if c.gcIsPrecise() {
|
||||
c.addGlobalsBitmap()
|
||||
if err := c.Verify(); err != nil {
|
||||
return errors.New("GC pass caused a verification failure")
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Replace panic calls with calls to llvm.trap, to reduce code size. This is the
|
||||
// -panic=trap intrinsic.
|
||||
func (c *Compiler) replacePanicsWithTrap() {
|
||||
trap := c.mod.NamedFunction("llvm.trap")
|
||||
for _, name := range []string{"runtime._panic", "runtime.runtimePanic"} {
|
||||
fn := c.mod.NamedFunction(name)
|
||||
if fn.IsNil() {
|
||||
continue
|
||||
}
|
||||
for _, use := range getUses(fn) {
|
||||
if use.IsACallInst().IsNil() || use.CalledValue() != fn {
|
||||
panic("expected use of a panic function to be a call")
|
||||
}
|
||||
c.builder.SetInsertPointBefore(use)
|
||||
c.builder.CreateCall(trap, nil, "")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Eliminate created but not used maps.
|
||||
//
|
||||
// In the future, this should statically allocate created but never modified
|
||||
@@ -231,7 +280,7 @@ func (c *Compiler) OptimizeAllocs() {
|
||||
sizeInWords := (size + uint64(alignment) - 1) / uint64(alignment)
|
||||
allocaType := llvm.ArrayType(c.ctx.IntType(alignment*8), int(sizeInWords))
|
||||
alloca := c.builder.CreateAlloca(allocaType, "stackalloc.alloca")
|
||||
zero, _ := c.getZeroValue(alloca.Type().ElementType())
|
||||
zero := c.getZeroValue(alloca.Type().ElementType())
|
||||
c.builder.CreateStore(zero, alloca)
|
||||
stackalloc := c.builder.CreateBitCast(alloca, bitcast.Type(), "stackalloc")
|
||||
bitcast.ReplaceAllUsesWith(stackalloc)
|
||||
@@ -266,21 +315,12 @@ func (c *Compiler) doesEscape(value llvm.Value) bool {
|
||||
return true
|
||||
}
|
||||
} else if use.IsACallInst() != nilValue {
|
||||
// Call only escapes when the (pointer) parameter is not marked
|
||||
// "nocapture". This flag means that the parameter does not escape
|
||||
// the give function.
|
||||
if use.CalledValue().IsAFunction() != nilValue {
|
||||
if use.CalledValue().IsDeclaration() {
|
||||
// Kind of dirty: assume external functions don't let
|
||||
// pointers escape.
|
||||
// TODO: introduce //go:noescape that sets the 'nocapture'
|
||||
// flag on each input parameter.
|
||||
continue
|
||||
}
|
||||
}
|
||||
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
|
||||
|
||||
@@ -0,0 +1,156 @@
|
||||
package compiler
|
||||
|
||||
import (
|
||||
"math/big"
|
||||
"strings"
|
||||
)
|
||||
|
||||
var basicTypes = map[string]int64{
|
||||
"bool": 1,
|
||||
"int": 2,
|
||||
"int8": 3,
|
||||
"int16": 4,
|
||||
"int32": 5,
|
||||
"int64": 6,
|
||||
"uint": 7,
|
||||
"uint8": 8,
|
||||
"uint16": 9,
|
||||
"uint32": 10,
|
||||
"uint64": 11,
|
||||
"uintptr": 12,
|
||||
"float32": 13,
|
||||
"float64": 14,
|
||||
"complex64": 15,
|
||||
"complex128": 16,
|
||||
"string": 17,
|
||||
"unsafeptr": 18,
|
||||
}
|
||||
|
||||
func (c *Compiler) assignTypeCodes(typeSlice typeInfoSlice) {
|
||||
fn := c.mod.NamedFunction("reflect.ValueOf")
|
||||
if fn.IsNil() {
|
||||
// reflect.ValueOf is never used, so we can use the most efficient
|
||||
// encoding possible.
|
||||
for i, t := range typeSlice {
|
||||
t.num = uint64(i + 1)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Assign typecodes the way the reflect package expects.
|
||||
fallbackIndex := 1
|
||||
namedTypes := make(map[string]int)
|
||||
for _, t := range typeSlice {
|
||||
if t.name[:5] != "type:" {
|
||||
panic("expected type name to start with 'type:'")
|
||||
}
|
||||
num := c.getTypeCodeNum(t.name[5:], &fallbackIndex, namedTypes)
|
||||
if num.BitLen() > c.uintptrType.IntTypeWidth() || !num.IsUint64() {
|
||||
// TODO: support this in some way, using a side table for example.
|
||||
// That's less efficient but better than not working at all.
|
||||
// Particularly important on systems with 16-bit pointers (e.g.
|
||||
// AVR).
|
||||
panic("compiler: could not store type code number inside interface type code")
|
||||
}
|
||||
t.num = num.Uint64()
|
||||
}
|
||||
}
|
||||
|
||||
// getTypeCodeNum returns the typecode for a given type as expected by the
|
||||
// reflect package. Also see getTypeCodeName, which serializes types to a string
|
||||
// based on a types.Type value for this function.
|
||||
func (c *Compiler) getTypeCodeNum(id string, fallbackIndex *int, namedTypes map[string]int) *big.Int {
|
||||
// Note: see src/reflect/type.go for bit allocations.
|
||||
// A type can be named or unnamed. Example of both:
|
||||
// basic:~foo:uint64
|
||||
// basic:uint64
|
||||
// Extract the class (basic, slice, pointer, etc.), the name, and the
|
||||
// contents of this type ID string. Allocate bits based on that, as
|
||||
// src/runtime/types.go expects.
|
||||
class := id[:strings.IndexByte(id, ':')]
|
||||
value := id[len(class)+1:]
|
||||
name := ""
|
||||
if value[0] == '~' {
|
||||
name = value[1:strings.IndexByte(value, ':')]
|
||||
value = value[len(name)+2:]
|
||||
}
|
||||
if class == "basic" {
|
||||
// Basic types follow the following bit pattern:
|
||||
// ...xxxxx0
|
||||
// where xxxxx is allocated for the 18 possible basic types and all the
|
||||
// upper bits are used to indicate the named type.
|
||||
num, ok := basicTypes[value]
|
||||
if !ok {
|
||||
panic("invalid basic type: " + id)
|
||||
}
|
||||
if name != "" {
|
||||
// This type is named, set the upper bits to the name ID.
|
||||
num |= int64(getNamedTypeNum(namedTypes, name)) << 5
|
||||
}
|
||||
return big.NewInt(num << 1)
|
||||
} else {
|
||||
// Complex types use the following bit pattern:
|
||||
// ...nxxx1
|
||||
// where xxx indicates the complex type (any non-basic type). The upper
|
||||
// bits contain whatever the type contains. Types that wrap a single
|
||||
// other type (channel, interface, pointer, slice) just contain the bits
|
||||
// of the wrapped type. Other types (like struct) have a different
|
||||
// method of encoding the contents of the type.
|
||||
var num *big.Int
|
||||
var classNumber int64
|
||||
switch class {
|
||||
case "chan":
|
||||
num = c.getTypeCodeNum(value, fallbackIndex, namedTypes)
|
||||
classNumber = 0
|
||||
case "interface":
|
||||
num = big.NewInt(int64(*fallbackIndex))
|
||||
*fallbackIndex++
|
||||
classNumber = 1
|
||||
case "pointer":
|
||||
num = c.getTypeCodeNum(value, fallbackIndex, namedTypes)
|
||||
classNumber = 2
|
||||
case "slice":
|
||||
num = c.getTypeCodeNum(value, fallbackIndex, namedTypes)
|
||||
classNumber = 3
|
||||
case "array":
|
||||
num = big.NewInt(int64(*fallbackIndex))
|
||||
*fallbackIndex++
|
||||
classNumber = 4
|
||||
case "func":
|
||||
num = big.NewInt(int64(*fallbackIndex))
|
||||
*fallbackIndex++
|
||||
classNumber = 5
|
||||
case "map":
|
||||
num = big.NewInt(int64(*fallbackIndex))
|
||||
*fallbackIndex++
|
||||
classNumber = 6
|
||||
case "struct":
|
||||
num = big.NewInt(int64(*fallbackIndex))
|
||||
*fallbackIndex++
|
||||
classNumber = 7
|
||||
default:
|
||||
panic("unknown type kind: " + id)
|
||||
}
|
||||
if name == "" {
|
||||
num.Lsh(num, 5).Or(num, big.NewInt((classNumber<<1)+1))
|
||||
} else {
|
||||
// TODO: store num in a sidetable
|
||||
num = big.NewInt(int64(getNamedTypeNum(namedTypes, name))<<1 | 1)
|
||||
num.Lsh(num, 4).Or(num, big.NewInt((classNumber<<1)+1))
|
||||
}
|
||||
return num
|
||||
}
|
||||
}
|
||||
|
||||
// getNamedTypeNum returns an appropriate (unique) number for the given named
|
||||
// type. If the name already has a number that number is returned, else a new
|
||||
// number is returned. The number is always non-zero.
|
||||
func getNamedTypeNum(namedTypes map[string]int, name string) int {
|
||||
if num, ok := namedTypes[name]; ok {
|
||||
return num
|
||||
} else {
|
||||
num = len(namedTypes) + 1
|
||||
namedTypes[name] = num
|
||||
return num
|
||||
}
|
||||
}
|
||||
+39
-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())
|
||||
@@ -107,6 +113,9 @@ func (s *StdSizes) Sizeof(T types.Type) int64 {
|
||||
if k == types.Uintptr {
|
||||
return s.PtrSize
|
||||
}
|
||||
if k == types.UnsafePointer {
|
||||
return s.PtrSize
|
||||
}
|
||||
panic("unknown basic type: " + t.String())
|
||||
case *types.Array:
|
||||
n := t.Len()
|
||||
@@ -125,11 +134,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:
|
||||
|
||||
@@ -0,0 +1,167 @@
|
||||
package compiler
|
||||
|
||||
// This file implements the syscall.Syscall and syscall.Syscall6 instructions as
|
||||
// compiler builtins.
|
||||
|
||||
import (
|
||||
"go/constant"
|
||||
"strconv"
|
||||
|
||||
"golang.org/x/tools/go/ssa"
|
||||
"tinygo.org/x/go-llvm"
|
||||
)
|
||||
|
||||
// emitSyscall emits an inline system call instruction, depending on the target
|
||||
// OS/arch.
|
||||
func (c *Compiler) emitSyscall(frame *Frame, call *ssa.CallCommon) (llvm.Value, error) {
|
||||
num, _ := constant.Uint64Val(call.Args[0].(*ssa.Const).Value)
|
||||
var syscallResult llvm.Value
|
||||
switch {
|
||||
case c.GOARCH == "amd64":
|
||||
if c.GOOS == "darwin" {
|
||||
// Darwin adds this magic number to system call numbers:
|
||||
//
|
||||
// > Syscall classes for 64-bit system call entry.
|
||||
// > For 64-bit users, the 32-bit syscall number is partitioned
|
||||
// > with the high-order bits representing the class and low-order
|
||||
// > bits being the syscall number within that class.
|
||||
// > The high-order 32-bits of the 64-bit syscall number are unused.
|
||||
// > All system classes enter the kernel via the syscall instruction.
|
||||
//
|
||||
// Source: https://opensource.apple.com/source/xnu/xnu-792.13.8/osfmk/mach/i386/syscall_sw.h
|
||||
num += 0x2000000
|
||||
}
|
||||
// Sources:
|
||||
// https://stackoverflow.com/a/2538212
|
||||
// https://en.wikibooks.org/wiki/X86_Assembly/Interfacing_with_Linux#syscall
|
||||
args := []llvm.Value{llvm.ConstInt(c.uintptrType, num, false)}
|
||||
argTypes := []llvm.Type{c.uintptrType}
|
||||
// Constraints will look something like:
|
||||
// "={rax},0,{rdi},{rsi},{rdx},{r10},{r8},{r9},~{rcx},~{r11}"
|
||||
constraints := "={rax},0"
|
||||
for i, arg := range call.Args[1:] {
|
||||
constraints += "," + [...]string{
|
||||
"{rdi}",
|
||||
"{rsi}",
|
||||
"{rdx}",
|
||||
"{r10}",
|
||||
"{r8}",
|
||||
"{r9}",
|
||||
"{r11}",
|
||||
"{r12}",
|
||||
"{r13}",
|
||||
}[i]
|
||||
llvmValue := c.getValue(frame, arg)
|
||||
args = append(args, llvmValue)
|
||||
argTypes = append(argTypes, llvmValue.Type())
|
||||
}
|
||||
constraints += ",~{rcx},~{r11}"
|
||||
fnType := llvm.FunctionType(c.uintptrType, argTypes, false)
|
||||
target := llvm.InlineAsm(fnType, "syscall", constraints, true, false, llvm.InlineAsmDialectIntel)
|
||||
syscallResult = c.builder.CreateCall(target, args, "")
|
||||
case c.GOARCH == "arm" && c.GOOS == "linux":
|
||||
// Implement the EABI system call convention for Linux.
|
||||
// Source: syscall(2) man page.
|
||||
args := []llvm.Value{}
|
||||
argTypes := []llvm.Type{}
|
||||
// Constraints will look something like:
|
||||
// ={r0},0,{r1},{r2},{r7},~{r3}
|
||||
constraints := "={r0}"
|
||||
for i, arg := range call.Args[1:] {
|
||||
constraints += "," + [...]string{
|
||||
"0", // tie to output
|
||||
"{r1}",
|
||||
"{r2}",
|
||||
"{r3}",
|
||||
"{r4}",
|
||||
"{r5}",
|
||||
"{r6}",
|
||||
}[i]
|
||||
llvmValue := c.getValue(frame, arg)
|
||||
args = append(args, llvmValue)
|
||||
argTypes = append(argTypes, llvmValue.Type())
|
||||
}
|
||||
args = append(args, llvm.ConstInt(c.uintptrType, num, false))
|
||||
argTypes = append(argTypes, c.uintptrType)
|
||||
constraints += ",{r7}" // syscall number
|
||||
for i := len(call.Args) - 1; i < 4; i++ {
|
||||
// r0-r3 get clobbered after the syscall returns
|
||||
constraints += ",~{r" + strconv.Itoa(i) + "}"
|
||||
}
|
||||
fnType := llvm.FunctionType(c.uintptrType, argTypes, false)
|
||||
target := llvm.InlineAsm(fnType, "svc #0", constraints, true, false, 0)
|
||||
syscallResult = c.builder.CreateCall(target, args, "")
|
||||
case c.GOARCH == "arm64" && c.GOOS == "linux":
|
||||
// Source: syscall(2) man page.
|
||||
args := []llvm.Value{}
|
||||
argTypes := []llvm.Type{}
|
||||
// Constraints will look something like:
|
||||
// ={x0},0,{x1},{x2},{x8},~{x3},~{x4},~{x5},~{x6},~{x7},~{x16},~{x17}
|
||||
constraints := "={x0}"
|
||||
for i, arg := range call.Args[1:] {
|
||||
constraints += "," + [...]string{
|
||||
"0", // tie to output
|
||||
"{x1}",
|
||||
"{x2}",
|
||||
"{x3}",
|
||||
"{x4}",
|
||||
"{x5}",
|
||||
}[i]
|
||||
llvmValue := c.getValue(frame, arg)
|
||||
args = append(args, llvmValue)
|
||||
argTypes = append(argTypes, llvmValue.Type())
|
||||
}
|
||||
args = append(args, llvm.ConstInt(c.uintptrType, num, false))
|
||||
argTypes = append(argTypes, c.uintptrType)
|
||||
constraints += ",{x8}" // syscall number
|
||||
for i := len(call.Args) - 1; i < 8; i++ {
|
||||
// x0-x7 may get clobbered during the syscall following the aarch64
|
||||
// calling convention.
|
||||
constraints += ",~{x" + strconv.Itoa(i) + "}"
|
||||
}
|
||||
constraints += ",~{x16},~{x17}" // scratch registers
|
||||
fnType := llvm.FunctionType(c.uintptrType, argTypes, false)
|
||||
target := llvm.InlineAsm(fnType, "svc #0", constraints, true, false, 0)
|
||||
syscallResult = c.builder.CreateCall(target, args, "")
|
||||
default:
|
||||
return llvm.Value{}, c.makeError(call.Pos(), "unknown GOOS/GOARCH for syscall: "+c.GOOS+"/"+c.GOARCH)
|
||||
}
|
||||
switch c.GOOS {
|
||||
case "linux":
|
||||
// Return values: r0, r1 uintptr, err Errno
|
||||
// Pseudocode:
|
||||
// var err uintptr
|
||||
// if syscallResult < 0 && syscallResult > -4096 {
|
||||
// err = -syscallResult
|
||||
// }
|
||||
// return syscallResult, 0, err
|
||||
zero := llvm.ConstInt(c.uintptrType, 0, false)
|
||||
inrange1 := c.builder.CreateICmp(llvm.IntSLT, syscallResult, llvm.ConstInt(c.uintptrType, 0, false), "")
|
||||
inrange2 := c.builder.CreateICmp(llvm.IntSGT, syscallResult, llvm.ConstInt(c.uintptrType, 0xfffffffffffff000, true), "") // -4096
|
||||
hasError := c.builder.CreateAnd(inrange1, inrange2, "")
|
||||
errResult := c.builder.CreateSelect(hasError, c.builder.CreateSub(zero, syscallResult, ""), zero, "syscallError")
|
||||
retval := llvm.Undef(llvm.StructType([]llvm.Type{c.uintptrType, c.uintptrType, c.uintptrType}, false))
|
||||
retval = c.builder.CreateInsertValue(retval, syscallResult, 0, "")
|
||||
retval = c.builder.CreateInsertValue(retval, zero, 1, "")
|
||||
retval = c.builder.CreateInsertValue(retval, errResult, 2, "")
|
||||
return retval, nil
|
||||
case "darwin":
|
||||
// Return values: r0, r1 uintptr, err Errno
|
||||
// Pseudocode:
|
||||
// var err uintptr
|
||||
// if syscallResult != 0 {
|
||||
// err = syscallResult
|
||||
// }
|
||||
// return syscallResult, 0, err
|
||||
zero := llvm.ConstInt(c.uintptrType, 0, false)
|
||||
hasError := c.builder.CreateICmp(llvm.IntNE, syscallResult, llvm.ConstInt(c.uintptrType, 0, false), "")
|
||||
errResult := c.builder.CreateSelect(hasError, syscallResult, zero, "syscallError")
|
||||
retval := llvm.Undef(llvm.StructType([]llvm.Type{c.uintptrType, c.uintptrType, c.uintptrType}, false))
|
||||
retval = c.builder.CreateInsertValue(retval, syscallResult, 0, "")
|
||||
retval = c.builder.CreateInsertValue(retval, zero, 1, "")
|
||||
retval = c.builder.CreateInsertValue(retval, errResult, 2, "")
|
||||
return retval, nil
|
||||
default:
|
||||
return llvm.Value{}, c.makeError(call.Pos(), "unknown GOOS/GOARCH for syscall: "+c.GOOS+"/"+c.GOARCH)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
package compiler
|
||||
|
||||
// This file implements volatile loads/stores in runtime/volatile.LoadT and
|
||||
// runtime/volatile.StoreT as compiler builtins.
|
||||
|
||||
import (
|
||||
"golang.org/x/tools/go/ssa"
|
||||
"tinygo.org/x/go-llvm"
|
||||
)
|
||||
|
||||
func (c *Compiler) emitVolatileLoad(frame *Frame, instr *ssa.CallCommon) (llvm.Value, error) {
|
||||
addr := c.getValue(frame, instr.Args[0])
|
||||
c.emitNilCheck(frame, addr, "deref")
|
||||
val := c.builder.CreateLoad(addr, "")
|
||||
val.SetVolatile(true)
|
||||
return val, nil
|
||||
}
|
||||
|
||||
func (c *Compiler) emitVolatileStore(frame *Frame, instr *ssa.CallCommon) (llvm.Value, error) {
|
||||
addr := c.getValue(frame, instr.Args[0])
|
||||
val := c.getValue(frame, instr.Args[1])
|
||||
c.emitNilCheck(frame, addr, "deref")
|
||||
store := c.builder.CreateStore(val, addr)
|
||||
store.SetVolatile(true)
|
||||
return llvm.Value{}, nil
|
||||
}
|
||||
@@ -0,0 +1,108 @@
|
||||
package compiler
|
||||
|
||||
// This file contains utility functions to pack and unpack sets of values. It
|
||||
// can take in a list of values and tries to store it efficiently in the pointer
|
||||
// itself if possible and legal.
|
||||
|
||||
import (
|
||||
"tinygo.org/x/go-llvm"
|
||||
)
|
||||
|
||||
// emitPointerPack packs the list of values into a single pointer value using
|
||||
// bitcasts, or else allocates a value on the heap if it cannot be packed in the
|
||||
// pointer value directly. It returns the pointer with the packed data.
|
||||
func (c *Compiler) emitPointerPack(values []llvm.Value) llvm.Value {
|
||||
valueTypes := make([]llvm.Type, len(values))
|
||||
for i, value := range values {
|
||||
valueTypes[i] = value.Type()
|
||||
}
|
||||
packedType := c.ctx.StructType(valueTypes, false)
|
||||
|
||||
// Allocate memory for the packed data.
|
||||
var packedAlloc, packedHeapAlloc llvm.Value
|
||||
size := c.targetData.TypeAllocSize(packedType)
|
||||
if size == 0 {
|
||||
return llvm.ConstPointerNull(c.i8ptrType)
|
||||
} else if len(values) == 1 && values[0].Type().TypeKind() == llvm.PointerTypeKind {
|
||||
return c.builder.CreateBitCast(values[0], c.i8ptrType, "pack.ptr")
|
||||
} else if size <= c.targetData.TypeAllocSize(c.i8ptrType) && !c.gcIsPrecise() {
|
||||
// Packed data fits in a pointer, so store it directly inside the
|
||||
// pointer.
|
||||
if len(values) == 1 && values[0].Type().TypeKind() == llvm.IntegerTypeKind {
|
||||
// Try to keep this cast in SSA form.
|
||||
return c.builder.CreateIntToPtr(values[0], c.i8ptrType, "pack.int")
|
||||
}
|
||||
// Because packedType is a struct and we have to cast it to a *i8, store
|
||||
// it in an alloca first for bitcasting (store+bitcast+load).
|
||||
packedAlloc = c.builder.CreateAlloca(packedType, "")
|
||||
} else {
|
||||
// Packed data is bigger than a pointer, so allocate it on the heap.
|
||||
sizeValue := llvm.ConstInt(c.uintptrType, size, false)
|
||||
packedHeapAlloc = c.createRuntimeCall("alloc", []llvm.Value{sizeValue}, "")
|
||||
packedAlloc = c.builder.CreateBitCast(packedHeapAlloc, llvm.PointerType(packedType, 0), "")
|
||||
}
|
||||
// Store all values in the alloca or heap pointer.
|
||||
for i, value := range values {
|
||||
indices := []llvm.Value{
|
||||
llvm.ConstInt(c.ctx.Int32Type(), 0, false),
|
||||
llvm.ConstInt(c.ctx.Int32Type(), uint64(i), false),
|
||||
}
|
||||
gep := c.builder.CreateInBoundsGEP(packedAlloc, indices, "")
|
||||
c.builder.CreateStore(value, gep)
|
||||
}
|
||||
|
||||
if packedHeapAlloc.IsNil() {
|
||||
// Load value (as *i8) from the alloca.
|
||||
packedAlloc = c.builder.CreateBitCast(packedAlloc, llvm.PointerType(c.i8ptrType, 0), "")
|
||||
return c.builder.CreateLoad(packedAlloc, "")
|
||||
} else {
|
||||
// Get the original heap allocation pointer, which already is an *i8.
|
||||
return c.builder.CreateBitCast(packedAlloc, c.i8ptrType, "")
|
||||
}
|
||||
}
|
||||
|
||||
// emitPointerUnpack extracts a list of values packed using emitPointerPack.
|
||||
func (c *Compiler) emitPointerUnpack(ptr llvm.Value, valueTypes []llvm.Type) []llvm.Value {
|
||||
packedType := c.ctx.StructType(valueTypes, false)
|
||||
|
||||
// Get a correctly-typed pointer to the packed data.
|
||||
var packedAlloc llvm.Value
|
||||
size := c.targetData.TypeAllocSize(packedType)
|
||||
if size == 0 {
|
||||
// No data to unpack.
|
||||
} else if len(valueTypes) == 1 && valueTypes[0].TypeKind() == llvm.PointerTypeKind {
|
||||
// A single pointer is always stored directly.
|
||||
return []llvm.Value{c.builder.CreateBitCast(ptr, valueTypes[0], "unpack.ptr")}
|
||||
} else if size <= c.targetData.TypeAllocSize(c.i8ptrType) && !c.gcIsPrecise() {
|
||||
// Packed data stored directly in pointer.
|
||||
if len(valueTypes) == 1 && valueTypes[0].TypeKind() == llvm.IntegerTypeKind {
|
||||
// Keep this cast in SSA form.
|
||||
return []llvm.Value{c.builder.CreatePtrToInt(ptr, valueTypes[0], "unpack.int")}
|
||||
}
|
||||
// Fallback: load it using an alloca.
|
||||
packedRawAlloc := c.builder.CreateAlloca(llvm.PointerType(c.i8ptrType, 0), "unpack.raw.alloc")
|
||||
packedRawValue := c.builder.CreateBitCast(ptr, llvm.PointerType(c.i8ptrType, 0), "unpack.raw.value")
|
||||
c.builder.CreateStore(packedRawValue, packedRawAlloc)
|
||||
packedAlloc = c.builder.CreateBitCast(packedRawAlloc, llvm.PointerType(packedType, 0), "unpack.alloc")
|
||||
} else {
|
||||
// Packed data stored on the heap. Bitcast the passed-in pointer to the
|
||||
// correct pointer type.
|
||||
packedAlloc = c.builder.CreateBitCast(ptr, llvm.PointerType(packedType, 0), "unpack.raw.ptr")
|
||||
}
|
||||
// Load each value from the packed data.
|
||||
values := make([]llvm.Value, len(valueTypes))
|
||||
for i, valueType := range valueTypes {
|
||||
if c.targetData.TypeAllocSize(valueType) == 0 {
|
||||
// This value has length zero, so there's nothing to load.
|
||||
values[i] = c.getZeroValue(valueType)
|
||||
continue
|
||||
}
|
||||
indices := []llvm.Value{
|
||||
llvm.ConstInt(c.ctx.Int32Type(), 0, false),
|
||||
llvm.ConstInt(c.ctx.Int32Type(), uint64(i), false),
|
||||
}
|
||||
gep := c.builder.CreateInBoundsGEP(packedAlloc, indices, "")
|
||||
values[i] = c.builder.CreateLoad(gep, "")
|
||||
}
|
||||
return values
|
||||
}
|
||||
+58
-18
@@ -84,15 +84,18 @@ func (fr *frame) evalBasicBlock(bb, incoming llvm.BasicBlock, indent string) (re
|
||||
|
||||
// Memory operators
|
||||
case !inst.IsAAllocaInst().IsNil():
|
||||
fr.locals[inst] = &AllocaValue{
|
||||
Underlying: getZeroValue(inst.Type().ElementType()),
|
||||
Dirty: false,
|
||||
allocType := inst.Type().ElementType()
|
||||
alloca := llvm.AddGlobal(fr.Mod, allocType, fr.pkgName+"$alloca")
|
||||
alloca.SetInitializer(getZeroValue(allocType))
|
||||
alloca.SetLinkage(llvm.InternalLinkage)
|
||||
fr.locals[inst] = &LocalValue{
|
||||
Underlying: alloca,
|
||||
Eval: fr.Eval,
|
||||
}
|
||||
case !inst.IsALoadInst().IsNil():
|
||||
operand := fr.getLocal(inst.Operand(0))
|
||||
operand := fr.getLocal(inst.Operand(0)).(*LocalValue)
|
||||
var value llvm.Value
|
||||
if !operand.IsConstant() || inst.IsVolatile() {
|
||||
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()
|
||||
@@ -173,11 +176,8 @@ func (fr *frame) evalBasicBlock(bb, incoming llvm.BasicBlock, indent string) (re
|
||||
continue // special case: bitcast of alloc
|
||||
}
|
||||
}
|
||||
value := fr.getLocal(operand)
|
||||
if bc, ok := value.(*PointerCastValue); ok {
|
||||
value = bc.Underlying // avoid double bitcasts
|
||||
}
|
||||
fr.locals[inst] = &PointerCastValue{Eval: fr.Eval, Underlying: value, CastType: inst.Type()}
|
||||
value := fr.getLocal(operand).(*LocalValue)
|
||||
fr.locals[inst] = &LocalValue{fr.Eval, fr.builder.CreateBitCast(value.Value(), inst.Type(), "")}
|
||||
|
||||
// Other operators
|
||||
case !inst.IsAICmpInst().IsNil():
|
||||
@@ -222,7 +222,7 @@ func (fr *frame) evalBasicBlock(bb, incoming llvm.BasicBlock, indent string) (re
|
||||
alloc := llvm.AddGlobal(fr.Mod, allocType, fr.pkgName+"$alloc")
|
||||
alloc.SetInitializer(getZeroValue(allocType))
|
||||
alloc.SetLinkage(llvm.InternalLinkage)
|
||||
result := &GlobalValue{
|
||||
result := &LocalValue{
|
||||
Underlying: alloc,
|
||||
Eval: fr.Eval,
|
||||
}
|
||||
@@ -246,15 +246,15 @@ func (fr *frame) evalBasicBlock(bb, incoming llvm.BasicBlock, indent string) (re
|
||||
m := fr.getLocal(inst.Operand(0)).(*MapValue)
|
||||
// "key" is a Go string value, which in the TinyGo calling convention is split up
|
||||
// into separate pointer and length parameters.
|
||||
keyBuf := fr.getLocal(inst.Operand(1))
|
||||
keyLen := fr.getLocal(inst.Operand(2))
|
||||
valPtr := fr.getLocal(inst.Operand(3))
|
||||
keyBuf := fr.getLocal(inst.Operand(1)).(*LocalValue)
|
||||
keyLen := fr.getLocal(inst.Operand(2)).(*LocalValue)
|
||||
valPtr := fr.getLocal(inst.Operand(3)).(*LocalValue)
|
||||
m.PutString(keyBuf, keyLen, valPtr)
|
||||
case callee.Name() == "runtime.hashmapBinarySet":
|
||||
// set a binary (int etc.) key in the map
|
||||
m := fr.getLocal(inst.Operand(0)).(*MapValue)
|
||||
keyBuf := fr.getLocal(inst.Operand(1))
|
||||
valPtr := fr.getLocal(inst.Operand(2))
|
||||
keyBuf := fr.getLocal(inst.Operand(1)).(*LocalValue)
|
||||
valPtr := fr.getLocal(inst.Operand(2)).(*LocalValue)
|
||||
m.PutBinary(keyBuf, valPtr)
|
||||
case callee.Name() == "runtime.stringConcat":
|
||||
// adding two strings together
|
||||
@@ -307,8 +307,48 @@ 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":
|
||||
fr.locals[inst] = &LocalValue{fr.Eval, llvm.ConstPtrToInt(inst.Operand(0), fr.TargetData.IntPtrType())}
|
||||
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 callee.Name() == "llvm.dbg.value":
|
||||
// do nothing
|
||||
case strings.HasPrefix(callee.Name(), "runtime.print") || callee.Name() == "runtime._panic":
|
||||
// This are all print instructions, which necessarily have side
|
||||
// effects but no results.
|
||||
|
||||
+14
-11
@@ -18,7 +18,6 @@ type Eval struct {
|
||||
TargetData llvm.TargetData
|
||||
Debug bool
|
||||
builder llvm.Builder
|
||||
dibuilder *llvm.DIBuilder
|
||||
dirtyGlobals map[llvm.Value]struct{}
|
||||
sideEffectFuncs map[llvm.Value]*sideEffectResult // cache of side effect scan results
|
||||
}
|
||||
@@ -38,14 +37,21 @@ func Run(mod llvm.Module, targetData llvm.TargetData, debug bool) error {
|
||||
dirtyGlobals: map[llvm.Value]struct{}{},
|
||||
}
|
||||
e.builder = mod.Context().NewBuilder()
|
||||
e.dibuilder = llvm.NewDIBuilder(mod)
|
||||
|
||||
initAll := mod.NamedFunction(name)
|
||||
bb := initAll.EntryBasicBlock()
|
||||
e.builder.SetInsertPointBefore(bb.LastInstruction())
|
||||
e.builder.SetInstDebugLocation(bb.FirstInstruction())
|
||||
// Create a dummy alloca in the entry block that we can set the insert point
|
||||
// to. This is necessary because otherwise we might be removing the
|
||||
// instruction (init call) that we are removing after successful
|
||||
// interpretation.
|
||||
e.builder.SetInsertPointBefore(bb.FirstInstruction())
|
||||
dummy := e.builder.CreateAlloca(e.Mod.Context().Int8Type(), "dummy")
|
||||
e.builder.SetInsertPointBefore(dummy)
|
||||
var initCalls []llvm.Value
|
||||
for inst := bb.FirstInstruction(); !inst.IsNil(); inst = llvm.NextInstruction(inst) {
|
||||
if inst == dummy {
|
||||
continue
|
||||
}
|
||||
if !inst.IsAReturnInst().IsNil() {
|
||||
break // ret void
|
||||
}
|
||||
@@ -63,14 +69,15 @@ func Run(mod llvm.Module, targetData llvm.TargetData, debug bool) error {
|
||||
return errors.New("expected all instructions in " + name + " to be *.init() calls")
|
||||
}
|
||||
pkgName := initName[:len(initName)-5]
|
||||
_, err := e.Function(call.CalledValue(), []Value{&LocalValue{e, undefPtr}, &LocalValue{e, undefPtr}}, pkgName)
|
||||
fn := call.CalledValue()
|
||||
call.EraseFromParentAsInstruction()
|
||||
_, err := e.Function(fn, []Value{&LocalValue{e, undefPtr}, &LocalValue{e, undefPtr}}, pkgName)
|
||||
if err == ErrUnreachable {
|
||||
break
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
call.EraseFromParentAsInstruction()
|
||||
}
|
||||
|
||||
return nil
|
||||
@@ -114,11 +121,7 @@ func (e *Eval) function(fn llvm.Value, params []Value, pkgName, indent string) (
|
||||
// getValue determines what kind of LLVM value it gets and returns the
|
||||
// appropriate Value type.
|
||||
func (e *Eval) getValue(v llvm.Value) Value {
|
||||
if !v.IsAGlobalVariable().IsNil() {
|
||||
return &GlobalValue{e, v}
|
||||
} else {
|
||||
return &LocalValue{e, v}
|
||||
}
|
||||
return &LocalValue{e, v}
|
||||
}
|
||||
|
||||
// markDirty marks the passed-in LLVM value dirty, recursively. For example,
|
||||
|
||||
+26
-10
@@ -24,6 +24,20 @@ type sideEffectResult struct {
|
||||
// returns whether this function has side effects and if it does, which globals
|
||||
// it mentions anywhere in this function or any called functions.
|
||||
func (e *Eval) hasSideEffects(fn llvm.Value) *sideEffectResult {
|
||||
switch fn.Name() {
|
||||
case "runtime.alloc":
|
||||
// Cannot be scanned but can be interpreted.
|
||||
return &sideEffectResult{severity: sideEffectNone}
|
||||
case "runtime.nanotime":
|
||||
// Fixed value at compile time.
|
||||
return &sideEffectResult{severity: sideEffectNone}
|
||||
case "runtime._panic":
|
||||
return &sideEffectResult{severity: sideEffectLimited}
|
||||
case "runtime.interfaceImplements":
|
||||
return &sideEffectResult{severity: sideEffectNone}
|
||||
case "llvm.dbg.value":
|
||||
return &sideEffectResult{severity: sideEffectNone}
|
||||
}
|
||||
if e.sideEffectFuncs == nil {
|
||||
e.sideEffectFuncs = make(map[llvm.Value]*sideEffectResult)
|
||||
}
|
||||
@@ -73,25 +87,27 @@ func (e *Eval) hasSideEffects(fn llvm.Value) *sideEffectResult {
|
||||
result.updateSeverity(sideEffectAll)
|
||||
continue
|
||||
}
|
||||
name := child.Name()
|
||||
if child.IsDeclaration() {
|
||||
if name == "runtime.makeInterface" {
|
||||
// Can be interpreted so does not have side effects.
|
||||
continue
|
||||
}
|
||||
// External function call. Assume only limited side effects
|
||||
// (no affected globals, etc.).
|
||||
if result.hasLocalSideEffects(dirtyLocals, inst) {
|
||||
if e.hasLocalSideEffects(dirtyLocals, inst) {
|
||||
result.updateSeverity(sideEffectLimited)
|
||||
}
|
||||
continue
|
||||
}
|
||||
childSideEffects := e.hasSideEffects(fn)
|
||||
childSideEffects := e.hasSideEffects(child)
|
||||
switch childSideEffects.severity {
|
||||
case sideEffectInProgress, sideEffectNone:
|
||||
// no side effects or recursive function - continue scanning
|
||||
case sideEffectLimited:
|
||||
// The return value may be problematic.
|
||||
if e.hasLocalSideEffects(dirtyLocals, inst) {
|
||||
result.updateSeverity(sideEffectLimited)
|
||||
}
|
||||
case sideEffectAll:
|
||||
result.updateSeverity(sideEffectAll)
|
||||
default:
|
||||
result.update(childSideEffects)
|
||||
panic("unreachable")
|
||||
}
|
||||
case llvm.Load, llvm.Store:
|
||||
if inst.IsVolatile() {
|
||||
@@ -118,7 +134,7 @@ func (e *Eval) hasSideEffects(fn llvm.Value) *sideEffectResult {
|
||||
// hasLocalSideEffects checks whether the given instruction flows into a branch
|
||||
// or return instruction, in which case the whole function must be marked as
|
||||
// having side effects and be called at runtime.
|
||||
func (r *sideEffectResult) hasLocalSideEffects(dirtyLocals map[llvm.Value]struct{}, inst llvm.Value) bool {
|
||||
func (e *Eval) hasLocalSideEffects(dirtyLocals map[llvm.Value]struct{}, inst llvm.Value) bool {
|
||||
if _, ok := dirtyLocals[inst]; ok {
|
||||
// It is already known that this local is dirty.
|
||||
return true
|
||||
@@ -156,7 +172,7 @@ func (r *sideEffectResult) hasLocalSideEffects(dirtyLocals map[llvm.Value]struct
|
||||
// For a list:
|
||||
// https://godoc.org/github.com/llvm-mirror/llvm/bindings/go/llvm#Opcode
|
||||
dirtyLocals[user] = struct{}{}
|
||||
if r.hasLocalSideEffects(dirtyLocals, user) {
|
||||
if e.hasLocalSideEffects(dirtyLocals, user) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
+72
-309
@@ -36,11 +36,17 @@ func (v *LocalValue) Type() llvm.Type {
|
||||
}
|
||||
|
||||
func (v *LocalValue) IsConstant() bool {
|
||||
if _, ok := v.Eval.dirtyGlobals[v.Underlying]; ok {
|
||||
return false
|
||||
}
|
||||
return v.Underlying.IsConstant()
|
||||
}
|
||||
|
||||
// Load loads a constant value if this is a constant GEP, otherwise it panics.
|
||||
// Load loads a constant value if this is a constant pointer.
|
||||
func (v *LocalValue) Load() llvm.Value {
|
||||
if !v.Underlying.IsAGlobalVariable().IsNil() {
|
||||
return v.Underlying.Initializer()
|
||||
}
|
||||
switch v.Underlying.Opcode() {
|
||||
case llvm.GetElementPtr:
|
||||
indices := v.getConstGEPIndices()
|
||||
@@ -50,21 +56,32 @@ func (v *LocalValue) Load() llvm.Value {
|
||||
global := v.Eval.getValue(v.Underlying.Operand(0))
|
||||
agg := global.Load()
|
||||
return llvm.ConstExtractValue(agg, indices[1:])
|
||||
case llvm.BitCast:
|
||||
panic("interp: load from a bitcast")
|
||||
default:
|
||||
panic("interp: load from a constant")
|
||||
}
|
||||
}
|
||||
|
||||
// Store stores to the underlying value if the value type is a constant GEP,
|
||||
// Store stores to the underlying value if the value type is a pointer type,
|
||||
// otherwise it panics.
|
||||
func (v *LocalValue) Store(value llvm.Value) {
|
||||
if !v.Underlying.IsAGlobalVariable().IsNil() {
|
||||
if !value.IsConstant() {
|
||||
v.MarkDirty()
|
||||
v.Eval.builder.CreateStore(value, v.Underlying)
|
||||
} else {
|
||||
v.Underlying.SetInitializer(value)
|
||||
}
|
||||
return
|
||||
}
|
||||
switch v.Underlying.Opcode() {
|
||||
case llvm.GetElementPtr:
|
||||
indices := v.getConstGEPIndices()
|
||||
if indices[0] != 0 {
|
||||
panic("invalid GEP")
|
||||
}
|
||||
global := &GlobalValue{v.Eval, v.Underlying.Operand(0)}
|
||||
global := &LocalValue{v.Eval, v.Underlying.Operand(0)}
|
||||
agg := global.Load()
|
||||
agg = llvm.ConstInsertValue(agg, value, indices[1:])
|
||||
global.Store(agg)
|
||||
@@ -74,10 +91,13 @@ func (v *LocalValue) Store(value llvm.Value) {
|
||||
}
|
||||
}
|
||||
|
||||
// GetElementPtr returns a constant GEP when the underlying value is also a
|
||||
// constant GEP. It panics when the underlying value is not a constant GEP:
|
||||
// getting the pointer to a constant is not possible.
|
||||
// GetElementPtr returns a GEP when the underlying value is of pointer type.
|
||||
func (v *LocalValue) GetElementPtr(indices []uint32) Value {
|
||||
if !v.Underlying.IsAGlobalVariable().IsNil() {
|
||||
int32Type := v.Underlying.Type().Context().Int32Type()
|
||||
gep := llvm.ConstGEP(v.Underlying, getLLVMIndices(int32Type, indices))
|
||||
return &LocalValue{v.Eval, gep}
|
||||
}
|
||||
switch v.Underlying.Opcode() {
|
||||
case llvm.GetElementPtr, llvm.IntToPtr:
|
||||
int32Type := v.Underlying.Type().Context().Int32Type()
|
||||
@@ -107,283 +127,18 @@ func (v *LocalValue) getConstGEPIndices() []uint32 {
|
||||
return indices
|
||||
}
|
||||
|
||||
// GlobalValue wraps a LLVM global variable.
|
||||
type GlobalValue struct {
|
||||
Eval *Eval
|
||||
Underlying llvm.Value
|
||||
}
|
||||
|
||||
// Value returns the initializer for this global variable.
|
||||
func (v *GlobalValue) Value() llvm.Value {
|
||||
return v.Underlying
|
||||
}
|
||||
|
||||
// Type returns the type of this global variable, which is a pointer type. Use
|
||||
// Type().ElementType() to get the actual global variable type.
|
||||
func (v *GlobalValue) Type() llvm.Type {
|
||||
return v.Underlying.Type()
|
||||
}
|
||||
|
||||
// IsConstant returns true if this global is not dirty, false otherwise.
|
||||
func (v *GlobalValue) IsConstant() bool {
|
||||
if _, ok := v.Eval.dirtyGlobals[v.Underlying]; ok {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// Load returns the initializer of the global variable.
|
||||
func (v *GlobalValue) Load() llvm.Value {
|
||||
return v.Underlying.Initializer()
|
||||
}
|
||||
|
||||
// Store sets the initializer of the global variable.
|
||||
func (v *GlobalValue) Store(value llvm.Value) {
|
||||
if !value.IsConstant() {
|
||||
v.MarkDirty()
|
||||
v.Eval.builder.CreateStore(value, v.Underlying)
|
||||
} else {
|
||||
v.Underlying.SetInitializer(value)
|
||||
}
|
||||
}
|
||||
|
||||
// GetElementPtr returns a constant GEP on this global, which can be used in
|
||||
// load and store instructions.
|
||||
func (v *GlobalValue) GetElementPtr(indices []uint32) Value {
|
||||
int32Type := v.Underlying.Type().Context().Int32Type()
|
||||
gep := llvm.ConstGEP(v.Underlying, getLLVMIndices(int32Type, indices))
|
||||
return &LocalValue{v.Eval, gep}
|
||||
}
|
||||
|
||||
func (v *GlobalValue) String() string {
|
||||
return "&GlobalValue{" + v.Underlying.Name() + "}"
|
||||
}
|
||||
|
||||
// MarkDirty marks this global as dirty, meaning that every load from and store
|
||||
// to this global (from now on) must be performed at runtime.
|
||||
func (v *GlobalValue) MarkDirty() {
|
||||
func (v *LocalValue) MarkDirty() {
|
||||
if v.Underlying.IsAGlobalVariable().IsNil() {
|
||||
panic("trying to mark a non-global as dirty")
|
||||
}
|
||||
if !v.IsConstant() {
|
||||
return // already dirty
|
||||
}
|
||||
v.Eval.dirtyGlobals[v.Underlying] = struct{}{}
|
||||
}
|
||||
|
||||
// An alloca represents a local alloca, which is a stack allocated variable.
|
||||
// It is emulated by storing the constant of the alloca.
|
||||
type AllocaValue struct {
|
||||
Eval *Eval
|
||||
Underlying llvm.Value // the constant value itself if not dirty, otherwise the alloca instruction
|
||||
Dirty bool // this value must be evaluated at runtime
|
||||
}
|
||||
|
||||
// Value turns this alloca into a runtime alloca instead of a compile-time
|
||||
// constant (if not already converted), and returns the alloca itself.
|
||||
func (v *AllocaValue) Value() llvm.Value {
|
||||
if !v.Dirty {
|
||||
// Mark this alloca a dirty, meaning it is run at runtime instead of
|
||||
// compile time.
|
||||
alloca := v.Eval.builder.CreateAlloca(v.Underlying.Type(), "")
|
||||
v.Eval.builder.CreateStore(v.Underlying, alloca)
|
||||
v.Dirty = true
|
||||
v.Underlying = alloca
|
||||
}
|
||||
return v.Underlying
|
||||
}
|
||||
|
||||
// Type returns the type of this alloca, which is always a pointer.
|
||||
func (v *AllocaValue) Type() llvm.Type {
|
||||
if v.Dirty {
|
||||
return v.Underlying.Type()
|
||||
} else {
|
||||
return llvm.PointerType(v.Underlying.Type(), 0)
|
||||
}
|
||||
}
|
||||
|
||||
func (v *AllocaValue) IsConstant() bool {
|
||||
return !v.Dirty
|
||||
}
|
||||
|
||||
// Load returns the value this alloca contains, which may be evaluated at
|
||||
// runtime.
|
||||
func (v *AllocaValue) Load() llvm.Value {
|
||||
if v.Dirty {
|
||||
ret := v.Eval.builder.CreateLoad(v.Underlying, "")
|
||||
if ret.IsNil() {
|
||||
panic("alloca is nil")
|
||||
}
|
||||
return ret
|
||||
} else {
|
||||
if v.Underlying.IsNil() {
|
||||
panic("alloca is nil")
|
||||
}
|
||||
return v.Underlying
|
||||
}
|
||||
}
|
||||
|
||||
// Store updates the value of this alloca.
|
||||
func (v *AllocaValue) Store(value llvm.Value) {
|
||||
if v.Underlying.Type() != value.Type() {
|
||||
panic("interp: trying to store to an alloca with a different type")
|
||||
}
|
||||
if v.Dirty || !value.IsConstant() {
|
||||
v.Eval.builder.CreateStore(value, v.Value())
|
||||
} else {
|
||||
v.Underlying = value
|
||||
}
|
||||
}
|
||||
|
||||
// GetElementPtr returns a value (a *GetElementPtrValue) that keeps a reference
|
||||
// to this alloca, so that Load() and Store() continue to work.
|
||||
func (v *AllocaValue) GetElementPtr(indices []uint32) Value {
|
||||
return &GetElementPtrValue{v, indices}
|
||||
}
|
||||
|
||||
func (v *AllocaValue) String() string {
|
||||
return "&AllocaValue{Type: " + v.Type().String() + "}"
|
||||
}
|
||||
|
||||
// GetElementPtrValue wraps an alloca, keeping track of what the GEP points to
|
||||
// so it can be used as a pointer value (with Load() and Store()).
|
||||
type GetElementPtrValue struct {
|
||||
Alloca *AllocaValue
|
||||
Indices []uint32
|
||||
}
|
||||
|
||||
// Type returns the type of this GEP, which is always of type pointer.
|
||||
func (v *GetElementPtrValue) Type() llvm.Type {
|
||||
if v.Alloca.Dirty {
|
||||
return v.Value().Type()
|
||||
} else {
|
||||
return llvm.PointerType(v.Load().Type(), 0)
|
||||
}
|
||||
}
|
||||
|
||||
func (v *GetElementPtrValue) IsConstant() bool {
|
||||
return v.Alloca.IsConstant()
|
||||
}
|
||||
|
||||
// Value creates the LLVM GEP instruction of this GetElementPtrValue wrapper and
|
||||
// returns it.
|
||||
func (v *GetElementPtrValue) Value() llvm.Value {
|
||||
if v.Alloca.Dirty {
|
||||
alloca := v.Alloca.Value()
|
||||
int32Type := v.Alloca.Type().Context().Int32Type()
|
||||
llvmIndices := getLLVMIndices(int32Type, v.Indices)
|
||||
return v.Alloca.Eval.builder.CreateGEP(alloca, llvmIndices, "")
|
||||
} else {
|
||||
panic("interp: todo: pointer to alloca gep")
|
||||
}
|
||||
}
|
||||
|
||||
// Load deferences the pointer this GEP points to. For a constant GEP, it
|
||||
// extracts the value from the underlying alloca.
|
||||
func (v *GetElementPtrValue) Load() llvm.Value {
|
||||
if v.Alloca.Dirty {
|
||||
gep := v.Value()
|
||||
return v.Alloca.Eval.builder.CreateLoad(gep, "")
|
||||
} else {
|
||||
underlying := v.Alloca.Load()
|
||||
indices := v.Indices
|
||||
if indices[0] != 0 {
|
||||
panic("invalid GEP")
|
||||
}
|
||||
return llvm.ConstExtractValue(underlying, indices[1:])
|
||||
}
|
||||
}
|
||||
|
||||
// Store stores to the pointer this GEP points to. For a constant GEP, it
|
||||
// updates the underlying allloca.
|
||||
func (v *GetElementPtrValue) Store(value llvm.Value) {
|
||||
if v.Alloca.Dirty || !value.IsConstant() {
|
||||
alloca := v.Alloca.Value()
|
||||
int32Type := v.Alloca.Type().Context().Int32Type()
|
||||
llvmIndices := getLLVMIndices(int32Type, v.Indices)
|
||||
gep := v.Alloca.Eval.builder.CreateGEP(alloca, llvmIndices, "")
|
||||
v.Alloca.Eval.builder.CreateStore(value, gep)
|
||||
} else {
|
||||
underlying := v.Alloca.Load()
|
||||
indices := v.Indices
|
||||
if indices[0] != 0 {
|
||||
panic("invalid GEP")
|
||||
}
|
||||
underlying = llvm.ConstInsertValue(underlying, value, indices[1:])
|
||||
v.Alloca.Store(underlying)
|
||||
}
|
||||
}
|
||||
|
||||
func (v *GetElementPtrValue) GetElementPtr(indices []uint32) Value {
|
||||
if v.Alloca.Dirty {
|
||||
panic("interp: todo: gep on a dirty gep")
|
||||
} else {
|
||||
combined := append([]uint32{}, v.Indices...)
|
||||
combined[len(combined)-1] += indices[0]
|
||||
combined = append(combined, indices[1:]...)
|
||||
return &GetElementPtrValue{v.Alloca, combined}
|
||||
}
|
||||
}
|
||||
|
||||
func (v *GetElementPtrValue) String() string {
|
||||
indices := ""
|
||||
for _, n := range v.Indices {
|
||||
if indices != "" {
|
||||
indices += ", "
|
||||
}
|
||||
indices += strconv.Itoa(int(n))
|
||||
}
|
||||
return "&GetElementPtrValue{Alloca: " + v.Alloca.String() + ", Indices: [" + indices + "]}"
|
||||
}
|
||||
|
||||
// PointerCastValue represents a bitcast operation on a pointer.
|
||||
type PointerCastValue struct {
|
||||
Eval *Eval
|
||||
Underlying Value
|
||||
CastType llvm.Type
|
||||
}
|
||||
|
||||
// Value returns a constant bitcast value.
|
||||
func (v *PointerCastValue) Value() llvm.Value {
|
||||
from := v.Underlying.Value()
|
||||
return llvm.ConstBitCast(from, v.CastType)
|
||||
}
|
||||
|
||||
// Type returns the type this pointer has been cast to.
|
||||
func (v *PointerCastValue) Type() llvm.Type {
|
||||
return v.CastType
|
||||
}
|
||||
|
||||
func (v *PointerCastValue) IsConstant() bool {
|
||||
return v.Underlying.IsConstant()
|
||||
}
|
||||
|
||||
// Load tries to load and bitcast the given value. If this value cannot be
|
||||
// bitcasted, Load panics.
|
||||
func (v *PointerCastValue) Load() llvm.Value {
|
||||
if v.Underlying.IsConstant() {
|
||||
typeFrom := v.Underlying.Type().ElementType()
|
||||
typeTo := v.CastType.ElementType()
|
||||
if isScalar(typeFrom) && isScalar(typeTo) && v.Eval.TargetData.TypeAllocSize(typeFrom) == v.Eval.TargetData.TypeAllocSize(typeTo) {
|
||||
return llvm.ConstBitCast(v.Underlying.Load(), v.CastType.ElementType())
|
||||
}
|
||||
}
|
||||
|
||||
panic("interp: load from a pointer bitcast: " + v.String())
|
||||
}
|
||||
|
||||
// Store panics: it is not (yet) possible to store directly to a bitcast.
|
||||
func (v *PointerCastValue) Store(value llvm.Value) {
|
||||
panic("interp: store on a pointer bitcast")
|
||||
}
|
||||
|
||||
// GetElementPtr panics: it is not (yet) possible to do a GEP operation on a
|
||||
// bitcast.
|
||||
func (v *PointerCastValue) GetElementPtr(indices []uint32) Value {
|
||||
panic("interp: GEP on a pointer bitcast")
|
||||
}
|
||||
|
||||
func (v *PointerCastValue) String() string {
|
||||
return "&PointerCastValue{Value: " + v.Underlying.String() + ", CastType: " + v.CastType.String() + "}"
|
||||
}
|
||||
|
||||
// MapValue implements a Go map which is created at compile time and stored as a
|
||||
// global variable.
|
||||
type MapValue struct {
|
||||
@@ -534,27 +289,24 @@ func (v *MapValue) GetElementPtr(indices []uint32) Value {
|
||||
|
||||
// PutString does a map assign operation, assuming that the map is of type
|
||||
// map[string]T.
|
||||
func (v *MapValue) PutString(keyBuf, keyLen, valPtr Value) {
|
||||
func (v *MapValue) PutString(keyBuf, keyLen, valPtr *LocalValue) {
|
||||
if !v.Underlying.IsNil() {
|
||||
panic("map already created")
|
||||
}
|
||||
|
||||
var value llvm.Value
|
||||
switch valPtr := valPtr.(type) {
|
||||
case *PointerCastValue:
|
||||
value = valPtr.Underlying.Load()
|
||||
if v.ValueType.IsNil() {
|
||||
v.ValueType = value.Type()
|
||||
if int(v.Eval.TargetData.TypeAllocSize(v.ValueType)) != v.ValueSize {
|
||||
panic("interp: map store value type has the wrong size")
|
||||
}
|
||||
} else {
|
||||
if value.Type() != v.ValueType {
|
||||
panic("interp: map store value type is inconsistent")
|
||||
}
|
||||
if valPtr.Underlying.Opcode() == llvm.BitCast {
|
||||
valPtr = &LocalValue{v.Eval, valPtr.Underlying.Operand(0)}
|
||||
}
|
||||
value := valPtr.Load()
|
||||
if v.ValueType.IsNil() {
|
||||
v.ValueType = value.Type()
|
||||
if int(v.Eval.TargetData.TypeAllocSize(v.ValueType)) != v.ValueSize {
|
||||
panic("interp: map store value type has the wrong size")
|
||||
}
|
||||
} else {
|
||||
if value.Type() != v.ValueType {
|
||||
panic("interp: map store value type is inconsistent")
|
||||
}
|
||||
default:
|
||||
panic("interp: todo: handle map value pointer")
|
||||
}
|
||||
|
||||
keyType := v.Eval.Mod.GetTypeByName("runtime._string")
|
||||
@@ -569,31 +321,42 @@ func (v *MapValue) PutString(keyBuf, keyLen, valPtr Value) {
|
||||
}
|
||||
|
||||
// PutBinary does a map assign operation.
|
||||
func (v *MapValue) PutBinary(keyPtr, valPtr Value) {
|
||||
func (v *MapValue) PutBinary(keyPtr, valPtr *LocalValue) {
|
||||
if !v.Underlying.IsNil() {
|
||||
panic("map already created")
|
||||
}
|
||||
|
||||
var value llvm.Value
|
||||
switch valPtr := valPtr.(type) {
|
||||
case *PointerCastValue:
|
||||
value = valPtr.Underlying.Load()
|
||||
if v.ValueType.IsNil() {
|
||||
v.ValueType = value.Type()
|
||||
if int(v.Eval.TargetData.TypeAllocSize(v.ValueType)) != v.ValueSize {
|
||||
panic("interp: map store value type has the wrong size")
|
||||
}
|
||||
} else {
|
||||
if value.Type() != v.ValueType {
|
||||
panic("interp: map store value type is inconsistent")
|
||||
}
|
||||
if valPtr.Underlying.Opcode() == llvm.BitCast {
|
||||
valPtr = &LocalValue{v.Eval, valPtr.Underlying.Operand(0)}
|
||||
}
|
||||
value := valPtr.Load()
|
||||
if v.ValueType.IsNil() {
|
||||
v.ValueType = value.Type()
|
||||
if int(v.Eval.TargetData.TypeAllocSize(v.ValueType)) != v.ValueSize {
|
||||
panic("interp: map store value type has the wrong size")
|
||||
}
|
||||
} else {
|
||||
if value.Type() != v.ValueType {
|
||||
panic("interp: map store value type is inconsistent")
|
||||
}
|
||||
default:
|
||||
panic("interp: todo: handle map value pointer")
|
||||
}
|
||||
|
||||
key := keyPtr.(*PointerCastValue).Underlying.Load()
|
||||
v.KeyType = key.Type()
|
||||
if keyPtr.Underlying.Opcode() == llvm.BitCast {
|
||||
keyPtr = &LocalValue{v.Eval, keyPtr.Underlying.Operand(0)}
|
||||
} else if keyPtr.Underlying.Opcode() == llvm.GetElementPtr {
|
||||
keyPtr = &LocalValue{v.Eval, keyPtr.Underlying.Operand(0)}
|
||||
}
|
||||
key := keyPtr.Load()
|
||||
if v.KeyType.IsNil() {
|
||||
v.KeyType = key.Type()
|
||||
if int(v.Eval.TargetData.TypeAllocSize(v.KeyType)) != v.KeySize {
|
||||
panic("interp: map store key type has the wrong size")
|
||||
}
|
||||
} else {
|
||||
if key.Type() != v.KeyType {
|
||||
panic("interp: map store key type is inconsistent")
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: avoid duplicate keys
|
||||
v.Keys = append(v.Keys, &LocalValue{v.Eval, key})
|
||||
|
||||
@@ -1,522 +0,0 @@
|
||||
package ir
|
||||
|
||||
// This file provides functionality to interpret very basic Go SSA, for
|
||||
// compile-time initialization of globals.
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"go/constant"
|
||||
"go/token"
|
||||
"go/types"
|
||||
"strings"
|
||||
|
||||
"golang.org/x/tools/go/ssa"
|
||||
)
|
||||
|
||||
var ErrCGoWrapper = errors.New("tinygo internal: cgo wrapper") // a signal, not an error
|
||||
|
||||
// Ignore these calls (replace with a zero return value) when encountered during
|
||||
// interpretation.
|
||||
var ignoreInitCalls = map[string]struct{}{
|
||||
"syscall.runtime_envs": struct{}{},
|
||||
"syscall/js.predefValue": struct{}{},
|
||||
"(syscall/js.Value).Get": struct{}{},
|
||||
"(syscall/js.Value).New": struct{}{},
|
||||
"(syscall/js.Value).Int": struct{}{},
|
||||
"os.init$1": struct{}{},
|
||||
}
|
||||
|
||||
// Interpret instructions as far as possible, and drop those instructions from
|
||||
// the basic block.
|
||||
func (p *Program) Interpret(block *ssa.BasicBlock, dumpSSA bool) error {
|
||||
if dumpSSA {
|
||||
fmt.Printf("\ninterpret: %s\n", block.Parent().Pkg.Pkg.Path())
|
||||
}
|
||||
for {
|
||||
i, err := p.interpret(block.Instrs, nil, nil, nil, dumpSSA)
|
||||
if err == ErrCGoWrapper {
|
||||
// skip this instruction
|
||||
block.Instrs = block.Instrs[i+1:]
|
||||
continue
|
||||
}
|
||||
block.Instrs = block.Instrs[i:]
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// Interpret instructions as far as possible, and return the index of the first
|
||||
// unknown instruction.
|
||||
func (p *Program) interpret(instrs []ssa.Instruction, paramKeys []*ssa.Parameter, paramValues []Value, results []Value, dumpSSA bool) (int, error) {
|
||||
locals := map[ssa.Value]Value{}
|
||||
for i, key := range paramKeys {
|
||||
locals[key] = paramValues[i]
|
||||
}
|
||||
for i, instr := range instrs {
|
||||
if _, ok := instr.(*ssa.DebugRef); ok {
|
||||
continue
|
||||
}
|
||||
if dumpSSA {
|
||||
if val, ok := instr.(ssa.Value); ok && val.Name() != "" {
|
||||
fmt.Printf("\t%s: %s = %s\n", instr.Parent().RelString(nil), val.Name(), val.String())
|
||||
} else {
|
||||
fmt.Printf("\t%s: %s\n", instr.Parent().RelString(nil), instr.String())
|
||||
}
|
||||
}
|
||||
switch instr := instr.(type) {
|
||||
case *ssa.Alloc:
|
||||
alloc, err := p.getZeroValue(instr.Type().Underlying().(*types.Pointer).Elem())
|
||||
if err != nil {
|
||||
return i, err
|
||||
}
|
||||
locals[instr] = &PointerValue{nil, &alloc}
|
||||
case *ssa.BinOp:
|
||||
if typ, ok := instr.Type().(*types.Basic); ok && typ.Kind() == types.String {
|
||||
// Concatenate two strings.
|
||||
// This happens in the time package, for example.
|
||||
x, err := p.getValue(instr.X, locals)
|
||||
if err != nil {
|
||||
return i, err
|
||||
}
|
||||
y, err := p.getValue(instr.Y, locals)
|
||||
if err != nil {
|
||||
return i, err
|
||||
}
|
||||
xstr := constant.StringVal(x.(*ConstValue).Expr.Value)
|
||||
ystr := constant.StringVal(y.(*ConstValue).Expr.Value)
|
||||
locals[instr] = &ConstValue{ssa.NewConst(constant.MakeString(xstr+ystr), types.Typ[types.String])}
|
||||
} else {
|
||||
return i, errors.New("init: unknown binop: " + instr.String())
|
||||
}
|
||||
case *ssa.Call:
|
||||
common := instr.Common()
|
||||
callee := common.StaticCallee()
|
||||
if callee == nil {
|
||||
return i, nil // don't understand dynamic dispatch
|
||||
}
|
||||
if _, ok := ignoreInitCalls[callee.String()]; ok {
|
||||
// These calls are not needed and can be ignored, for the time
|
||||
// being.
|
||||
results := make([]Value, callee.Signature.Results().Len())
|
||||
for i := range results {
|
||||
var err error
|
||||
results[i], err = p.getZeroValue(callee.Signature.Results().At(i).Type())
|
||||
if err != nil {
|
||||
return i, err
|
||||
}
|
||||
}
|
||||
if len(results) == 1 {
|
||||
locals[instr] = results[0]
|
||||
} else if len(results) > 1 {
|
||||
locals[instr] = &StructValue{Fields: results}
|
||||
}
|
||||
continue
|
||||
}
|
||||
if callee.String() == "os.NewFile" {
|
||||
// Emulate the creation of os.Stdin, os.Stdout and os.Stderr.
|
||||
resultPtrType := callee.Signature.Results().At(0).Type().(*types.Pointer)
|
||||
resultStructOuterType := resultPtrType.Elem().Underlying().(*types.Struct)
|
||||
if resultStructOuterType.NumFields() != 1 {
|
||||
panic("expected 1 field in os.File struct")
|
||||
}
|
||||
fileInnerPtrType := resultStructOuterType.Field(0).Type().(*types.Pointer)
|
||||
fileInnerType := fileInnerPtrType.Elem().(*types.Named)
|
||||
fileInnerStructType := fileInnerType.Underlying().(*types.Struct)
|
||||
fileInner, err := p.getZeroValue(fileInnerType) // os.file
|
||||
if err != nil {
|
||||
return i, err
|
||||
}
|
||||
for fieldIndex := 0; fieldIndex < fileInnerStructType.NumFields(); fieldIndex++ {
|
||||
field := fileInnerStructType.Field(fieldIndex)
|
||||
if field.Name() == "name" {
|
||||
// Set the 'name' field.
|
||||
name, err := p.getValue(common.Args[1], locals)
|
||||
if err != nil {
|
||||
return i, err
|
||||
}
|
||||
fileInner.(*StructValue).Fields[fieldIndex] = name
|
||||
} else if field.Type().String() == "internal/poll.FD" {
|
||||
// Set the file descriptor field.
|
||||
field := field.Type().Underlying().(*types.Struct)
|
||||
for subfieldIndex := 0; subfieldIndex < field.NumFields(); subfieldIndex++ {
|
||||
subfield := field.Field(subfieldIndex)
|
||||
if subfield.Name() == "Sysfd" {
|
||||
sysfd, err := p.getValue(common.Args[0], locals)
|
||||
if err != nil {
|
||||
return i, err
|
||||
}
|
||||
sysfd = &ConstValue{Expr: ssa.NewConst(sysfd.(*ConstValue).Expr.Value, subfield.Type())}
|
||||
fileInner.(*StructValue).Fields[fieldIndex].(*StructValue).Fields[subfieldIndex] = sysfd
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
fileInnerPtr := &PointerValue{fileInnerPtrType, &fileInner} // *os.file
|
||||
var fileOuter Value = &StructValue{Type: resultPtrType.Elem(), Fields: []Value{fileInnerPtr}} // os.File
|
||||
result := &PointerValue{resultPtrType.Elem(), &fileOuter} // *os.File
|
||||
locals[instr] = result
|
||||
continue
|
||||
}
|
||||
if canInterpret(callee) {
|
||||
params := make([]Value, len(common.Args))
|
||||
for i, arg := range common.Args {
|
||||
val, err := p.getValue(arg, locals)
|
||||
if err != nil {
|
||||
return i, err
|
||||
}
|
||||
params[i] = val
|
||||
}
|
||||
results := make([]Value, callee.Signature.Results().Len())
|
||||
subi, err := p.interpret(callee.Blocks[0].Instrs, callee.Params, params, results, dumpSSA)
|
||||
if err != nil {
|
||||
return i, err
|
||||
}
|
||||
if subi != len(callee.Blocks[0].Instrs) {
|
||||
return i, errors.New("init: could not interpret all instructions of subroutine")
|
||||
}
|
||||
if len(results) == 1 {
|
||||
locals[instr] = results[0]
|
||||
} else {
|
||||
panic("unimplemented: not exactly 1 result")
|
||||
}
|
||||
continue
|
||||
}
|
||||
if callee.Object() == nil || callee.Object().Name() == "init" {
|
||||
return i, nil // arrived at the init#num functions
|
||||
}
|
||||
return i, errors.New("todo: init call: " + callee.String())
|
||||
case *ssa.ChangeType:
|
||||
x, err := p.getValue(instr.X, locals)
|
||||
if err != nil {
|
||||
return i, 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 := instr.Type().Underlying().(*types.Struct); ok {
|
||||
return i, errors.New("todo: init: " + instr.String())
|
||||
}
|
||||
locals[instr] = x
|
||||
case *ssa.Convert:
|
||||
x, err := p.getValue(instr.X, locals)
|
||||
if err != nil {
|
||||
return i, err
|
||||
}
|
||||
typeFrom := instr.X.Type().Underlying()
|
||||
switch typeTo := instr.Type().Underlying().(type) {
|
||||
case *types.Basic:
|
||||
if typeTo.Kind() == types.String {
|
||||
return i, nil
|
||||
}
|
||||
|
||||
if _, ok := typeFrom.(*types.Pointer); ok && typeTo.Kind() == types.UnsafePointer {
|
||||
locals[instr] = &PointerBitCastValue{typeTo, x}
|
||||
} else if typeFrom, ok := typeFrom.(*types.Basic); ok {
|
||||
if typeFrom.Kind() == types.UnsafePointer && typeTo.Kind() == types.Uintptr {
|
||||
locals[instr] = &PointerToUintptrValue{x}
|
||||
} else if typeFrom.Info()&types.IsInteger != 0 && typeTo.Info()&types.IsInteger != 0 {
|
||||
locals[instr] = &ConstValue{Expr: ssa.NewConst(x.(*ConstValue).Expr.Value, typeTo)}
|
||||
} else {
|
||||
return i, nil
|
||||
}
|
||||
} else {
|
||||
return i, nil
|
||||
}
|
||||
case *types.Pointer:
|
||||
if typeFrom, ok := typeFrom.(*types.Basic); ok && typeFrom.Kind() == types.UnsafePointer {
|
||||
locals[instr] = &PointerBitCastValue{typeTo, x}
|
||||
} else {
|
||||
panic("expected unsafe pointer conversion")
|
||||
}
|
||||
default:
|
||||
return i, nil
|
||||
}
|
||||
case *ssa.DebugRef:
|
||||
// ignore
|
||||
case *ssa.Extract:
|
||||
tuple, err := p.getValue(instr.Tuple, locals)
|
||||
if err != nil {
|
||||
return i, err
|
||||
}
|
||||
locals[instr] = tuple.(*StructValue).Fields[instr.Index]
|
||||
case *ssa.FieldAddr:
|
||||
x, err := p.getValue(instr.X, locals)
|
||||
if err != nil {
|
||||
return i, err
|
||||
}
|
||||
var structVal *StructValue
|
||||
switch x := x.(type) {
|
||||
case *GlobalValue:
|
||||
structVal = x.Global.initializer.(*StructValue)
|
||||
case *PointerValue:
|
||||
structVal = (*x.Elem).(*StructValue)
|
||||
default:
|
||||
panic("expected a pointer")
|
||||
}
|
||||
locals[instr] = &PointerValue{nil, &structVal.Fields[instr.Field]}
|
||||
case *ssa.IndexAddr:
|
||||
x, err := p.getValue(instr.X, locals)
|
||||
if err != nil {
|
||||
return i, err
|
||||
}
|
||||
if cnst, ok := instr.Index.(*ssa.Const); ok {
|
||||
index, _ := constant.Int64Val(cnst.Value)
|
||||
switch xPtr := x.(type) {
|
||||
case *GlobalValue:
|
||||
x = xPtr.Global.initializer
|
||||
case *PointerValue:
|
||||
x = *xPtr.Elem
|
||||
default:
|
||||
panic("expected a pointer")
|
||||
}
|
||||
switch x := x.(type) {
|
||||
case *ArrayValue:
|
||||
locals[instr] = &PointerValue{nil, &x.Elems[index]}
|
||||
default:
|
||||
return i, errors.New("todo: init IndexAddr not on an array or struct")
|
||||
}
|
||||
} else {
|
||||
return i, errors.New("todo: init IndexAddr index: " + instr.Index.String())
|
||||
}
|
||||
case *ssa.MakeMap:
|
||||
locals[instr] = &MapValue{instr.Type().Underlying().(*types.Map), nil, nil}
|
||||
case *ssa.MapUpdate:
|
||||
// Assume no duplicate keys exist. This is most likely true for
|
||||
// autogenerated code, but may not be true when trying to interpret
|
||||
// user code.
|
||||
key, err := p.getValue(instr.Key, locals)
|
||||
if err != nil {
|
||||
return i, err
|
||||
}
|
||||
value, err := p.getValue(instr.Value, locals)
|
||||
if err != nil {
|
||||
return i, err
|
||||
}
|
||||
x := locals[instr.Map].(*MapValue)
|
||||
x.Keys = append(x.Keys, key)
|
||||
x.Values = append(x.Values, value)
|
||||
case *ssa.Return:
|
||||
for i, r := range instr.Results {
|
||||
val, err := p.getValue(r, locals)
|
||||
if err != nil {
|
||||
return i, err
|
||||
}
|
||||
results[i] = val
|
||||
}
|
||||
case *ssa.Slice:
|
||||
// Turn a just-allocated array into a slice.
|
||||
if instr.Low != nil || instr.High != nil || instr.Max != nil {
|
||||
return i, errors.New("init: slice expression with bounds")
|
||||
}
|
||||
source, err := p.getValue(instr.X, locals)
|
||||
if err != nil {
|
||||
return i, err
|
||||
}
|
||||
switch source := source.(type) {
|
||||
case *PointerValue: // pointer to array
|
||||
array := (*source.Elem).(*ArrayValue)
|
||||
locals[instr] = &SliceValue{instr.Type().Underlying().(*types.Slice), array}
|
||||
default:
|
||||
return i, errors.New("init: unknown slice type")
|
||||
}
|
||||
case *ssa.Store:
|
||||
if addr, ok := instr.Addr.(*ssa.Global); ok {
|
||||
if strings.HasPrefix(instr.Addr.Name(), "__cgofn__cgo_") || strings.HasPrefix(instr.Addr.Name(), "_cgo_") {
|
||||
// Ignore CGo global variables which we don't use.
|
||||
continue
|
||||
}
|
||||
value, err := p.getValue(instr.Val, locals)
|
||||
if err != nil {
|
||||
return i, err
|
||||
}
|
||||
p.GetGlobal(addr).initializer = value
|
||||
} else if addr, ok := locals[instr.Addr]; ok {
|
||||
value, err := p.getValue(instr.Val, locals)
|
||||
if err != nil {
|
||||
return i, err
|
||||
}
|
||||
if addr, ok := addr.(*PointerValue); ok {
|
||||
*(addr.Elem) = value
|
||||
} else {
|
||||
panic("store to non-pointer")
|
||||
}
|
||||
} else {
|
||||
return i, errors.New("todo: init Store: " + instr.String())
|
||||
}
|
||||
case *ssa.UnOp:
|
||||
if instr.Op != token.MUL || instr.CommaOk {
|
||||
return i, errors.New("init: unknown unop: " + instr.String())
|
||||
}
|
||||
valPtr, err := p.getValue(instr.X, locals)
|
||||
if err != nil {
|
||||
return i, err
|
||||
}
|
||||
switch valPtr := valPtr.(type) {
|
||||
case *GlobalValue:
|
||||
locals[instr] = valPtr.Global.initializer
|
||||
case *PointerValue:
|
||||
locals[instr] = *valPtr.Elem
|
||||
default:
|
||||
panic("expected a pointer")
|
||||
}
|
||||
default:
|
||||
return i, nil
|
||||
}
|
||||
}
|
||||
return len(instrs), nil
|
||||
}
|
||||
|
||||
// Check whether this function can be interpreted at compile time. For that, it
|
||||
// needs to only contain relatively simple instructions (for example, no control
|
||||
// flow).
|
||||
func canInterpret(callee *ssa.Function) bool {
|
||||
if len(callee.Blocks) != 1 || callee.Signature.Results().Len() != 1 {
|
||||
// No control flow supported so only one basic block.
|
||||
// Only exactly one return value supported right now so check that as
|
||||
// well.
|
||||
return false
|
||||
}
|
||||
for _, instr := range callee.Blocks[0].Instrs {
|
||||
switch instr.(type) {
|
||||
// Ignore all functions fully supported by Program.interpret()
|
||||
// above.
|
||||
case *ssa.Alloc:
|
||||
case *ssa.ChangeType:
|
||||
case *ssa.DebugRef:
|
||||
case *ssa.Extract:
|
||||
case *ssa.FieldAddr:
|
||||
case *ssa.IndexAddr:
|
||||
case *ssa.MakeMap:
|
||||
case *ssa.MapUpdate:
|
||||
case *ssa.Return:
|
||||
case *ssa.Slice:
|
||||
case *ssa.Store:
|
||||
case *ssa.UnOp:
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func (p *Program) getValue(value ssa.Value, locals map[ssa.Value]Value) (Value, error) {
|
||||
switch value := value.(type) {
|
||||
case *ssa.Const:
|
||||
return &ConstValue{value}, nil
|
||||
case *ssa.Function:
|
||||
return &FunctionValue{value.Type(), value}, nil
|
||||
case *ssa.Global:
|
||||
if strings.HasPrefix(value.Name(), "__cgofn__cgo_") || strings.HasPrefix(value.Name(), "_cgo_") {
|
||||
// Ignore CGo global variables which we don't use.
|
||||
return nil, ErrCGoWrapper
|
||||
}
|
||||
g := p.GetGlobal(value)
|
||||
if g.initializer == nil {
|
||||
value, err := p.getZeroValue(value.Type().Underlying().(*types.Pointer).Elem())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
g.initializer = value
|
||||
}
|
||||
return &GlobalValue{g}, nil
|
||||
default:
|
||||
if local, ok := locals[value]; ok {
|
||||
return local, nil
|
||||
} else {
|
||||
return nil, errors.New("todo: init: unknown value: " + value.String())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (p *Program) getZeroValue(t types.Type) (Value, error) {
|
||||
switch typ := t.Underlying().(type) {
|
||||
case *types.Array:
|
||||
elems := make([]Value, typ.Len())
|
||||
for i := range elems {
|
||||
elem, err := p.getZeroValue(typ.Elem())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
elems[i] = elem
|
||||
}
|
||||
return &ArrayValue{typ.Elem(), elems}, nil
|
||||
case *types.Basic:
|
||||
return &ZeroBasicValue{typ}, nil
|
||||
case *types.Signature:
|
||||
return &FunctionValue{typ, nil}, nil
|
||||
case *types.Map:
|
||||
return &MapValue{typ, nil, nil}, nil
|
||||
case *types.Pointer:
|
||||
return &PointerValue{typ, nil}, nil
|
||||
case *types.Struct:
|
||||
elems := make([]Value, typ.NumFields())
|
||||
for i := range elems {
|
||||
elem, err := p.getZeroValue(typ.Field(i).Type())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
elems[i] = elem
|
||||
}
|
||||
return &StructValue{t, elems}, nil
|
||||
case *types.Slice:
|
||||
return &SliceValue{typ, nil}, nil
|
||||
default:
|
||||
return nil, errors.New("todo: init: unknown global type: " + typ.String())
|
||||
}
|
||||
}
|
||||
|
||||
// Boxed value for interpreter.
|
||||
type Value interface {
|
||||
}
|
||||
|
||||
type ConstValue struct {
|
||||
Expr *ssa.Const
|
||||
}
|
||||
|
||||
type ZeroBasicValue struct {
|
||||
Type *types.Basic
|
||||
}
|
||||
|
||||
type PointerValue struct {
|
||||
Type types.Type
|
||||
Elem *Value
|
||||
}
|
||||
|
||||
type FunctionValue struct {
|
||||
Type types.Type
|
||||
Elem *ssa.Function
|
||||
}
|
||||
|
||||
type PointerBitCastValue struct {
|
||||
Type types.Type
|
||||
Elem Value
|
||||
}
|
||||
|
||||
type PointerToUintptrValue struct {
|
||||
Elem Value
|
||||
}
|
||||
|
||||
type GlobalValue struct {
|
||||
Global *Global
|
||||
}
|
||||
|
||||
type ArrayValue struct {
|
||||
ElemType types.Type
|
||||
Elems []Value
|
||||
}
|
||||
|
||||
type StructValue struct {
|
||||
Type types.Type // types.Struct or types.Named
|
||||
Fields []Value
|
||||
}
|
||||
|
||||
type SliceValue struct {
|
||||
Type *types.Slice
|
||||
Array *ArrayValue
|
||||
}
|
||||
|
||||
type MapValue struct {
|
||||
Type *types.Map
|
||||
Keys []Value
|
||||
Values []Value
|
||||
}
|
||||
@@ -33,21 +33,21 @@ type Program struct {
|
||||
type Function struct {
|
||||
*ssa.Function
|
||||
LLVMFn llvm.Value
|
||||
linkName string // go:linkname, go:export, go:interrupt
|
||||
exported bool // go:export
|
||||
nobounds bool // go:nobounds
|
||||
flag bool // used by dead code elimination
|
||||
interrupt bool // go:interrupt
|
||||
linkName string // go:linkname, go:export, go:interrupt
|
||||
exported bool // go:export
|
||||
nobounds bool // go:nobounds
|
||||
flag bool // used by dead code elimination
|
||||
interrupt bool // go:interrupt
|
||||
inline InlineType // go:inline
|
||||
}
|
||||
|
||||
// Global variable, possibly constant.
|
||||
type Global struct {
|
||||
*ssa.Global
|
||||
program *Program
|
||||
LLVMGlobal llvm.Value
|
||||
linkName string // go:extern
|
||||
extern bool // go:extern
|
||||
initializer Value
|
||||
program *Program
|
||||
LLVMGlobal llvm.Value
|
||||
linkName string // go:extern
|
||||
extern bool // go:extern
|
||||
}
|
||||
|
||||
// Type with a name and possibly methods.
|
||||
@@ -70,7 +70,22 @@ type Interface struct {
|
||||
Type *types.Interface
|
||||
}
|
||||
|
||||
// Create and intialize a new *Program from a *ssa.Program.
|
||||
type InlineType int
|
||||
|
||||
// How much to inline.
|
||||
const (
|
||||
// Default behavior. The compiler decides for itself whether any given
|
||||
// function will be inlined. Whether any function is inlined depends on the
|
||||
// optimization level.
|
||||
InlineDefault InlineType = iota
|
||||
|
||||
// Inline hint, just like the C inline keyword (signalled using
|
||||
// //go:inline). The compiler will be more likely to inline this function,
|
||||
// but it is not a guarantee.
|
||||
InlineHint
|
||||
)
|
||||
|
||||
// Create and initialize a new *Program from a *ssa.Program.
|
||||
func NewProgram(lprogram *loader.Program, mainPath string) *Program {
|
||||
comments := map[string]*ast.CommentGroup{}
|
||||
for _, pkgInfo := range lprogram.Sorted() {
|
||||
@@ -188,9 +203,6 @@ func NewProgram(lprogram *loader.Program, mainPath string) *Program {
|
||||
func (p *Program) AddPackage(pkg *ssa.Package) {
|
||||
memberNames := make([]string, 0)
|
||||
for name := range pkg.Members {
|
||||
if isCGoInternal(name) {
|
||||
continue
|
||||
}
|
||||
memberNames = append(memberNames, name)
|
||||
}
|
||||
sort.Strings(memberNames)
|
||||
@@ -199,9 +211,6 @@ func (p *Program) AddPackage(pkg *ssa.Package) {
|
||||
member := pkg.Members[name]
|
||||
switch member := member.(type) {
|
||||
case *ssa.Function:
|
||||
if isCGoInternal(member.Name()) {
|
||||
continue
|
||||
}
|
||||
p.addFunction(member)
|
||||
case *ssa.Type:
|
||||
t := &NamedType{Type: member}
|
||||
@@ -269,10 +278,16 @@ func (f *Function) parsePragmas() {
|
||||
}
|
||||
if decl, ok := f.Syntax().(*ast.FuncDecl); ok && decl.Doc != nil {
|
||||
for _, comment := range decl.Doc.List {
|
||||
if !strings.HasPrefix(comment.Text, "//go:") {
|
||||
text := comment.Text
|
||||
if strings.HasPrefix(text, "//export ") {
|
||||
// Rewrite '//export' to '//go:export' for compatibility with
|
||||
// gc.
|
||||
text = "//go:" + text[2:]
|
||||
}
|
||||
if !strings.HasPrefix(text, "//go:") {
|
||||
continue
|
||||
}
|
||||
parts := strings.Fields(comment.Text)
|
||||
parts := strings.Fields(text)
|
||||
switch parts[0] {
|
||||
case "//go:export":
|
||||
if len(parts) != 2 {
|
||||
@@ -280,6 +295,8 @@ func (f *Function) parsePragmas() {
|
||||
}
|
||||
f.linkName = parts[1]
|
||||
f.exported = true
|
||||
case "//go:inline":
|
||||
f.inline = InlineHint
|
||||
case "//go:interrupt":
|
||||
if len(parts) != 2 {
|
||||
continue
|
||||
@@ -322,7 +339,7 @@ func (f *Function) IsNoBounds() bool {
|
||||
|
||||
// Return true iff this function is externally visible.
|
||||
func (f *Function) IsExported() bool {
|
||||
return f.exported
|
||||
return f.exported || f.CName() != ""
|
||||
}
|
||||
|
||||
// Return true for functions annotated with //go:interrupt. The function name is
|
||||
@@ -330,7 +347,12 @@ func (f *Function) IsExported() bool {
|
||||
//
|
||||
// On some platforms (like AVR), interrupts need a special compiler flag.
|
||||
func (f *Function) IsInterrupt() bool {
|
||||
return f.exported
|
||||
return f.interrupt
|
||||
}
|
||||
|
||||
// Return the inline directive of this function.
|
||||
func (f *Function) Inline() InlineType {
|
||||
return f.inline
|
||||
}
|
||||
|
||||
// Return the link name for this function.
|
||||
@@ -389,15 +411,25 @@ func (g *Global) LinkName() string {
|
||||
if g.linkName != "" {
|
||||
return g.linkName
|
||||
}
|
||||
if name := g.CName(); name != "" {
|
||||
return name
|
||||
}
|
||||
return g.RelString(nil)
|
||||
}
|
||||
|
||||
func (g *Global) IsExtern() bool {
|
||||
return g.extern
|
||||
return g.extern || g.CName() != ""
|
||||
}
|
||||
|
||||
func (g *Global) Initializer() Value {
|
||||
return g.initializer
|
||||
// Return the name of the C global if this is a CGo wrapper. Otherwise, return a
|
||||
// zero-length string.
|
||||
func (g *Global) CName() string {
|
||||
name := g.Name()
|
||||
if strings.HasPrefix(name, "C.") {
|
||||
// created by ../loader/cgo.go
|
||||
return name[2:]
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// Return true if this named type is annotated with the //go:volatile pragma,
|
||||
@@ -423,18 +455,6 @@ func (p *Program) IsVolatile(t types.Type) bool {
|
||||
}
|
||||
}
|
||||
|
||||
// Return true if this is a CGo-internal function that can be ignored.
|
||||
func isCGoInternal(name string) bool {
|
||||
if strings.HasPrefix(name, "_Cgo_") || strings.HasPrefix(name, "_cgo") {
|
||||
// _Cgo_ptr, _Cgo_use, _cgoCheckResult, _cgo_runtime_cgocall
|
||||
return true // CGo-internal functions
|
||||
}
|
||||
if strings.HasPrefix(name, "__cgofn__cgo_") {
|
||||
return true // CGo function pointer in global scope
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// Get all methods of a type.
|
||||
func getAllMethods(prog *ssa.Program, typ types.Type) []*types.Selection {
|
||||
ms := prog.MethodSets.MethodSet(typ)
|
||||
|
||||
+4
-3
@@ -68,11 +68,12 @@ func (p *Program) SimpleDCE() {
|
||||
// functions.
|
||||
main := p.mainPkg.Members["main"].(*ssa.Function)
|
||||
runtimePkg := p.Program.ImportedPackage("runtime")
|
||||
mathPkg := p.Program.ImportedPackage("math")
|
||||
p.GetFunction(main).flag = true
|
||||
worklist := []*ssa.Function{main}
|
||||
for _, f := range p.Functions {
|
||||
if f.exported || f.Synthetic == "package initializer" || f.Pkg == runtimePkg {
|
||||
if f.flag || isCGoInternal(f.Name()) {
|
||||
if f.exported || f.Synthetic == "package initializer" || f.Pkg == runtimePkg || (f.Pkg == mathPkg && f.Pkg != nil) {
|
||||
if f.flag {
|
||||
continue
|
||||
}
|
||||
f.flag = true
|
||||
@@ -102,7 +103,7 @@ func (p *Program) SimpleDCE() {
|
||||
}
|
||||
}
|
||||
for _, operand := range instr.Operands(nil) {
|
||||
if operand == nil || *operand == nil || isCGoInternal((*operand).Name()) {
|
||||
if operand == nil || *operand == nil {
|
||||
continue
|
||||
}
|
||||
switch operand := (*operand).(type) {
|
||||
|
||||
+1
-1
Submodule lib/compiler-rt updated: a4cbb02bca...5bc79797e1
+6
-4
@@ -22,9 +22,9 @@ 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"]:
|
||||
case "ld.lld":
|
||||
flags = append([]string{"tinygo:" + linker}, flags...)
|
||||
var cflag *C.char
|
||||
buf := C.calloc(C.size_t(len(flags)), C.size_t(unsafe.Sizeof(cflag)))
|
||||
@@ -39,7 +39,7 @@ func Link(dir, linker string, flags ...string) error {
|
||||
return errors.New("failed to link using built-in ld.lld")
|
||||
}
|
||||
return nil
|
||||
case "wasm-ld", commands["wasm-ld"]:
|
||||
case "wasm-ld":
|
||||
flags = append([]string{"tinygo:" + linker}, flags...)
|
||||
var cflag *C.char
|
||||
buf := C.calloc(C.size_t(len(flags)), C.size_t(unsafe.Sizeof(cflag)))
|
||||
@@ -57,10 +57,12 @@ func Link(dir, linker string, flags ...string) error {
|
||||
return nil
|
||||
default:
|
||||
// Fall back to external command.
|
||||
if cmdNames, ok := commands[linker]; ok {
|
||||
return execCommand(cmdNames, flags...)
|
||||
}
|
||||
cmd := exec.Command(linker, flags...)
|
||||
cmd.Stdout = os.Stdout
|
||||
cmd.Stderr = os.Stderr
|
||||
cmd.Dir = dir
|
||||
return cmd.Run()
|
||||
}
|
||||
}
|
||||
|
||||
+4
-2
@@ -13,10 +13,12 @@ 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 {
|
||||
if cmdNames, ok := commands[linker]; ok {
|
||||
return execCommand(cmdNames, flags...)
|
||||
}
|
||||
cmd := exec.Command(linker, flags...)
|
||||
cmd.Stdout = os.Stdout
|
||||
cmd.Stderr = os.Stderr
|
||||
cmd.Dir = dir
|
||||
return cmd.Run()
|
||||
}
|
||||
|
||||
-362
@@ -1,362 +0,0 @@
|
||||
package loader
|
||||
|
||||
// This file extracts the `import "C"` statement from the source and modifies
|
||||
// the AST for Cgo. It does not use libclang directly (see libclang.go).
|
||||
|
||||
import (
|
||||
"go/ast"
|
||||
"go/token"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// fileInfo holds all Cgo-related information of a given *ast.File.
|
||||
type fileInfo struct {
|
||||
*ast.File
|
||||
filename string
|
||||
functions []*functionInfo
|
||||
typedefs []*typedefInfo
|
||||
importCPos token.Pos
|
||||
}
|
||||
|
||||
// functionInfo stores some information about a Cgo function found by libclang
|
||||
// and declared in the AST.
|
||||
type functionInfo struct {
|
||||
name string
|
||||
args []paramInfo
|
||||
result string
|
||||
}
|
||||
|
||||
// paramInfo is a parameter of a Cgo function (see functionInfo).
|
||||
type paramInfo struct {
|
||||
name string
|
||||
typeName string
|
||||
}
|
||||
|
||||
// typedefInfo contains information about a single typedef in C.
|
||||
type typedefInfo struct {
|
||||
newName string // newly defined type name
|
||||
oldName string // old type name, may be something like "unsigned long long"
|
||||
size int // size in bytes
|
||||
}
|
||||
|
||||
// cgoAliases list type aliases between Go and C, for types that are equivalent
|
||||
// in both languages. See addTypeAliases.
|
||||
var cgoAliases = map[string]string{
|
||||
"C.int8_t": "int8",
|
||||
"C.int16_t": "int16",
|
||||
"C.int32_t": "int32",
|
||||
"C.int64_t": "int64",
|
||||
"C.uint8_t": "uint8",
|
||||
"C.uint16_t": "uint16",
|
||||
"C.uint32_t": "uint32",
|
||||
"C.uint64_t": "uint64",
|
||||
"C.uintptr_t": "uintptr",
|
||||
}
|
||||
|
||||
// cgoTypes lists some C types with ambiguous sizes that must be retrieved
|
||||
// somehow from C. This is done by adding some typedefs to get the size of each
|
||||
// type.
|
||||
const cgoTypes = `
|
||||
typedef signed char _Cgo_schar;
|
||||
typedef unsigned char _Cgo_uchar;
|
||||
typedef short _Cgo_short;
|
||||
typedef unsigned short _Cgo_ushort;
|
||||
typedef int _Cgo_int;
|
||||
typedef unsigned int _Cgo_uint;
|
||||
typedef long _Cgo_long;
|
||||
typedef unsigned long _Cgo_ulong;
|
||||
typedef long long _Cgo_longlong;
|
||||
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 {
|
||||
info := &fileInfo{
|
||||
File: f,
|
||||
filename: filename,
|
||||
}
|
||||
|
||||
// Find `import "C"` statements in the file.
|
||||
for i := 0; i < len(f.Decls); i++ {
|
||||
decl := f.Decls[i]
|
||||
genDecl, ok := decl.(*ast.GenDecl)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
if len(genDecl.Specs) != 1 {
|
||||
continue
|
||||
}
|
||||
spec, ok := genDecl.Specs[0].(*ast.ImportSpec)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
path, err := strconv.Unquote(spec.Path.Value)
|
||||
if err != nil {
|
||||
panic("could not parse import path: " + err.Error())
|
||||
}
|
||||
if path != "C" {
|
||||
continue
|
||||
}
|
||||
cgoComment := genDecl.Doc.Text()
|
||||
|
||||
// Stored for later use by generated functions, to use a somewhat sane
|
||||
// source location.
|
||||
info.importCPos = spec.Path.ValuePos
|
||||
|
||||
err = info.parseFragment(cgoComment+cgoTypes, cflags)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Remove this import declaration.
|
||||
f.Decls = append(f.Decls[:i], f.Decls[i+1:]...)
|
||||
i--
|
||||
}
|
||||
|
||||
// Print the AST, for debugging.
|
||||
//ast.Print(p.fset, f)
|
||||
|
||||
// Declare functions found by libclang.
|
||||
info.addFuncDecls()
|
||||
|
||||
// Forward C types to Go types (like C.uint32_t -> uint32).
|
||||
info.addTypeAliases()
|
||||
|
||||
// Add type declarations for C types, declared using typeef in C.
|
||||
info.addTypedefs()
|
||||
|
||||
// Patch the AST to use the declared types and functions.
|
||||
ast.Inspect(f, info.walker)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// addFuncDecls adds the C function declarations found by libclang in the
|
||||
// comment above the `import "C"` statement.
|
||||
func (info *fileInfo) addFuncDecls() {
|
||||
// TODO: replace all uses of importCPos with the real locations from
|
||||
// libclang.
|
||||
for _, fn := range info.functions {
|
||||
obj := &ast.Object{
|
||||
Kind: ast.Fun,
|
||||
Name: mapCgoType(fn.name),
|
||||
}
|
||||
args := make([]*ast.Field, len(fn.args))
|
||||
decl := &ast.FuncDecl{
|
||||
Name: &ast.Ident{
|
||||
NamePos: info.importCPos,
|
||||
Name: mapCgoType(fn.name),
|
||||
Obj: obj,
|
||||
},
|
||||
Type: &ast.FuncType{
|
||||
Func: info.importCPos,
|
||||
Params: &ast.FieldList{
|
||||
Opening: info.importCPos,
|
||||
List: args,
|
||||
Closing: info.importCPos,
|
||||
},
|
||||
Results: &ast.FieldList{
|
||||
List: []*ast.Field{
|
||||
&ast.Field{
|
||||
Type: &ast.Ident{
|
||||
NamePos: info.importCPos,
|
||||
Name: mapCgoType(fn.result),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
obj.Decl = decl
|
||||
for i, arg := range fn.args {
|
||||
args[i] = &ast.Field{
|
||||
Names: []*ast.Ident{
|
||||
&ast.Ident{
|
||||
NamePos: info.importCPos,
|
||||
Name: arg.name,
|
||||
Obj: &ast.Object{
|
||||
Kind: ast.Var,
|
||||
Name: mapCgoType(arg.name),
|
||||
Decl: decl,
|
||||
},
|
||||
},
|
||||
},
|
||||
Type: &ast.Ident{
|
||||
NamePos: info.importCPos,
|
||||
Name: mapCgoType(arg.typeName),
|
||||
},
|
||||
}
|
||||
}
|
||||
info.Decls = append(info.Decls, decl)
|
||||
}
|
||||
}
|
||||
|
||||
// addTypeAliases aliases some built-in Go types with their equivalent C types.
|
||||
// It adds code like the following to the AST:
|
||||
//
|
||||
// type (
|
||||
// C.int8_t = int8
|
||||
// C.int16_t = int16
|
||||
// // ...
|
||||
// )
|
||||
func (info *fileInfo) addTypeAliases() {
|
||||
aliasKeys := make([]string, 0, len(cgoAliases))
|
||||
for key := range cgoAliases {
|
||||
aliasKeys = append(aliasKeys, key)
|
||||
}
|
||||
sort.Strings(aliasKeys)
|
||||
gen := &ast.GenDecl{
|
||||
TokPos: info.importCPos,
|
||||
Tok: token.TYPE,
|
||||
Lparen: info.importCPos,
|
||||
Rparen: info.importCPos,
|
||||
}
|
||||
for _, typeName := range aliasKeys {
|
||||
goTypeName := cgoAliases[typeName]
|
||||
obj := &ast.Object{
|
||||
Kind: ast.Typ,
|
||||
Name: typeName,
|
||||
}
|
||||
typeSpec := &ast.TypeSpec{
|
||||
Name: &ast.Ident{
|
||||
NamePos: info.importCPos,
|
||||
Name: typeName,
|
||||
Obj: obj,
|
||||
},
|
||||
Assign: info.importCPos,
|
||||
Type: &ast.Ident{
|
||||
NamePos: info.importCPos,
|
||||
Name: goTypeName,
|
||||
},
|
||||
}
|
||||
obj.Decl = typeSpec
|
||||
gen.Specs = append(gen.Specs, typeSpec)
|
||||
}
|
||||
info.Decls = append(info.Decls, gen)
|
||||
}
|
||||
|
||||
func (info *fileInfo) addTypedefs() {
|
||||
gen := &ast.GenDecl{
|
||||
TokPos: info.importCPos,
|
||||
Tok: token.TYPE,
|
||||
}
|
||||
for _, typedef := range info.typedefs {
|
||||
newType := mapCgoType(typedef.newName)
|
||||
oldType := mapCgoType(typedef.oldName)
|
||||
switch oldType {
|
||||
// TODO: plain char (may be signed or unsigned)
|
||||
case "C.schar", "C.short", "C.int", "C.long", "C.longlong":
|
||||
switch typedef.size {
|
||||
case 1:
|
||||
oldType = "int8"
|
||||
case 2:
|
||||
oldType = "int16"
|
||||
case 4:
|
||||
oldType = "int32"
|
||||
case 8:
|
||||
oldType = "int64"
|
||||
}
|
||||
case "C.uchar", "C.ushort", "C.uint", "C.ulong", "C.ulonglong":
|
||||
switch typedef.size {
|
||||
case 1:
|
||||
oldType = "uint8"
|
||||
case 2:
|
||||
oldType = "uint16"
|
||||
case 4:
|
||||
oldType = "uint32"
|
||||
case 8:
|
||||
oldType = "uint64"
|
||||
}
|
||||
}
|
||||
if strings.HasPrefix(newType, "C._Cgo_") {
|
||||
newType = "C." + newType[len("C._Cgo_"):]
|
||||
}
|
||||
if _, ok := cgoAliases[newType]; ok {
|
||||
// This is a type that also exists in Go (defined in stdint.h).
|
||||
continue
|
||||
}
|
||||
obj := &ast.Object{
|
||||
Kind: ast.Typ,
|
||||
Name: newType,
|
||||
}
|
||||
typeSpec := &ast.TypeSpec{
|
||||
Name: &ast.Ident{
|
||||
NamePos: info.importCPos,
|
||||
Name: newType,
|
||||
Obj: obj,
|
||||
},
|
||||
Type: &ast.Ident{
|
||||
NamePos: info.importCPos,
|
||||
Name: oldType,
|
||||
},
|
||||
}
|
||||
obj.Decl = typeSpec
|
||||
gen.Specs = append(gen.Specs, typeSpec)
|
||||
}
|
||||
info.Decls = append(info.Decls, gen)
|
||||
}
|
||||
|
||||
// walker replaces all "C".<something> call expressions to literal
|
||||
// "C.<something>" expressions. This is impossible to write in Go (a dot cannot
|
||||
// be used in the middle of a name) so is used as a new namespace for C call
|
||||
// expressions.
|
||||
func (info *fileInfo) walker(node ast.Node) bool {
|
||||
switch node := node.(type) {
|
||||
case *ast.CallExpr:
|
||||
fun, ok := node.Fun.(*ast.SelectorExpr)
|
||||
if !ok {
|
||||
return true
|
||||
}
|
||||
x, ok := fun.X.(*ast.Ident)
|
||||
if !ok {
|
||||
return true
|
||||
}
|
||||
if x.Name == "C" {
|
||||
node.Fun = &ast.Ident{
|
||||
NamePos: x.NamePos,
|
||||
Name: mapCgoType(fun.Sel.Name),
|
||||
}
|
||||
}
|
||||
case *ast.ValueSpec:
|
||||
typ, ok := node.Type.(*ast.SelectorExpr)
|
||||
if !ok {
|
||||
return true
|
||||
}
|
||||
x, ok := typ.X.(*ast.Ident)
|
||||
if !ok {
|
||||
return true
|
||||
}
|
||||
if x.Name == "C" {
|
||||
node.Type = &ast.Ident{
|
||||
NamePos: x.NamePos,
|
||||
Name: mapCgoType(typ.Sel.Name),
|
||||
}
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// mapCgoType converts a C type name into a Go type name with a "C." prefix.
|
||||
func mapCgoType(t string) string {
|
||||
switch t {
|
||||
case "signed char":
|
||||
return "C.schar"
|
||||
case "long long":
|
||||
return "C.longlong"
|
||||
case "unsigned char":
|
||||
return "C.schar"
|
||||
case "unsigned short":
|
||||
return "C.ushort"
|
||||
case "unsigned int":
|
||||
return "C.uint"
|
||||
case "unsigned long":
|
||||
return "C.ulong"
|
||||
case "unsigned long long":
|
||||
return "C.ulonglong"
|
||||
default:
|
||||
return "C." + t
|
||||
}
|
||||
}
|
||||
@@ -1,124 +0,0 @@
|
||||
package loader
|
||||
|
||||
// This file parses a fragment of C with libclang and stores the result for AST
|
||||
// modification. It does not touch the AST itself.
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
/*
|
||||
#include <clang-c/Index.h> // if this fails, install libclang-7-dev
|
||||
#include <stdlib.h>
|
||||
|
||||
int tinygo_clang_visitor(CXCursor c, CXCursor parent, CXClientData client_data);
|
||||
*/
|
||||
import "C"
|
||||
|
||||
var globalFileInfo *fileInfo
|
||||
|
||||
func (info *fileInfo) parseFragment(fragment string, cflags []string) error {
|
||||
index := C.clang_createIndex(0, 1)
|
||||
defer C.clang_disposeIndex(index)
|
||||
|
||||
filenameC := C.CString("cgo-fake.c")
|
||||
defer C.free(unsafe.Pointer(filenameC))
|
||||
|
||||
fragmentC := C.CString(fragment)
|
||||
defer C.free(unsafe.Pointer(fragmentC))
|
||||
|
||||
unsavedFile := C.struct_CXUnsavedFile{
|
||||
Filename: filenameC,
|
||||
Length: C.ulong(len(fragment)),
|
||||
Contents: fragmentC,
|
||||
}
|
||||
|
||||
// convert Go slice of strings to C array of strings.
|
||||
cmdargsC := C.malloc(C.size_t(len(cflags)) * C.size_t(unsafe.Sizeof(uintptr(0))))
|
||||
defer C.free(cmdargsC)
|
||||
cmdargs := (*[1 << 16]*C.char)(cmdargsC)
|
||||
for i, cflag := range cflags {
|
||||
s := C.CString(cflag)
|
||||
cmdargs[i] = s
|
||||
defer C.free(unsafe.Pointer(s))
|
||||
}
|
||||
|
||||
var unit C.CXTranslationUnit
|
||||
errCode := C.clang_parseTranslationUnit2(
|
||||
index,
|
||||
filenameC,
|
||||
(**C.char)(cmdargsC), C.int(len(cflags)), // command line args
|
||||
&unsavedFile, 1, // unsaved files
|
||||
C.CXTranslationUnit_None,
|
||||
&unit)
|
||||
if errCode != 0 {
|
||||
panic("loader: failed to parse source with libclang")
|
||||
}
|
||||
defer C.clang_disposeTranslationUnit(unit)
|
||||
|
||||
if C.clang_getNumDiagnostics(unit) != 0 {
|
||||
return errors.New("cgo: libclang cannot parse fragment")
|
||||
}
|
||||
|
||||
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)))
|
||||
|
||||
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)
|
||||
switch kind {
|
||||
case C.CXCursor_FunctionDecl:
|
||||
name := getString(C.clang_getCursorSpelling(c))
|
||||
cursorType := C.clang_getCursorType(c)
|
||||
if C.clang_isFunctionTypeVariadic(cursorType) != 0 {
|
||||
return C.CXChildVisit_Continue // not supported
|
||||
}
|
||||
numArgs := C.clang_Cursor_getNumArguments(c)
|
||||
fn := &functionInfo{name: name}
|
||||
info.functions = append(info.functions, fn)
|
||||
for i := C.int(0); i < numArgs; i++ {
|
||||
arg := C.clang_Cursor_getArgument(c, C.uint(i))
|
||||
argName := getString(C.clang_getCursorSpelling(arg))
|
||||
argType := C.clang_getArgType(cursorType, C.uint(i))
|
||||
argTypeName := getString(C.clang_getTypeSpelling(argType))
|
||||
fn.args = append(fn.args, paramInfo{argName, argTypeName})
|
||||
}
|
||||
resultType := C.clang_getCursorResultType(c)
|
||||
resultTypeName := getString(C.clang_getTypeSpelling(resultType))
|
||||
fn.result = resultTypeName
|
||||
case C.CXCursor_TypedefDecl:
|
||||
typedefType := C.clang_getCursorType(c)
|
||||
name := getString(C.clang_getTypedefName(typedefType))
|
||||
underlyingType := C.clang_getTypedefDeclUnderlyingType(c)
|
||||
underlyingTypeName := getString(C.clang_getTypeSpelling(underlyingType))
|
||||
typeSize := C.clang_Type_getSizeOf(underlyingType)
|
||||
info.typedefs = append(info.typedefs, &typedefInfo{
|
||||
newName: name,
|
||||
oldName: underlyingTypeName,
|
||||
size: int(typeSize),
|
||||
})
|
||||
}
|
||||
return C.CXChildVisit_Continue
|
||||
}
|
||||
|
||||
func getString(clangString C.CXString) (s string) {
|
||||
rawString := C.clang_getCString(clangString)
|
||||
s = C.GoString(rawString)
|
||||
C.clang_disposeString(clangString)
|
||||
return
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
// +build !byollvm
|
||||
|
||||
package loader
|
||||
|
||||
/*
|
||||
#cgo CFLAGS: -I/usr/lib/llvm-7/include
|
||||
#cgo LDFLAGS: -L/usr/lib/llvm-7/lib -lclang
|
||||
*/
|
||||
import "C"
|
||||
+33
-14
@@ -10,17 +10,22 @@ import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
|
||||
"github.com/tinygo-org/tinygo/cgo"
|
||||
)
|
||||
|
||||
// Program holds all packages and some metadata about the program as a whole.
|
||||
type Program struct {
|
||||
Build *build.Context
|
||||
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)
|
||||
TINYGOROOT string // root of the TinyGo installation or root of the source code
|
||||
CFlags []string
|
||||
}
|
||||
|
||||
// Package holds a loaded package, its imports, and its parsed files.
|
||||
@@ -42,7 +47,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,13 +302,23 @@ 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)
|
||||
continue
|
||||
}
|
||||
files = append(files, f)
|
||||
}
|
||||
if len(p.CgoFiles) != 0 {
|
||||
clangIncludes := ""
|
||||
if _, err := os.Stat(filepath.Join(p.TINYGOROOT, "llvm", "tools", "clang", "lib", "Headers")); !os.IsNotExist(err) {
|
||||
// Running from the source directory.
|
||||
clangIncludes = filepath.Join(p.TINYGOROOT, "llvm", "tools", "clang", "lib", "Headers")
|
||||
} else {
|
||||
// Running from the installation directory.
|
||||
clangIncludes = filepath.Join(p.TINYGOROOT, "lib", "clang", "include")
|
||||
}
|
||||
generated, errs := cgo.Process(files, p.Program.Dir, p.fset, append(p.CFlags, "-I"+p.Package.Dir, "-I"+clangIncludes))
|
||||
if errs != nil {
|
||||
fileErrs = append(fileErrs, errs...)
|
||||
}
|
||||
files = append(files, generated)
|
||||
}
|
||||
if len(fileErrs) != 0 {
|
||||
return nil, Errors{p, fileErrs}
|
||||
}
|
||||
@@ -327,7 +346,7 @@ func (p *Package) importRecursively() error {
|
||||
p.Importing = true
|
||||
for _, to := range p.Package.Imports {
|
||||
if to == "C" {
|
||||
// Do Cgo processing in a later stage.
|
||||
// Do CGo processing in a later stage.
|
||||
continue
|
||||
}
|
||||
if _, ok := p.Imports[to]; ok {
|
||||
|
||||
@@ -11,6 +11,7 @@ import (
|
||||
"os/exec"
|
||||
"os/signal"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strconv"
|
||||
"strings"
|
||||
"syscall"
|
||||
@@ -20,13 +21,6 @@ import (
|
||||
"github.com/tinygo-org/tinygo/loader"
|
||||
)
|
||||
|
||||
var commands = map[string]string{
|
||||
"ar": "ar",
|
||||
"clang": "clang-7",
|
||||
"ld.lld": "ld.lld-7",
|
||||
"wasm-ld": "wasm-ld-7",
|
||||
}
|
||||
|
||||
// commandError is an error type to wrap os/exec.Command errors. This provides
|
||||
// some more information regarding what went wrong while running a command.
|
||||
type commandError struct {
|
||||
@@ -39,17 +33,27 @@ func (e *commandError) Error() string {
|
||||
return e.Msg + " " + e.File + ": " + e.Err.Error()
|
||||
}
|
||||
|
||||
// multiError is a list of multiple errors (actually: diagnostics) returned
|
||||
// during LLVM IR generation.
|
||||
type multiError struct {
|
||||
Errs []error
|
||||
}
|
||||
|
||||
func (e *multiError) Error() string {
|
||||
return e.Errs[0].Error()
|
||||
}
|
||||
|
||||
type BuildConfig struct {
|
||||
opt string
|
||||
gc string
|
||||
printIR bool
|
||||
dumpSSA bool
|
||||
debug bool
|
||||
printSizes string
|
||||
initInterp bool
|
||||
cFlags []string
|
||||
ldFlags []string
|
||||
wasmAbi string
|
||||
opt string
|
||||
gc string
|
||||
panicStrategy string
|
||||
printIR bool
|
||||
dumpSSA bool
|
||||
debug bool
|
||||
printSizes string
|
||||
cFlags []string
|
||||
ldFlags []string
|
||||
wasmAbi string
|
||||
}
|
||||
|
||||
// Helper function for Compiler object.
|
||||
@@ -58,24 +62,39 @@ func Compile(pkgName, outpath string, spec *TargetSpec, config *BuildConfig, act
|
||||
config.gc = spec.GC
|
||||
}
|
||||
|
||||
// Append command line passed CFlags and LDFlags
|
||||
spec.CFlags = append(spec.CFlags, config.cFlags...)
|
||||
spec.LDFlags = append(spec.LDFlags, config.ldFlags...)
|
||||
root := sourceDir()
|
||||
|
||||
// Merge and adjust CFlags.
|
||||
cflags := append([]string{}, config.cFlags...)
|
||||
for _, flag := range spec.CFlags {
|
||||
cflags = append(cflags, strings.Replace(flag, "{root}", root, -1))
|
||||
}
|
||||
|
||||
// Merge and adjust LDFlags.
|
||||
ldflags := append([]string{}, config.ldFlags...)
|
||||
for _, flag := range spec.LDFlags {
|
||||
ldflags = append(ldflags, strings.Replace(flag, "{root}", root, -1))
|
||||
}
|
||||
|
||||
goroot := getGoroot()
|
||||
if goroot == "" {
|
||||
return errors.New("cannot locate $GOROOT, please set it manually")
|
||||
}
|
||||
compilerConfig := compiler.Config{
|
||||
Triple: spec.Triple,
|
||||
CPU: spec.CPU,
|
||||
GOOS: spec.GOOS,
|
||||
GOARCH: spec.GOARCH,
|
||||
GC: config.gc,
|
||||
CFlags: spec.CFlags,
|
||||
LDFlags: spec.LDFlags,
|
||||
Debug: config.debug,
|
||||
DumpSSA: config.dumpSSA,
|
||||
RootDir: sourceDir(),
|
||||
GOPATH: getGopath(),
|
||||
BuildTags: spec.BuildTags,
|
||||
InitInterp: config.initInterp,
|
||||
Triple: spec.Triple,
|
||||
CPU: spec.CPU,
|
||||
GOOS: spec.GOOS,
|
||||
GOARCH: spec.GOARCH,
|
||||
GC: config.gc,
|
||||
PanicStrategy: config.panicStrategy,
|
||||
CFlags: cflags,
|
||||
LDFlags: ldflags,
|
||||
Debug: config.debug,
|
||||
DumpSSA: config.dumpSSA,
|
||||
TINYGOROOT: root,
|
||||
GOROOT: goroot,
|
||||
GOPATH: getGopath(),
|
||||
BuildTags: spec.BuildTags,
|
||||
}
|
||||
c, err := compiler.NewCompiler(pkgName, compilerConfig)
|
||||
if err != nil {
|
||||
@@ -83,29 +102,32 @@ func Compile(pkgName, outpath string, spec *TargetSpec, config *BuildConfig, act
|
||||
}
|
||||
|
||||
// Compile Go code to IR.
|
||||
err = c.Compile(pkgName)
|
||||
if err != nil {
|
||||
return err
|
||||
errs := c.Compile(pkgName)
|
||||
if len(errs) != 0 {
|
||||
if len(errs) == 1 {
|
||||
return errs[0]
|
||||
}
|
||||
return &multiError{errs}
|
||||
}
|
||||
if config.printIR {
|
||||
fmt.Println("Generated LLVM IR:")
|
||||
fmt.Println("; Generated LLVM IR:")
|
||||
fmt.Println(c.IR())
|
||||
}
|
||||
if err := c.Verify(); err != nil {
|
||||
return errors.New("verification error after IR construction")
|
||||
}
|
||||
|
||||
if config.initInterp {
|
||||
err = interp.Run(c.Module(), c.TargetData(), config.dumpSSA)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := c.Verify(); err != nil {
|
||||
return errors.New("verification error after interpreting runtime.initAll")
|
||||
}
|
||||
err = interp.Run(c.Module(), c.TargetData(), config.dumpSSA)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := c.Verify(); err != nil {
|
||||
return errors.New("verification error after interpreting runtime.initAll")
|
||||
}
|
||||
|
||||
c.ApplyFunctionSections() // -ffunction-sections
|
||||
if spec.GOOS != "darwin" {
|
||||
c.ApplyFunctionSections() // -ffunction-sections
|
||||
}
|
||||
if err := c.Verify(); err != nil {
|
||||
return errors.New("verification error after applying function sections")
|
||||
}
|
||||
@@ -199,19 +221,20 @@ 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(ldflags, "-o", executable, objfile, "-L", root)
|
||||
if spec.RTLib == "compiler-rt" {
|
||||
ldflags = append(ldflags, librt)
|
||||
}
|
||||
|
||||
// Compile extra files.
|
||||
for i, path := range spec.ExtraFiles {
|
||||
abspath := filepath.Join(root, path)
|
||||
outpath := filepath.Join(dir, "extra-"+strconv.Itoa(i)+"-"+filepath.Base(path)+".o")
|
||||
cmd := exec.Command(spec.Compiler, append(spec.CFlags, "-c", "-o", outpath, path)...)
|
||||
cmd.Stdout = os.Stdout
|
||||
cmd.Stderr = os.Stderr
|
||||
cmd.Dir = sourceDir()
|
||||
err := cmd.Run()
|
||||
cmdNames := []string{spec.Compiler}
|
||||
if names, ok := commands[spec.Compiler]; ok {
|
||||
cmdNames = names
|
||||
}
|
||||
err := execCommand(cmdNames, append(cflags, "-c", "-o", outpath, abspath)...)
|
||||
if err != nil {
|
||||
return &commandError{"failed to build", path, err}
|
||||
}
|
||||
@@ -223,11 +246,11 @@ func Compile(pkgName, outpath string, spec *TargetSpec, config *BuildConfig, act
|
||||
for _, file := range pkg.CFiles {
|
||||
path := filepath.Join(pkg.Package.Dir, file)
|
||||
outpath := filepath.Join(dir, "pkg"+strconv.Itoa(i)+"-"+file+".o")
|
||||
cmd := exec.Command(spec.Compiler, append(spec.CFlags, "-c", "-o", outpath, path)...)
|
||||
cmd.Stdout = os.Stdout
|
||||
cmd.Stderr = os.Stderr
|
||||
cmd.Dir = sourceDir()
|
||||
err := cmd.Run()
|
||||
cmdNames := []string{spec.Compiler}
|
||||
if names, ok := commands[spec.Compiler]; ok {
|
||||
cmdNames = names
|
||||
}
|
||||
err := execCommand(cmdNames, append(cflags, "-c", "-o", outpath, path)...)
|
||||
if err != nil {
|
||||
return &commandError{"failed to build", path, err}
|
||||
}
|
||||
@@ -236,7 +259,7 @@ func Compile(pkgName, outpath string, spec *TargetSpec, config *BuildConfig, act
|
||||
}
|
||||
|
||||
// Link the object files together.
|
||||
err = Link(sourceDir(), spec.Linker, ldflags...)
|
||||
err = Link(spec.Linker, ldflags...)
|
||||
if err != nil {
|
||||
return &commandError{"failed to link", executable, err}
|
||||
}
|
||||
@@ -260,19 +283,19 @@ func Compile(pkgName, outpath string, spec *TargetSpec, config *BuildConfig, act
|
||||
}
|
||||
}
|
||||
|
||||
// Get an Intel .hex file or .bin file from the .elf file.
|
||||
if outext == ".hex" || outext == ".bin" {
|
||||
// Get an Intel .hex file or .bin file from the .elf file.
|
||||
tmppath = filepath.Join(dir, "main"+outext)
|
||||
format := map[string]string{
|
||||
".hex": "ihex",
|
||||
".bin": "binary",
|
||||
}[outext]
|
||||
cmd := exec.Command(spec.Objcopy, "-O", format, executable, tmppath)
|
||||
cmd.Stdout = os.Stdout
|
||||
cmd.Stderr = os.Stderr
|
||||
err = cmd.Run()
|
||||
err := Objcopy(executable, tmppath)
|
||||
if err != nil {
|
||||
return &commandError{"failed to extract " + format + " from", executable, err}
|
||||
return err
|
||||
}
|
||||
} else if outext == ".uf2" {
|
||||
// Get UF2 from the .elf file.
|
||||
tmppath = filepath.Join(dir, "main"+outext)
|
||||
err := ConvertELFFileToUF2File(executable, tmppath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return action(tmppath)
|
||||
@@ -319,14 +342,31 @@ func Flash(pkgName, target, port string, config *BuildConfig) error {
|
||||
return err
|
||||
}
|
||||
|
||||
return Compile(pkgName, ".hex", spec, config, func(tmppath string) error {
|
||||
// determine the type of file to compile
|
||||
var fileExt string
|
||||
|
||||
switch {
|
||||
case strings.Contains(spec.Flasher, "{hex}"):
|
||||
fileExt = ".hex"
|
||||
case strings.Contains(spec.Flasher, "{elf}"):
|
||||
fileExt = ".elf"
|
||||
case strings.Contains(spec.Flasher, "{bin}"):
|
||||
fileExt = ".bin"
|
||||
case strings.Contains(spec.Flasher, "{uf2}"):
|
||||
fileExt = ".uf2"
|
||||
default:
|
||||
return errors.New("invalid target file - did you forget the {hex} token in the 'flash' section?")
|
||||
}
|
||||
|
||||
return Compile(pkgName, fileExt, spec, config, func(tmppath string) error {
|
||||
if spec.Flasher == "" {
|
||||
return errors.New("no flash command specified - did you miss a -target flag?")
|
||||
}
|
||||
|
||||
// Create the command.
|
||||
flashCmd := spec.Flasher
|
||||
flashCmd = strings.Replace(flashCmd, "{hex}", tmppath, -1)
|
||||
fileToken := "{" + fileExt[1:] + "}"
|
||||
flashCmd = strings.Replace(flashCmd, fileToken, tmppath, -1)
|
||||
flashCmd = strings.Replace(flashCmd, "{port}", port, -1)
|
||||
|
||||
// Execute the command.
|
||||
@@ -455,6 +495,8 @@ func Run(pkgName, target string, config *BuildConfig) error {
|
||||
}
|
||||
|
||||
func usage() {
|
||||
fmt.Fprintln(os.Stderr, "TinyGo is a Go compiler for small places.")
|
||||
fmt.Fprintln(os.Stderr, "version:", version)
|
||||
fmt.Fprintf(os.Stderr, "usage: %s command [-printir] [-target=<target>] -o <output> <input>\n", os.Args[0])
|
||||
fmt.Fprintln(os.Stderr, "\ncommands:")
|
||||
fmt.Fprintln(os.Stderr, " build: compile packages and dependencies")
|
||||
@@ -481,6 +523,10 @@ func handleCompilerError(err error) {
|
||||
for _, err := range errLoader.Errs {
|
||||
fmt.Fprintln(os.Stderr, err)
|
||||
}
|
||||
} else if errMulti, ok := err.(*multiError); ok {
|
||||
for _, err := range errMulti.Errs {
|
||||
fmt.Fprintln(os.Stderr, err)
|
||||
}
|
||||
} else {
|
||||
fmt.Fprintln(os.Stderr, "error:", err)
|
||||
}
|
||||
@@ -491,14 +537,14 @@ func handleCompilerError(err error) {
|
||||
func main() {
|
||||
outpath := flag.String("o", "", "output filename")
|
||||
opt := flag.String("opt", "z", "optimization level: 0, 1, 2, s, z")
|
||||
gc := flag.String("gc", "", "garbage collector to use (none, dumb, marksweep)")
|
||||
gc := flag.String("gc", "", "garbage collector to use (none, leaking, conservative)")
|
||||
panicStrategy := flag.String("panic", "print", "panic strategy (abort, trap)")
|
||||
printIR := flag.Bool("printir", false, "print LLVM IR")
|
||||
dumpSSA := flag.Bool("dumpssa", false, "dump internal Go SSA")
|
||||
target := flag.String("target", "", "LLVM target")
|
||||
printSize := flag.String("size", "", "print sizes (none, short, full)")
|
||||
nodebug := flag.Bool("no-debug", false, "disable DWARF debug symbol generation")
|
||||
ocdOutput := flag.Bool("ocd-output", false, "print OCD daemon output during debug")
|
||||
initInterp := flag.Bool("initinterp", true, "enable/disable partial evaluator of generated IR")
|
||||
port := flag.String("port", "/dev/ttyACM0", "flash port")
|
||||
cFlags := flag.String("cflags", "", "additional cflags for compiler")
|
||||
ldFlags := flag.String("ldflags", "", "additional ldflags for linker")
|
||||
@@ -513,14 +559,14 @@ func main() {
|
||||
|
||||
flag.CommandLine.Parse(os.Args[2:])
|
||||
config := &BuildConfig{
|
||||
opt: *opt,
|
||||
gc: *gc,
|
||||
printIR: *printIR,
|
||||
dumpSSA: *dumpSSA,
|
||||
debug: !*nodebug,
|
||||
printSizes: *printSize,
|
||||
initInterp: *initInterp,
|
||||
wasmAbi: *wasmAbi,
|
||||
opt: *opt,
|
||||
gc: *gc,
|
||||
panicStrategy: *panicStrategy,
|
||||
printIR: *printIR,
|
||||
dumpSSA: *dumpSSA,
|
||||
debug: !*nodebug,
|
||||
printSizes: *printSize,
|
||||
wasmAbi: *wasmAbi,
|
||||
}
|
||||
|
||||
if *cFlags != "" {
|
||||
@@ -531,6 +577,12 @@ func main() {
|
||||
config.ldFlags = strings.Split(*ldFlags, " ")
|
||||
}
|
||||
|
||||
if *panicStrategy != "print" && *panicStrategy != "trap" {
|
||||
fmt.Fprintln(os.Stderr, "Panic strategy must be either print or trap.")
|
||||
usage()
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
os.Setenv("CC", "clang -target="+*target)
|
||||
|
||||
switch command {
|
||||
@@ -601,6 +653,8 @@ func main() {
|
||||
}
|
||||
case "help":
|
||||
usage()
|
||||
case "version":
|
||||
fmt.Printf("tinygo version %s %s/%s\n", version, runtime.GOOS, runtime.GOARCH)
|
||||
default:
|
||||
fmt.Fprintln(os.Stderr, "Unknown command:", command)
|
||||
usage()
|
||||
|
||||
+58
-7
@@ -10,19 +10,25 @@ import (
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"sort"
|
||||
"testing"
|
||||
|
||||
"github.com/tinygo-org/tinygo/loader"
|
||||
)
|
||||
|
||||
const TESTDATA = "testdata"
|
||||
|
||||
func TestCompiler(t *testing.T) {
|
||||
matches, err := filepath.Glob(TESTDATA + "/*.go")
|
||||
matches, err := filepath.Glob(filepath.Join(TESTDATA, "*.go"))
|
||||
if err != nil {
|
||||
t.Fatal("could not read test files:", err)
|
||||
}
|
||||
|
||||
dirMatches, err := filepath.Glob(TESTDATA + "/*/main.go")
|
||||
dirMatches, err := filepath.Glob(filepath.Join(TESTDATA, "*", "main.go"))
|
||||
if err != nil {
|
||||
t.Fatal("could not read test packages:", err)
|
||||
}
|
||||
if len(matches) == 0 || len(dirMatches) == 0 {
|
||||
t.Fatal("no test files found")
|
||||
}
|
||||
@@ -39,25 +45,64 @@ func TestCompiler(t *testing.T) {
|
||||
}
|
||||
defer os.RemoveAll(tmpdir)
|
||||
|
||||
t.Log("running tests on the host...")
|
||||
t.Log("running tests on host...")
|
||||
for _, path := range matches {
|
||||
t.Run(path, func(t *testing.T) {
|
||||
runTest(path, tmpdir, "", t)
|
||||
})
|
||||
}
|
||||
|
||||
t.Log("running tests on the qemu target...")
|
||||
if testing.Short() {
|
||||
return
|
||||
}
|
||||
|
||||
t.Log("running tests for emulated cortex-m3...")
|
||||
for _, path := range matches {
|
||||
if path == "testdata/reflect.go" {
|
||||
continue
|
||||
}
|
||||
t.Run(path, func(t *testing.T) {
|
||||
runTest(path, tmpdir, "qemu", t)
|
||||
})
|
||||
}
|
||||
|
||||
if runtime.GOOS == "linux" {
|
||||
t.Log("running tests for linux/arm...")
|
||||
for _, path := range matches {
|
||||
if path == filepath.Join("testdata", "cgo")+string(filepath.Separator) {
|
||||
continue // TODO: improve CGo
|
||||
}
|
||||
t.Run(path, func(t *testing.T) {
|
||||
runTest(path, tmpdir, "arm--linux-gnueabihf", t)
|
||||
})
|
||||
}
|
||||
|
||||
t.Log("running tests for linux/arm64...")
|
||||
for _, path := range matches {
|
||||
if path == filepath.Join("testdata", "cgo")+string(filepath.Separator) {
|
||||
continue // TODO: improve CGo
|
||||
}
|
||||
t.Run(path, func(t *testing.T) {
|
||||
runTest(path, tmpdir, "aarch64--linux-gnu", t)
|
||||
})
|
||||
}
|
||||
|
||||
t.Log("running tests for WebAssembly...")
|
||||
for _, path := range matches {
|
||||
if path == filepath.Join("testdata", "gc.go") {
|
||||
continue // known to fail
|
||||
}
|
||||
t.Run(path, func(t *testing.T) {
|
||||
runTest(path, tmpdir, "wasm", t)
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func runTest(path, tmpdir string, target string, t *testing.T) {
|
||||
// Get the expected output for this test.
|
||||
txtpath := path[:len(path)-3] + ".txt"
|
||||
if path[len(path)-1] == '/' {
|
||||
if path[len(path)-1] == os.PathSeparator {
|
||||
txtpath = path + "out.txt"
|
||||
}
|
||||
f, err := os.Open(txtpath)
|
||||
@@ -76,12 +121,18 @@ func runTest(path, tmpdir string, target string, t *testing.T) {
|
||||
dumpSSA: false,
|
||||
debug: false,
|
||||
printSizes: "",
|
||||
initInterp: true,
|
||||
wasmAbi: "js",
|
||||
}
|
||||
binary := filepath.Join(tmpdir, "test")
|
||||
err = Build("./"+path, binary, target, config)
|
||||
if err != nil {
|
||||
t.Log("failed to build:", err)
|
||||
if errLoader, ok := err.(loader.Errors); ok {
|
||||
for _, err := range errLoader.Errs {
|
||||
t.Log("failed to build:", err)
|
||||
}
|
||||
} else {
|
||||
t.Log("failed to build:", err)
|
||||
}
|
||||
t.Fail()
|
||||
return
|
||||
}
|
||||
|
||||
+127
@@ -0,0 +1,127 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"debug/elf"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
|
||||
"github.com/marcinbor85/gohex"
|
||||
)
|
||||
|
||||
// ObjcopyError is an error returned by functions that act like objcopy.
|
||||
type ObjcopyError struct {
|
||||
Op string
|
||||
Err error
|
||||
}
|
||||
|
||||
func (e ObjcopyError) Error() string {
|
||||
if e.Err == nil {
|
||||
return e.Op
|
||||
}
|
||||
return e.Op + ": " + e.Err.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()
|
||||
|
||||
// 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
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
// Objcopy converts an ELF file to a different (simpler) output file format:
|
||||
// .bin or .hex. It extracts only the .text section.
|
||||
func Objcopy(infile, outfile string) error {
|
||||
f, err := os.OpenFile(outfile, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0666)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
// Read the .text segment.
|
||||
addr, data, err := ExtractROM(infile)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Write to the file, in the correct format.
|
||||
switch filepath.Ext(outfile) {
|
||||
case ".bin":
|
||||
// The address is not stored in a .bin file (therefore you
|
||||
// should use .hex files in most cases).
|
||||
_, err := f.Write(data)
|
||||
return err
|
||||
case ".hex":
|
||||
mem := gohex.NewMemory()
|
||||
err := mem.AddBinary(uint32(addr), data)
|
||||
if err != nil {
|
||||
return ObjcopyError{"failed to create .hex file", err}
|
||||
}
|
||||
mem.DumpIntelHex(f, 16) // TODO: handle error
|
||||
return nil
|
||||
default:
|
||||
panic("unreachable")
|
||||
}
|
||||
}
|
||||
@@ -118,3 +118,21 @@ func SetPriority(irq uint32, priority uint32) {
|
||||
priority = priority << (regpos * 8) // bits to set
|
||||
NVIC.IPR[regnum] = RegValue((uint32(NVIC.IPR[regnum]) &^ mask) | priority)
|
||||
}
|
||||
|
||||
// DisableInterrupts disables all interrupts, and returns the old state.
|
||||
//
|
||||
// TODO: it doesn't actually return the old state, meaning that it cannot be
|
||||
// nested.
|
||||
func DisableInterrupts() uintptr {
|
||||
Asm("cpsid if")
|
||||
return 0
|
||||
}
|
||||
|
||||
// EnableInterrupts enables all interrupts again. The value passed in must be
|
||||
// the mask returned by DisableInterrupts.
|
||||
//
|
||||
// TODO: it doesn't actually use the old state, meaning that it cannot be
|
||||
// nested.
|
||||
func EnableInterrupts(mask uintptr) {
|
||||
Asm("cpsie if")
|
||||
}
|
||||
|
||||
@@ -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,25 @@
|
||||
// Example using the i2s hardware interface on the Adafruit Circuit Playground Express
|
||||
// to read data from the onboard MEMS microphone.
|
||||
//
|
||||
package main
|
||||
|
||||
import (
|
||||
"machine"
|
||||
)
|
||||
|
||||
func main() {
|
||||
machine.I2S0.Configure(machine.I2SConfig{
|
||||
Mode: machine.I2SModePDM,
|
||||
ClockSource: machine.I2SClockSourceExternal,
|
||||
Stereo: true,
|
||||
})
|
||||
|
||||
data := make([]uint32, 64)
|
||||
|
||||
for {
|
||||
// get the next group of samples
|
||||
machine.I2S0.Read(data)
|
||||
|
||||
println("data", data[0], data[1], data[2], data[4], "...")
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
// blink program for the BBC micro:bit
|
||||
package main
|
||||
|
||||
import (
|
||||
"machine"
|
||||
"time"
|
||||
)
|
||||
|
||||
// The LED matrix in the micro:bit is a multiplexed display: https://en.wikipedia.org/wiki/Multiplexed_display
|
||||
// Driver for easier control: https://github.com/tinygo-org/drivers/tree/master/microbitmatrix
|
||||
func main() {
|
||||
ledrow := machine.GPIO{machine.LED_ROW_1}
|
||||
ledrow.Configure(machine.GPIOConfig{Mode: machine.GPIO_OUTPUT})
|
||||
ledcol := machine.GPIO{machine.LED_COL_1}
|
||||
ledcol.Configure(machine.GPIOConfig{Mode: machine.GPIO_OUTPUT})
|
||||
ledcol.Low()
|
||||
for {
|
||||
ledrow.Low()
|
||||
time.Sleep(time.Millisecond * 500)
|
||||
|
||||
ledrow.High()
|
||||
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,131 @@
|
||||
# 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 web server:
|
||||
|
||||
```bash
|
||||
$ go run main.go
|
||||
Serving ./html on http://localhost:8080
|
||||
```
|
||||
|
||||
Use your web browser to visit http://localhost:8080.
|
||||
|
||||
* The wasm "export" example displays a simple math equation using HTML, with
|
||||
the result calculated dynamically using WebAssembly. Changing any of the
|
||||
values on the left hand side triggers the exported wasm `update` function to
|
||||
recalculate the result.
|
||||
* The wasm "main" example uses `println` to write to your browser JavaScript
|
||||
console. You may need to open the browser development tools console to see it.
|
||||
|
||||
## How it works
|
||||
|
||||
Execution of the contents require a few JavaScript 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 the JavaScript, it is important the wasm file is served with the
|
||||
[`Content-Type`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Type)
|
||||
header set to `application/wasm`. Without it, most browsers won't run it.
|
||||
|
||||
```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) {
|
||||
resp.Header().Add("Cache-Control", "no-cache")
|
||||
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.
|
||||
|
||||
For development purposes (**only!**), it also sets the `Cache-Control` header
|
||||
so your browser doesn't cache the files. This is useful while developing, to
|
||||
ensure your browser displays the newest wasm when you recompile.
|
||||
|
||||
In a production environment you **probably wouldn't** want to set the
|
||||
`Cache-Control` header like this. Caching is generally beneficial for end
|
||||
users.
|
||||
|
||||
Further information on the `Cache-Control` header can be found here:
|
||||
|
||||
* https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cache-Control
|
||||
@@ -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)
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
'use strict';
|
||||
|
||||
const WASM_URL = 'wasm.wasm';
|
||||
|
||||
var wasm;
|
||||
|
||||
function updateResult() {
|
||||
wasm.exports.update();
|
||||
}
|
||||
|
||||
function init() {
|
||||
document.querySelector('#a').oninput = updateResult;
|
||||
document.querySelector('#b').oninput = updateResult;
|
||||
|
||||
const go = new Go();
|
||||
if ('instantiateStreaming' in WebAssembly) {
|
||||
WebAssembly.instantiateStreaming(fetch(WASM_URL), go.importObject).then(function (obj) {
|
||||
wasm = obj.instance;
|
||||
go.run(wasm);
|
||||
updateResult();
|
||||
})
|
||||
} else {
|
||||
fetch(WASM_URL).then(resp =>
|
||||
resp.arrayBuffer()
|
||||
).then(bytes =>
|
||||
WebAssembly.instantiate(bytes, go.importObject).then(function (obj) {
|
||||
wasm = obj.instance;
|
||||
go.run(wasm);
|
||||
updateResult();
|
||||
})
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
init();
|
||||
@@ -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,5 @@
|
||||
package main
|
||||
|
||||
func main() {
|
||||
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) {
|
||||
resp.Header().Add("Cache-Control", "no-cache")
|
||||
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>
|
||||
@@ -1,23 +0,0 @@
|
||||
'use strict';
|
||||
|
||||
const WASM_URL = '../../../wasm.wasm';
|
||||
|
||||
var wasm;
|
||||
|
||||
function updateResult() {
|
||||
wasm.exports.update();
|
||||
}
|
||||
|
||||
function init() {
|
||||
document.querySelector('#a').oninput = updateResult;
|
||||
document.querySelector('#b').oninput = updateResult;
|
||||
|
||||
const go = new Go();
|
||||
WebAssembly.instantiateStreaming(fetch(WASM_URL), go.importObject).then(function(obj) {
|
||||
wasm = obj.instance;
|
||||
go.run(wasm);
|
||||
updateResult();
|
||||
})
|
||||
}
|
||||
|
||||
init();
|
||||
@@ -0,0 +1,113 @@
|
||||
// +build sam,atsamd21,circuitplay_express
|
||||
|
||||
package machine
|
||||
|
||||
import "device/sam"
|
||||
|
||||
// GPIO Pins
|
||||
const (
|
||||
D0 = PB09
|
||||
D1 = PB08
|
||||
D2 = PB02
|
||||
D3 = PB03
|
||||
D4 = PA28
|
||||
D5 = PA14
|
||||
D6 = PA05
|
||||
D7 = PA15
|
||||
D8 = PB23
|
||||
D9 = PA06
|
||||
D10 = PA07
|
||||
D11 = 0xff // does not seem to exist
|
||||
D12 = PA02
|
||||
D13 = PA17 // PWM available
|
||||
)
|
||||
|
||||
// Analog Pins
|
||||
const (
|
||||
A0 = PA02 // PWM available, also ADC/AIN[0]
|
||||
A1 = PA05 // ADC/AIN[5]
|
||||
A2 = PA06 // PWM available, also ADC/AIN[6]
|
||||
A3 = PA07 // PWM available, also ADC/AIN[7]
|
||||
A4 = PB03 // PORTB
|
||||
A5 = PB02 // PORTB
|
||||
A6 = PB09 // PORTB
|
||||
A7 = PB08 // PORTB
|
||||
A8 = PA11 // ADC/AIN[19]
|
||||
A9 = PA09 // ADC/AIN[17]
|
||||
A10 = PA04
|
||||
)
|
||||
|
||||
const (
|
||||
LED = D13
|
||||
NEOPIXELS = D8
|
||||
|
||||
BUTTONA = D4
|
||||
BUTTONB = D5
|
||||
SLIDER = D7 // built-in slide switch
|
||||
|
||||
BUTTON = BUTTONA
|
||||
BUTTON1 = BUTTONB
|
||||
|
||||
LIGHTSENSOR = A8
|
||||
TEMPSENSOR = A9
|
||||
PROXIMITY = A10
|
||||
)
|
||||
|
||||
// USBCDC pins (logical UART0)
|
||||
const (
|
||||
USBCDC_DM_PIN = PA24
|
||||
USBCDC_DP_PIN = PA25
|
||||
)
|
||||
|
||||
// UART0 pins (logical UART1)
|
||||
const (
|
||||
UART_TX_PIN = PB08 // PORTB
|
||||
UART_RX_PIN = PB09 // PORTB
|
||||
)
|
||||
|
||||
// I2C pins
|
||||
const (
|
||||
SDA_PIN = PB02 // I2C0 external
|
||||
SCL_PIN = PB03 // I2C0 external
|
||||
|
||||
SDA1_PIN = PA00 // I2C1 internal
|
||||
SCL1_PIN = PA01 // I2C1 internal
|
||||
)
|
||||
|
||||
// I2C on the Circuit Playground Express.
|
||||
var (
|
||||
// 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)
|
||||
const (
|
||||
SPI0_SCK_PIN = PA21 // SCK: SERCOM3/PAD[3]
|
||||
SPI0_MOSI_PIN = PA20 // MOSI: SERCOM3/PAD[2]
|
||||
SPI0_MISO_PIN = PA16 // MISO: SERCOM3/PAD[0]
|
||||
)
|
||||
|
||||
// SPI on the Circuit Playground Express.
|
||||
var (
|
||||
SPI0 = SPI{Bus: sam.SERCOM3_SPI}
|
||||
)
|
||||
|
||||
// I2S pins
|
||||
const (
|
||||
I2S_SCK_PIN = PA10
|
||||
I2S_SD_PIN = PA08
|
||||
I2S_WS_PIN = 0xff // no WS, instead uses SCK to sync
|
||||
)
|
||||
|
||||
// I2S on the Circuit Playground Express.
|
||||
var (
|
||||
I2S0 = I2S{Bus: sam.I2S}
|
||||
)
|
||||
@@ -1,47 +1,87 @@
|
||||
// +build sam,atsamd21g18a,itsybitsy_m0
|
||||
// +build sam,atsamd21,itsybitsy_m0
|
||||
|
||||
package machine
|
||||
|
||||
import "device/sam"
|
||||
|
||||
// GPIO Pins
|
||||
const (
|
||||
D0 = 11 // UART0 RX
|
||||
D1 = 10 // UART0 TX
|
||||
D2 = 14
|
||||
D3 = 9 // PWM available
|
||||
D4 = 8 // PWM available
|
||||
D5 = 15 // PWM available
|
||||
D6 = 20 // PWM available
|
||||
D7 = 21 // PWM available
|
||||
D8 = 6 // PWM available
|
||||
D9 = 7 // PWM available
|
||||
D10 = 18 // can be used for PWM or UART1 TX
|
||||
D11 = 16 // can be used for PWM or UART1 RX
|
||||
D12 = 19 // PWM available
|
||||
D13 = 17 // PWM available
|
||||
D0 = PA11 // UART0 RX
|
||||
D1 = PA10 // UART0 TX
|
||||
D2 = PA14
|
||||
D3 = PA09 // PWM available
|
||||
D4 = PA08 // PWM available
|
||||
D5 = PA15 // PWM available
|
||||
D6 = PA20 // PWM available
|
||||
D7 = PA21 // PWM available
|
||||
D8 = PA06 // PWM available
|
||||
D9 = PA07 // PWM available
|
||||
D10 = PA18 // can be used for PWM or UART1 TX
|
||||
D11 = PA16 // can be used for PWM or UART1 RX
|
||||
D12 = PA19 // PWM available
|
||||
D13 = PA17 // PWM available
|
||||
)
|
||||
|
||||
// Analog pins
|
||||
const (
|
||||
A0 = 2 // ADC/AIN[0]
|
||||
// A1 = 8 // ADC/AIN[2] TODO: requires PORTB
|
||||
// A2 = 9 // ADC/AIN[3] TODO: requires PORTB
|
||||
A3 = 4 // ADC/AIN[4]
|
||||
A4 = 5 // ADC/AIN[5]
|
||||
//A5 = 2 // ADC/AIN[10] TODO: requires PORTB
|
||||
A0 = PA02 // ADC/AIN[0]
|
||||
A1 = PB08 // ADC/AIN[2]
|
||||
A2 = PB09 // ADC/AIN[3]
|
||||
A3 = PA04 // ADC/AIN[4]
|
||||
A4 = PA05 // ADC/AIN[5]
|
||||
A5 = PB02 // ADC/AIN[10]
|
||||
)
|
||||
|
||||
const (
|
||||
LED = D13
|
||||
)
|
||||
|
||||
// UART0 pins
|
||||
// UART0 aka USBCDC pins
|
||||
const (
|
||||
UART_TX_PIN = D1
|
||||
UART_RX_PIN = D0
|
||||
USBCDC_DM_PIN = PA24
|
||||
USBCDC_DP_PIN = PA25
|
||||
)
|
||||
|
||||
// UART1 pins
|
||||
const (
|
||||
UART_TX_PIN = D10
|
||||
UART_RX_PIN = D11
|
||||
)
|
||||
|
||||
// I2C pins
|
||||
const (
|
||||
SDA_PIN = 22 // SDA: SERCOM3/PAD[0]
|
||||
SCL_PIN = 23 // SCL: SERCOM3/PAD[1]
|
||||
SDA_PIN = PA22 // SDA: SERCOM3/PAD[0]
|
||||
SCL_PIN = PA23 // SCL: SERCOM3/PAD[1]
|
||||
)
|
||||
|
||||
// I2C on the ItsyBitsy M0.
|
||||
var (
|
||||
I2C0 = I2C{Bus: sam.SERCOM3_I2CM,
|
||||
SDA: SDA_PIN,
|
||||
SCL: SCL_PIN,
|
||||
PinMode: GPIO_SERCOM}
|
||||
)
|
||||
|
||||
// SPI pins
|
||||
const (
|
||||
SPI0_SCK_PIN = PB11 // SCK: SERCOM4/PAD[3]
|
||||
SPI0_MOSI_PIN = PB10 // MOSI: SERCOM4/PAD[2]
|
||||
SPI0_MISO_PIN = PA12 // MISO: SERCOM4/PAD[0]
|
||||
)
|
||||
|
||||
// SPI on the ItsyBitsy M0.
|
||||
var (
|
||||
SPI0 = SPI{Bus: sam.SERCOM4_SPI}
|
||||
)
|
||||
|
||||
// I2S pins
|
||||
const (
|
||||
I2S_SCK_PIN = PA10
|
||||
I2S_SD_PIN = PA08
|
||||
I2S_WS_PIN = 0xff // TODO: figure out what this is on ItsyBitsy M0.
|
||||
)
|
||||
|
||||
// I2S on the ItsyBitsy M0.
|
||||
var (
|
||||
I2S0 = I2S{Bus: sam.I2S}
|
||||
)
|
||||
|
||||
@@ -2,11 +2,6 @@
|
||||
|
||||
package machine
|
||||
|
||||
import (
|
||||
"device/nrf"
|
||||
"errors"
|
||||
)
|
||||
|
||||
// The micro:bit does not have a 32kHz crystal on board.
|
||||
const HasLowFrequencyCrystal = false
|
||||
|
||||
@@ -43,6 +38,27 @@ const (
|
||||
SPI0_MISO_PIN = 22 // P14 on the board
|
||||
)
|
||||
|
||||
// GPIO/Analog pins
|
||||
const (
|
||||
P0 = 3
|
||||
P1 = 2
|
||||
P2 = 1
|
||||
P3 = 4
|
||||
P4 = 5
|
||||
P5 = 17
|
||||
P6 = 12
|
||||
P7 = 11
|
||||
P8 = 18
|
||||
P9 = 10
|
||||
P10 = 6
|
||||
P11 = 26
|
||||
P12 = 20
|
||||
P13 = 23
|
||||
P14 = 22
|
||||
P15 = 21
|
||||
P16 = 16
|
||||
)
|
||||
|
||||
// LED matrix pins
|
||||
const (
|
||||
LED_COL_1 = 4
|
||||
@@ -58,65 +74,3 @@ const (
|
||||
LED_ROW_2 = 14
|
||||
LED_ROW_3 = 15
|
||||
)
|
||||
|
||||
// matrixSettings has the legs of the LED grid in the form {row, column} for each LED position.
|
||||
var matrixSettings = [5][5][2]uint8{
|
||||
{{LED_ROW_1, LED_COL_1}, {LED_ROW_2, LED_COL_4}, {LED_ROW_1, LED_COL_2}, {LED_ROW_2, LED_COL_5}, {LED_ROW_1, LED_COL_3}},
|
||||
{{LED_ROW_3, LED_COL_4}, {LED_ROW_3, LED_COL_5}, {LED_ROW_3, LED_COL_6}, {LED_ROW_3, LED_COL_7}, {LED_ROW_3, LED_COL_8}},
|
||||
{{LED_ROW_2, LED_COL_2}, {LED_ROW_1, LED_COL_9}, {LED_ROW_2, LED_COL_3}, {LED_ROW_3, LED_COL_9}, {LED_ROW_2, LED_COL_1}},
|
||||
{{LED_ROW_1, LED_COL_8}, {LED_ROW_1, LED_COL_7}, {LED_ROW_1, LED_COL_6}, {LED_ROW_1, LED_COL_5}, {LED_ROW_1, LED_COL_4}},
|
||||
{{LED_ROW_3, LED_COL_3}, {LED_ROW_2, LED_COL_7}, {LED_ROW_3, LED_COL_1}, {LED_ROW_2, LED_COL_6}, {LED_ROW_3, LED_COL_2}}}
|
||||
|
||||
// InitLEDMatrix initializes the LED matrix, by setting all of the row/col pins to output
|
||||
// then calling ClearLEDMatrix.
|
||||
func InitLEDMatrix() {
|
||||
set := 0
|
||||
for i := LED_COL_1; i <= LED_ROW_3; i++ {
|
||||
set |= 1 << uint8(i)
|
||||
}
|
||||
nrf.GPIO.DIRSET = nrf.RegValue(set)
|
||||
ClearLEDMatrix()
|
||||
}
|
||||
|
||||
// ClearLEDMatrix clears the entire LED matrix.
|
||||
func ClearLEDMatrix() {
|
||||
set := 0
|
||||
for i := LED_COL_1; i <= LED_COL_9; i++ {
|
||||
set |= 1 << uint8(i)
|
||||
}
|
||||
nrf.GPIO.OUTSET = nrf.RegValue(set)
|
||||
nrf.GPIO.OUTCLR = (1 << LED_ROW_1) | (1 << LED_ROW_2) | (1 << LED_ROW_3)
|
||||
}
|
||||
|
||||
// SetLEDMatrix turns on a single LED on the LED matrix.
|
||||
// Currently limited to a single LED at a time, it will clear the matrix before setting it.
|
||||
func SetLEDMatrix(x, y uint8) error {
|
||||
if x > 4 || y > 4 {
|
||||
return errors.New("Invalid LED matrix row or column")
|
||||
}
|
||||
|
||||
// Clear matrix
|
||||
ClearLEDMatrix()
|
||||
|
||||
nrf.GPIO.OUTSET = (1 << matrixSettings[y][x][0])
|
||||
nrf.GPIO.OUTCLR = (1 << matrixSettings[y][x][1])
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// SetEntireLEDMatrixOn turns on all of the LEDs on the LED matrix.
|
||||
func SetEntireLEDMatrixOn() error {
|
||||
set := 0
|
||||
for i := LED_ROW_1; i <= LED_ROW_3; i++ {
|
||||
set |= 1 << uint8(i)
|
||||
}
|
||||
nrf.GPIO.OUTSET = nrf.RegValue(set)
|
||||
|
||||
set = 0
|
||||
for i := LED_COL_1; i <= LED_COL_9; i++ {
|
||||
set |= 1 << uint8(i)
|
||||
}
|
||||
nrf.GPIO.OUTCLR = nrf.RegValue(set)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -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
|
||||
)
|
||||
@@ -0,0 +1,54 @@
|
||||
// +build sam
|
||||
|
||||
// This is the definition for I2S bus functions.
|
||||
// Actual implementations if available for any given hardware
|
||||
// are to be found in its the board definition.
|
||||
//
|
||||
// For more info about I2S, see: https://en.wikipedia.org/wiki/I%C2%B2S
|
||||
//
|
||||
|
||||
package machine
|
||||
|
||||
type I2SMode uint8
|
||||
type I2SStandard uint8
|
||||
type I2SClockSource uint8
|
||||
type I2SDataFormat uint8
|
||||
|
||||
const (
|
||||
I2SModeMaster I2SMode = iota
|
||||
I2SModeSlave
|
||||
I2SModePDM
|
||||
)
|
||||
|
||||
const (
|
||||
I2StandardPhilips I2SStandard = iota
|
||||
I2SStandardMSB
|
||||
I2SStandardLSB
|
||||
)
|
||||
|
||||
const (
|
||||
I2SClockSourceInternal I2SClockSource = iota
|
||||
I2SClockSourceExternal
|
||||
)
|
||||
|
||||
const (
|
||||
I2SDataFormatDefault I2SDataFormat = 0
|
||||
I2SDataFormat8bit = 8
|
||||
I2SDataFormat16bit = 16
|
||||
I2SDataFormat24bit = 24
|
||||
I2SDataFormat32bit = 32
|
||||
)
|
||||
|
||||
// All fields are optional and may not be required or used on a particular platform.
|
||||
type I2SConfig struct {
|
||||
SCK uint8
|
||||
WS uint8
|
||||
SD uint8
|
||||
Mode I2SMode
|
||||
Standard I2SStandard
|
||||
ClockSource I2SClockSource
|
||||
DataFormat I2SDataFormat
|
||||
AudioFrequency uint32
|
||||
MasterClockOutput bool
|
||||
Stereo bool
|
||||
}
|
||||
@@ -10,15 +10,15 @@ import (
|
||||
func (p GPIO) Configure(config GPIOConfig) {
|
||||
if config.Mode == GPIO_OUTPUT { // set output bit
|
||||
if p.Pin < 8 {
|
||||
*avr.DDRD |= 1 << p.Pin
|
||||
avr.DDRD.SetBits(1 << p.Pin)
|
||||
} else {
|
||||
*avr.DDRB |= 1 << (p.Pin - 8)
|
||||
avr.DDRB.SetBits(1 << (p.Pin - 8))
|
||||
}
|
||||
} else { // configure input: clear output bit
|
||||
if p.Pin < 8 {
|
||||
*avr.DDRD &^= 1 << p.Pin
|
||||
avr.DDRD.ClearBits(1 << p.Pin)
|
||||
} else {
|
||||
*avr.DDRB &^= 1 << (p.Pin - 8)
|
||||
avr.DDRB.ClearBits(1 << (p.Pin - 8))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -26,15 +26,15 @@ func (p GPIO) Configure(config GPIOConfig) {
|
||||
// Get returns the current value of a GPIO pin.
|
||||
func (p GPIO) Get() bool {
|
||||
if p.Pin < 8 {
|
||||
val := *avr.PIND & (1 << p.Pin)
|
||||
val := avr.PIND.Get() & (1 << p.Pin)
|
||||
return (val > 0)
|
||||
} else {
|
||||
val := *avr.PINB & (1 << (p.Pin - 8))
|
||||
val := avr.PINB.Get() & (1 << (p.Pin - 8))
|
||||
return (val > 0)
|
||||
}
|
||||
}
|
||||
|
||||
func (p GPIO) getPortMask() (*avr.RegValue, uint8) {
|
||||
func (p GPIO) getPortMask() (*avr.Register8, uint8) {
|
||||
if p.Pin < 8 {
|
||||
return avr.PORTD, 1 << p.Pin
|
||||
} else {
|
||||
@@ -45,64 +45,64 @@ func (p GPIO) getPortMask() (*avr.RegValue, uint8) {
|
||||
// InitPWM initializes the registers needed for PWM.
|
||||
func InitPWM() {
|
||||
// use waveform generation
|
||||
*avr.TCCR0A |= avr.TCCR0A_WGM00
|
||||
avr.TCCR0A.SetBits(avr.TCCR0A_WGM00)
|
||||
|
||||
// set timer 0 prescale factor to 64
|
||||
*avr.TCCR0B |= avr.TCCR0B_CS01 | avr.TCCR0B_CS00
|
||||
avr.TCCR0B.SetBits(avr.TCCR0B_CS01 | avr.TCCR0B_CS00)
|
||||
|
||||
// set timer 1 prescale factor to 64
|
||||
*avr.TCCR1B |= avr.TCCR1B_CS11
|
||||
avr.TCCR1B.SetBits(avr.TCCR1B_CS11)
|
||||
|
||||
// put timer 1 in 8-bit phase correct pwm mode
|
||||
*avr.TCCR1A |= avr.TCCR1A_WGM10
|
||||
avr.TCCR1A.SetBits(avr.TCCR1A_WGM10)
|
||||
|
||||
// set timer 2 prescale factor to 64
|
||||
*avr.TCCR2B |= avr.TCCR2B_CS22
|
||||
avr.TCCR2B.SetBits(avr.TCCR2B_CS22)
|
||||
|
||||
// configure timer 2 for phase correct pwm (8-bit)
|
||||
*avr.TCCR2A |= avr.TCCR2A_WGM20
|
||||
avr.TCCR2A.SetBits(avr.TCCR2A_WGM20)
|
||||
}
|
||||
|
||||
// Configure configures a PWM pin for output.
|
||||
func (pwm PWM) Configure() {
|
||||
if pwm.Pin < 8 {
|
||||
*avr.DDRD |= 1 << pwm.Pin
|
||||
avr.DDRD.SetBits(1 << pwm.Pin)
|
||||
} else {
|
||||
*avr.DDRB |= 1 << (pwm.Pin - 8)
|
||||
avr.DDRB.SetBits(1 << (pwm.Pin - 8))
|
||||
}
|
||||
}
|
||||
|
||||
// Set turns on the duty cycle for a PWM pin using the provided value. On the AVR this is normally a
|
||||
// 8-bit value ranging from 0 to 255.
|
||||
func (pwm PWM) Set(value uint16) {
|
||||
value8 := value >> 8
|
||||
value8 := uint8(value >> 8)
|
||||
switch pwm.Pin {
|
||||
case 3:
|
||||
// connect pwm to pin on timer 2, channel B
|
||||
*avr.TCCR2A |= avr.TCCR2A_COM2B1
|
||||
*avr.OCR2B = avr.RegValue(value8) // set pwm duty
|
||||
avr.TCCR2A.SetBits(avr.TCCR2A_COM2B1)
|
||||
avr.OCR2B.Set(value8) // set pwm duty
|
||||
case 5:
|
||||
// connect pwm to pin on timer 0, channel B
|
||||
*avr.TCCR0A |= avr.TCCR0A_COM0B1
|
||||
*avr.OCR0B = avr.RegValue(value8) // set pwm duty
|
||||
avr.TCCR0A.SetBits(avr.TCCR0A_COM0B1)
|
||||
avr.OCR0B.Set(value8) // set pwm duty
|
||||
case 6:
|
||||
// connect pwm to pin on timer 0, channel A
|
||||
*avr.TCCR0A |= avr.TCCR0A_COM0A1
|
||||
*avr.OCR0A = avr.RegValue(value8) // set pwm duty
|
||||
avr.TCCR0A.SetBits(avr.TCCR0A_COM0A1)
|
||||
avr.OCR0A.Set(value8) // set pwm duty
|
||||
case 9:
|
||||
// connect pwm to pin on timer 1, channel A
|
||||
*avr.TCCR1A |= avr.TCCR1A_COM1A1
|
||||
avr.TCCR1A.SetBits(avr.TCCR1A_COM1A1)
|
||||
// this is a 16-bit value, but we only currently allow the low order bits to be set
|
||||
*avr.OCR1AL = avr.RegValue(value8) // set pwm duty
|
||||
avr.OCR1AL.Set(value8) // set pwm duty
|
||||
case 10:
|
||||
// connect pwm to pin on timer 1, channel B
|
||||
*avr.TCCR1A |= avr.TCCR1A_COM1B1
|
||||
avr.TCCR1A.SetBits(avr.TCCR1A_COM1B1)
|
||||
// this is a 16-bit value, but we only currently allow the low order bits to be set
|
||||
*avr.OCR1BL = avr.RegValue(value8) // set pwm duty
|
||||
avr.OCR1BL.Set(value8) // set pwm duty
|
||||
case 11:
|
||||
// connect pwm to pin on timer 2, channel A
|
||||
*avr.TCCR2A |= avr.TCCR2A_COM2A1
|
||||
*avr.OCR2A = avr.RegValue(value8) // set pwm duty
|
||||
avr.TCCR2A.SetBits(avr.TCCR2A_COM2A1)
|
||||
avr.OCR2A.Set(value8) // set pwm duty
|
||||
default:
|
||||
panic("Invalid PWM pin")
|
||||
}
|
||||
@@ -121,19 +121,19 @@ func (i2c I2C) Configure(config I2CConfig) {
|
||||
}
|
||||
|
||||
// Activate internal pullups for twi.
|
||||
*avr.PORTC |= (avr.DIDR0_ADC4D | avr.DIDR0_ADC5D)
|
||||
avr.PORTC.SetBits((avr.DIDR0_ADC4D | avr.DIDR0_ADC5D))
|
||||
|
||||
// Initialize twi prescaler and bit rate.
|
||||
*avr.TWSR |= (avr.TWSR_TWPS0 | avr.TWSR_TWPS1)
|
||||
avr.TWSR.SetBits((avr.TWSR_TWPS0 | avr.TWSR_TWPS1))
|
||||
|
||||
// twi bit rate formula from atmega128 manual pg. 204:
|
||||
// SCL Frequency = CPU Clock Frequency / (16 + (2 * TWBR))
|
||||
// NOTE: TWBR should be 10 or higher for master mode.
|
||||
// It is 72 for a 16mhz board with 100kHz TWI
|
||||
*avr.TWBR = avr.RegValue(((CPU_FREQUENCY / config.Frequency) - 16) / 2)
|
||||
avr.TWBR.Set(uint8(((CPU_FREQUENCY / config.Frequency) - 16) / 2))
|
||||
|
||||
// Enable twi module.
|
||||
*avr.TWCR = avr.TWCR_TWEN
|
||||
avr.TWCR.Set(avr.TWCR_TWEN)
|
||||
}
|
||||
|
||||
// Tx does a single I2C transaction at the specified address.
|
||||
@@ -162,10 +162,10 @@ func (i2c I2C) Tx(addr uint16, w, r []byte) error {
|
||||
// start starts an I2C communication session.
|
||||
func (i2c I2C) start(address uint8, write bool) {
|
||||
// Clear TWI interrupt flag, put start condition on SDA, and enable TWI.
|
||||
*avr.TWCR = (avr.TWCR_TWINT | avr.TWCR_TWSTA | avr.TWCR_TWEN)
|
||||
avr.TWCR.Set((avr.TWCR_TWINT | avr.TWCR_TWSTA | avr.TWCR_TWEN))
|
||||
|
||||
// Wait till start condition is transmitted.
|
||||
for (*avr.TWCR & avr.TWCR_TWINT) == 0 {
|
||||
for (avr.TWCR.Get() & avr.TWCR_TWINT) == 0 {
|
||||
}
|
||||
|
||||
// Write 7-bit shifted peripheral address.
|
||||
@@ -179,36 +179,36 @@ func (i2c I2C) start(address uint8, write bool) {
|
||||
// stop ends an I2C communication session.
|
||||
func (i2c I2C) stop() {
|
||||
// Send stop condition.
|
||||
*avr.TWCR = (avr.TWCR_TWEN | avr.TWCR_TWINT | avr.TWCR_TWSTO)
|
||||
avr.TWCR.Set(avr.TWCR_TWEN | avr.TWCR_TWINT | avr.TWCR_TWSTO)
|
||||
|
||||
// Wait for stop condition to be executed on bus.
|
||||
for (*avr.TWCR & avr.TWCR_TWSTO) == 0 {
|
||||
for (avr.TWCR.Get() & avr.TWCR_TWSTO) == 0 {
|
||||
}
|
||||
}
|
||||
|
||||
// writeByte writes a single byte to the I2C bus.
|
||||
func (i2c I2C) writeByte(data byte) {
|
||||
// Write data to register.
|
||||
*avr.TWDR = avr.RegValue(data)
|
||||
avr.TWDR.Set(data)
|
||||
|
||||
// Clear TWI interrupt flag and enable TWI.
|
||||
*avr.TWCR = (avr.TWCR_TWEN | avr.TWCR_TWINT)
|
||||
avr.TWCR.Set(avr.TWCR_TWEN | avr.TWCR_TWINT)
|
||||
|
||||
// Wait till data is transmitted.
|
||||
for (*avr.TWCR & avr.TWCR_TWINT) == 0 {
|
||||
for (avr.TWCR.Get() & avr.TWCR_TWINT) == 0 {
|
||||
}
|
||||
}
|
||||
|
||||
// readByte reads a single byte from the I2C bus.
|
||||
func (i2c I2C) readByte() byte {
|
||||
// Clear TWI interrupt flag and enable TWI.
|
||||
*avr.TWCR = (avr.TWCR_TWEN | avr.TWCR_TWINT | avr.TWCR_TWEA)
|
||||
avr.TWCR.Set(avr.TWCR_TWEN | avr.TWCR_TWINT | avr.TWCR_TWEA)
|
||||
|
||||
// Wait till read request is transmitted.
|
||||
for (*avr.TWCR & avr.TWCR_TWINT) == 0 {
|
||||
for (avr.TWCR.Get() & avr.TWCR_TWINT) == 0 {
|
||||
}
|
||||
|
||||
return byte(*avr.TWDR)
|
||||
return byte(avr.TWDR.Get())
|
||||
}
|
||||
|
||||
// UART on the AVR.
|
||||
@@ -226,32 +226,32 @@ func (uart UART) Configure(config UARTConfig) {
|
||||
// https://www.microchip.com/webdoc/AVRLibcReferenceManual/FAQ_1faq_wrong_baud_rate.html
|
||||
// ((F_CPU + UART_BAUD_RATE * 8L) / (UART_BAUD_RATE * 16L) - 1)
|
||||
ps := ((CPU_FREQUENCY+config.BaudRate*8)/(config.BaudRate*16) - 1)
|
||||
*avr.UBRR0H = avr.RegValue(ps >> 8)
|
||||
*avr.UBRR0L = avr.RegValue(ps & 0xff)
|
||||
avr.UBRR0H.Set(uint8(ps >> 8))
|
||||
avr.UBRR0L.Set(uint8(ps & 0xff))
|
||||
|
||||
// enable RX, TX and RX interrupt
|
||||
*avr.UCSR0B = avr.UCSR0B_RXEN0 | avr.UCSR0B_TXEN0 | avr.UCSR0B_RXCIE0
|
||||
avr.UCSR0B.Set(avr.UCSR0B_RXEN0 | avr.UCSR0B_TXEN0 | avr.UCSR0B_RXCIE0)
|
||||
|
||||
// 8-bits data
|
||||
*avr.UCSR0C = avr.UCSR0C_UCSZ01 | avr.UCSR0C_UCSZ00
|
||||
avr.UCSR0C.Set(avr.UCSR0C_UCSZ01 | avr.UCSR0C_UCSZ00)
|
||||
}
|
||||
|
||||
// WriteByte writes a byte of data to the UART.
|
||||
func (uart UART) WriteByte(c byte) error {
|
||||
// Wait until UART buffer is not busy.
|
||||
for (*avr.UCSR0A & avr.UCSR0A_UDRE0) == 0 {
|
||||
for (avr.UCSR0A.Get() & avr.UCSR0A_UDRE0) == 0 {
|
||||
}
|
||||
*avr.UDR0 = avr.RegValue(c) // send char
|
||||
avr.UDR0.Set(c) // send char
|
||||
return nil
|
||||
}
|
||||
|
||||
//go:interrupt USART_RX_vect
|
||||
func handleUSART_RX() {
|
||||
// Read register to clear it.
|
||||
data := *avr.UDR0
|
||||
data := avr.UDR0.Get()
|
||||
|
||||
// Ensure no error.
|
||||
if (*avr.UCSR0A & (avr.UCSR0A_FE0 | avr.UCSR0A_DOR0 | avr.UCSR0A_UPE0)) == 0 {
|
||||
if (avr.UCSR0A.Get() & (avr.UCSR0A_FE0 | avr.UCSR0A_DOR0 | avr.UCSR0A_UPE0)) == 0 {
|
||||
// Put data from UDR register into buffer.
|
||||
UART0.Receive(byte(data))
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,908 +0,0 @@
|
||||
// +build sam,atsamd21g18a
|
||||
|
||||
// Peripheral abstraction layer for the atsamd21.
|
||||
//
|
||||
// Datasheet:
|
||||
// http://ww1.microchip.com/downloads/en/DeviceDoc/SAMD21-Family-DataSheet-DS40001882D.pdf
|
||||
//
|
||||
package machine
|
||||
|
||||
import (
|
||||
"device/arm"
|
||||
"device/sam"
|
||||
"errors"
|
||||
)
|
||||
|
||||
const CPU_FREQUENCY = 48000000
|
||||
|
||||
type GPIOMode uint8
|
||||
|
||||
const (
|
||||
GPIO_ANALOG = 1
|
||||
GPIO_SERCOM = 2
|
||||
GPIO_SERCOM_ALT = 3
|
||||
GPIO_TIMER = 4
|
||||
GPIO_TIMER_ALT = 5
|
||||
GPIO_COM = 6
|
||||
GPIO_AC_CLK = 7
|
||||
GPIO_DIGITAL = 8
|
||||
GPIO_INPUT = 9
|
||||
GPIO_INPUT_PULLUP = 10
|
||||
GPIO_OUTPUT = 11
|
||||
GPIO_PWM = GPIO_TIMER
|
||||
GPIO_PWM_ALT = GPIO_TIMER_ALT
|
||||
)
|
||||
|
||||
// Configure this pin with the given configuration.
|
||||
func (p GPIO) Configure(config GPIOConfig) {
|
||||
switch config.Mode {
|
||||
case GPIO_OUTPUT:
|
||||
sam.PORT.DIRSET0 = (1 << p.Pin)
|
||||
// output is also set to input enable so pin can read back its own value
|
||||
p.setPinCfg(sam.PORT_PINCFG0_INEN)
|
||||
|
||||
case GPIO_INPUT:
|
||||
sam.PORT.DIRCLR0 = (1 << p.Pin)
|
||||
p.setPinCfg(sam.PORT_PINCFG0_INEN)
|
||||
|
||||
case GPIO_SERCOM:
|
||||
if p.Pin&1 > 0 {
|
||||
// odd pin, so save the even pins
|
||||
val := p.getPMux() & sam.PORT_PMUX0_PMUXE_Msk
|
||||
p.setPMux(val | (GPIO_SERCOM << sam.PORT_PMUX0_PMUXO_Pos))
|
||||
} else {
|
||||
// even pin, so save the odd pins
|
||||
val := p.getPMux() & sam.PORT_PMUX0_PMUXO_Msk
|
||||
p.setPMux(val | (GPIO_SERCOM << sam.PORT_PMUX0_PMUXE_Pos))
|
||||
}
|
||||
// enable port config
|
||||
p.setPinCfg(sam.PORT_PINCFG0_PMUXEN | sam.PORT_PINCFG0_DRVSTR | sam.PORT_PINCFG0_INEN)
|
||||
}
|
||||
}
|
||||
|
||||
// Get returns the current value of a GPIO pin.
|
||||
func (p GPIO) Get() bool {
|
||||
return (sam.PORT.IN0>>p.Pin)&1 > 0
|
||||
}
|
||||
|
||||
// Set the pin to high or low.
|
||||
// Warning: only use this on an output pin!
|
||||
func (p GPIO) Set(high bool) {
|
||||
if high {
|
||||
sam.PORT.OUTSET0 = (1 << p.Pin)
|
||||
} else {
|
||||
sam.PORT.OUTCLR0 = (1 << p.Pin)
|
||||
}
|
||||
}
|
||||
|
||||
// getPMux returns the value for the correct PMUX register for this pin.
|
||||
func (p GPIO) getPMux() sam.RegValue8 {
|
||||
return getPMux(p.Pin)
|
||||
}
|
||||
|
||||
// setPMux sets the value for the correct PMUX register for this pin.
|
||||
func (p GPIO) setPMux(val sam.RegValue8) {
|
||||
setPMux(p.Pin, val)
|
||||
}
|
||||
|
||||
// getPinCfg returns the value for the correct PINCFG register for this pin.
|
||||
func (p GPIO) getPinCfg() sam.RegValue8 {
|
||||
return getPinCfg(p.Pin)
|
||||
}
|
||||
|
||||
// setPinCfg sets the value for the correct PINCFG register for this pin.
|
||||
func (p GPIO) setPinCfg(val sam.RegValue8) {
|
||||
setPinCfg(p.Pin, val)
|
||||
}
|
||||
|
||||
// UART on the SAMD21.
|
||||
type UART struct {
|
||||
Buffer *RingBuffer
|
||||
Bus *sam.SERCOM_USART_Type
|
||||
}
|
||||
|
||||
var (
|
||||
// The first hardware serial port on the SAMD21. Uses the SERCOM0 interface.
|
||||
UART0 = UART{Bus: sam.SERCOM0_USART, Buffer: NewRingBuffer()}
|
||||
|
||||
// The second hardware serial port on the SAMD21. Uses the SERCOM1 interface.
|
||||
UART1 = UART{Bus: sam.SERCOM1_USART, Buffer: NewRingBuffer()}
|
||||
)
|
||||
|
||||
const (
|
||||
sampleRate16X = 16
|
||||
lsbFirst = 1
|
||||
sercomRXPad0 = 0
|
||||
sercomRXPad1 = 1
|
||||
sercomRXPad2 = 2
|
||||
sercomRXPad3 = 3
|
||||
sercomTXPad0 = 0 // Only for UART
|
||||
sercomTXPad2 = 1 // Only for UART
|
||||
sercomTXPad023 = 2 // Only for UART with TX on PAD0, RTS on PAD2 and CTS on PAD3
|
||||
)
|
||||
|
||||
// Configure the UART.
|
||||
func (uart UART) Configure(config UARTConfig) {
|
||||
// Default baud rate to 115200.
|
||||
if config.BaudRate == 0 {
|
||||
config.BaudRate = 115200
|
||||
}
|
||||
|
||||
// determine pins
|
||||
if config.TX == 0 {
|
||||
// use default pins
|
||||
config.TX = UART_TX_PIN
|
||||
config.RX = UART_RX_PIN
|
||||
}
|
||||
|
||||
// determine pads
|
||||
var txpad, rxpad int
|
||||
switch config.TX {
|
||||
case UART_TX_PIN:
|
||||
txpad = sercomTXPad2
|
||||
case D10:
|
||||
txpad = sercomTXPad2
|
||||
case D11:
|
||||
txpad = sercomTXPad0
|
||||
default:
|
||||
panic("Invalid TX pin for UART")
|
||||
}
|
||||
|
||||
switch config.RX {
|
||||
case UART_RX_PIN:
|
||||
rxpad = sercomRXPad3
|
||||
case D10:
|
||||
rxpad = sercomRXPad2
|
||||
case D11:
|
||||
rxpad = sercomRXPad0
|
||||
case D12:
|
||||
rxpad = sercomRXPad3
|
||||
case D13:
|
||||
rxpad = sercomRXPad1
|
||||
default:
|
||||
panic("Invalid RX pin for UART")
|
||||
}
|
||||
|
||||
// configure pins
|
||||
GPIO{config.TX}.Configure(GPIOConfig{Mode: GPIO_SERCOM})
|
||||
GPIO{config.RX}.Configure(GPIOConfig{Mode: GPIO_SERCOM})
|
||||
|
||||
// reset SERCOM0
|
||||
uart.Bus.CTRLA |= sam.SERCOM_USART_CTRLA_SWRST
|
||||
for (uart.Bus.CTRLA&sam.SERCOM_USART_CTRLA_SWRST) > 0 ||
|
||||
(uart.Bus.SYNCBUSY&sam.SERCOM_USART_SYNCBUSY_SWRST) > 0 {
|
||||
}
|
||||
|
||||
// set UART mode/sample rate
|
||||
// SERCOM_USART_CTRLA_MODE(mode) |
|
||||
// SERCOM_USART_CTRLA_SAMPR(sampleRate);
|
||||
uart.Bus.CTRLA = (sam.SERCOM_USART_CTRLA_MODE_USART_INT_CLK << sam.SERCOM_USART_CTRLA_MODE_Pos) |
|
||||
(1 << sam.SERCOM_USART_CTRLA_SAMPR_Pos) // sample rate of 16x
|
||||
|
||||
// Set baud rate
|
||||
uart.SetBaudRate(config.BaudRate)
|
||||
|
||||
// setup UART frame
|
||||
// SERCOM_USART_CTRLA_FORM( (parityMode == SERCOM_NO_PARITY ? 0 : 1) ) |
|
||||
// dataOrder << SERCOM_USART_CTRLA_DORD_Pos;
|
||||
uart.Bus.CTRLA |= (0 << sam.SERCOM_USART_CTRLA_FORM_Pos) | // no parity
|
||||
(lsbFirst << sam.SERCOM_USART_CTRLA_DORD_Pos) // data order
|
||||
|
||||
// set UART stop bits/parity
|
||||
// SERCOM_USART_CTRLB_CHSIZE(charSize) |
|
||||
// nbStopBits << SERCOM_USART_CTRLB_SBMODE_Pos |
|
||||
// (parityMode == SERCOM_NO_PARITY ? 0 : parityMode) << SERCOM_USART_CTRLB_PMODE_Pos; //If no parity use default value
|
||||
uart.Bus.CTRLB |= (0 << sam.SERCOM_USART_CTRLB_CHSIZE_Pos) | // 8 bits is 0
|
||||
(0 << sam.SERCOM_USART_CTRLB_SBMODE_Pos) | // 1 stop bit is zero
|
||||
(0 << sam.SERCOM_USART_CTRLB_PMODE_Pos) // no parity
|
||||
|
||||
// set UART pads. This is not same as pins...
|
||||
// SERCOM_USART_CTRLA_TXPO(txPad) |
|
||||
// SERCOM_USART_CTRLA_RXPO(rxPad);
|
||||
uart.Bus.CTRLA |= sam.RegValue((txpad << sam.SERCOM_USART_CTRLA_TXPO_Pos) |
|
||||
(rxpad << sam.SERCOM_USART_CTRLA_RXPO_Pos))
|
||||
|
||||
// Enable Transceiver and Receiver
|
||||
//sercom->USART.CTRLB.reg |= SERCOM_USART_CTRLB_TXEN | SERCOM_USART_CTRLB_RXEN ;
|
||||
uart.Bus.CTRLB |= (sam.SERCOM_USART_CTRLB_TXEN | sam.SERCOM_USART_CTRLB_RXEN)
|
||||
|
||||
// Enable USART1 port.
|
||||
// sercom->USART.CTRLA.bit.ENABLE = 0x1u;
|
||||
uart.Bus.CTRLA |= sam.SERCOM_USART_CTRLA_ENABLE
|
||||
for (uart.Bus.SYNCBUSY & sam.SERCOM_USART_SYNCBUSY_ENABLE) > 0 {
|
||||
}
|
||||
|
||||
// setup interrupt on receive
|
||||
uart.Bus.INTENSET = sam.SERCOM_USART_INTENSET_RXC
|
||||
|
||||
// Enable RX IRQ.
|
||||
if config.TX == UART_TX_PIN {
|
||||
// UART0
|
||||
arm.EnableIRQ(sam.IRQ_SERCOM0)
|
||||
} else {
|
||||
// UART1
|
||||
arm.EnableIRQ(sam.IRQ_SERCOM1)
|
||||
}
|
||||
}
|
||||
|
||||
// SetBaudRate sets the communication speed for the UART.
|
||||
func (uart UART) SetBaudRate(br uint32) {
|
||||
// Asynchronous fractional mode (Table 24-2 in datasheet)
|
||||
// BAUD = fref / (sampleRateValue * fbaud)
|
||||
// (multiply by 8, to calculate fractional piece)
|
||||
// uint32_t baudTimes8 = (SystemCoreClock * 8) / (16 * baudrate);
|
||||
baud := (CPU_FREQUENCY * 8) / (sampleRate16X * br)
|
||||
|
||||
// sercom->USART.BAUD.FRAC.FP = (baudTimes8 % 8);
|
||||
// sercom->USART.BAUD.FRAC.BAUD = (baudTimes8 / 8);
|
||||
uart.Bus.BAUD = sam.RegValue16(((baud % 8) << sam.SERCOM_USART_BAUD_FRAC_MODE_FP_Pos) |
|
||||
((baud / 8) << sam.SERCOM_USART_BAUD_FRAC_MODE_BAUD_Pos))
|
||||
}
|
||||
|
||||
// WriteByte writes a byte of data to the UART.
|
||||
func (uart UART) WriteByte(c byte) error {
|
||||
// wait until ready to receive
|
||||
for (uart.Bus.INTFLAG & sam.SERCOM_USART_INTFLAG_DRE) == 0 {
|
||||
}
|
||||
uart.Bus.DATA = sam.RegValue16(c)
|
||||
return nil
|
||||
}
|
||||
|
||||
//go:export SERCOM0_IRQHandler
|
||||
func handleUART0() {
|
||||
// should reset IRQ
|
||||
UART0.Receive(byte((UART0.Bus.DATA & 0xFF)))
|
||||
UART0.Bus.INTFLAG |= sam.SERCOM_USART_INTFLAG_RXC
|
||||
}
|
||||
|
||||
//go:export SERCOM1_IRQHandler
|
||||
func handleUART1() {
|
||||
// should reset IRQ
|
||||
UART1.Receive(byte((UART1.Bus.DATA & 0xFF)))
|
||||
UART1.Bus.INTFLAG |= sam.SERCOM_USART_INTFLAG_RXC
|
||||
}
|
||||
|
||||
// I2C on the SAMD21.
|
||||
type I2C struct {
|
||||
Bus *sam.SERCOM_I2CM_Type
|
||||
}
|
||||
|
||||
// Since the I2C interfaces on the SAMD21 use the SERCOMx peripherals,
|
||||
// you can have multiple ones. we currently only implement one.
|
||||
var (
|
||||
I2C0 = I2C{Bus: sam.SERCOM3_I2CM}
|
||||
)
|
||||
|
||||
// I2CConfig is used to store config info for I2C.
|
||||
type I2CConfig struct {
|
||||
Frequency uint32
|
||||
SCL uint8
|
||||
SDA uint8
|
||||
}
|
||||
|
||||
const (
|
||||
// Default rise time in nanoseconds, based on 4.7K ohm pull up resistors
|
||||
riseTimeNanoseconds = 125
|
||||
|
||||
// wire bus states
|
||||
wireUnknownState = 0
|
||||
wireIdleState = 1
|
||||
wireOwnerState = 2
|
||||
wireBusyState = 3
|
||||
|
||||
// wire commands
|
||||
wireCmdNoAction = 0
|
||||
wireCmdRepeatStart = 1
|
||||
wireCmdRead = 2
|
||||
wireCmdStop = 3
|
||||
)
|
||||
|
||||
const i2cTimeout = 1000
|
||||
|
||||
// Configure is intended to setup the I2C interface.
|
||||
func (i2c I2C) Configure(config I2CConfig) {
|
||||
// Default I2C bus speed is 100 kHz.
|
||||
if config.Frequency == 0 {
|
||||
config.Frequency = TWI_FREQ_100KHZ
|
||||
}
|
||||
|
||||
// reset SERCOM3
|
||||
i2c.Bus.CTRLA |= sam.SERCOM_I2CM_CTRLA_SWRST
|
||||
for (i2c.Bus.CTRLA&sam.SERCOM_I2CM_CTRLA_SWRST) > 0 ||
|
||||
(i2c.Bus.SYNCBUSY&sam.SERCOM_I2CM_SYNCBUSY_SWRST) > 0 {
|
||||
}
|
||||
|
||||
// Set i2c master mode
|
||||
//SERCOM_I2CM_CTRLA_MODE( I2C_MASTER_OPERATION )
|
||||
i2c.Bus.CTRLA = (sam.SERCOM_I2CM_CTRLA_MODE_I2C_MASTER << sam.SERCOM_I2CM_CTRLA_MODE_Pos) // |
|
||||
|
||||
i2c.SetBaudRate(config.Frequency)
|
||||
|
||||
// Enable I2CM port.
|
||||
// sercom->USART.CTRLA.bit.ENABLE = 0x1u;
|
||||
i2c.Bus.CTRLA |= sam.SERCOM_I2CM_CTRLA_ENABLE
|
||||
for (i2c.Bus.SYNCBUSY & sam.SERCOM_I2CM_SYNCBUSY_ENABLE) > 0 {
|
||||
}
|
||||
|
||||
// set bus idle mode
|
||||
i2c.Bus.STATUS |= (wireIdleState << sam.SERCOM_I2CM_STATUS_BUSSTATE_Pos)
|
||||
for (i2c.Bus.SYNCBUSY & sam.SERCOM_I2CM_SYNCBUSY_SYSOP) > 0 {
|
||||
}
|
||||
|
||||
// enable pins
|
||||
GPIO{SDA_PIN}.Configure(GPIOConfig{Mode: GPIO_SERCOM})
|
||||
GPIO{SCL_PIN}.Configure(GPIOConfig{Mode: GPIO_SERCOM})
|
||||
}
|
||||
|
||||
// SetBaudRate sets the communication speed for the I2C.
|
||||
func (i2c I2C) SetBaudRate(br uint32) {
|
||||
// Synchronous arithmetic baudrate, via Arduino SAMD implementation:
|
||||
// SystemCoreClock / ( 2 * baudrate) - 5 - (((SystemCoreClock / 1000000) * WIRE_RISE_TIME_NANOSECONDS) / (2 * 1000));
|
||||
baud := CPU_FREQUENCY/(2*br) - 5 - (((CPU_FREQUENCY / 1000000) * riseTimeNanoseconds) / (2 * 1000))
|
||||
i2c.Bus.BAUD = sam.RegValue(baud)
|
||||
}
|
||||
|
||||
// Tx does a single I2C transaction at the specified address.
|
||||
// It clocks out the given address, writes the bytes in w, reads back len(r)
|
||||
// bytes and stores them in r, and generates a stop condition on the bus.
|
||||
func (i2c I2C) Tx(addr uint16, w, r []byte) error {
|
||||
var err error
|
||||
if len(w) != 0 {
|
||||
// send start/address for write
|
||||
i2c.sendAddress(addr, true)
|
||||
|
||||
// wait until transmission complete
|
||||
timeout := i2cTimeout
|
||||
for (i2c.Bus.INTFLAG & sam.SERCOM_I2CM_INTFLAG_MB) == 0 {
|
||||
timeout--
|
||||
if timeout == 0 {
|
||||
return errors.New("I2C timeout on ready to write data")
|
||||
}
|
||||
}
|
||||
|
||||
// ACK received (0: ACK, 1: NACK)
|
||||
if (i2c.Bus.STATUS & sam.SERCOM_I2CM_STATUS_RXNACK) > 0 {
|
||||
return errors.New("I2C write error: expected ACK not NACK")
|
||||
}
|
||||
|
||||
// write data
|
||||
for _, b := range w {
|
||||
err = i2c.WriteByte(b)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
err = i2c.signalStop()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if len(r) != 0 {
|
||||
// send start/address for read
|
||||
i2c.sendAddress(addr, false)
|
||||
|
||||
// wait transmission complete
|
||||
for (i2c.Bus.INTFLAG & sam.SERCOM_I2CM_INTFLAG_SB) == 0 {
|
||||
// If the slave NACKS the address, the MB bit will be set.
|
||||
// In that case, send a stop condition and return error.
|
||||
if (i2c.Bus.INTFLAG & sam.SERCOM_I2CM_INTFLAG_MB) > 0 {
|
||||
i2c.Bus.CTRLB |= (wireCmdStop << sam.SERCOM_I2CM_CTRLB_CMD_Pos) // Stop condition
|
||||
return errors.New("I2C read error: expected ACK not NACK")
|
||||
}
|
||||
}
|
||||
|
||||
// ACK received (0: ACK, 1: NACK)
|
||||
if (i2c.Bus.STATUS & sam.SERCOM_I2CM_STATUS_RXNACK) > 0 {
|
||||
return errors.New("I2C read error: expected ACK not NACK")
|
||||
}
|
||||
|
||||
// read first byte
|
||||
r[0] = i2c.readByte()
|
||||
for i := 1; i < len(r); i++ {
|
||||
// Send an ACK
|
||||
i2c.Bus.CTRLB &^= sam.SERCOM_I2CM_CTRLB_ACKACT
|
||||
|
||||
i2c.signalRead()
|
||||
|
||||
// Read data and send the ACK
|
||||
r[i] = i2c.readByte()
|
||||
}
|
||||
|
||||
// Send NACK to end transmission
|
||||
i2c.Bus.CTRLB |= sam.SERCOM_I2CM_CTRLB_ACKACT
|
||||
|
||||
err = i2c.signalStop()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// WriteByte writes a single byte to the I2C bus.
|
||||
func (i2c I2C) WriteByte(data byte) error {
|
||||
// Send data byte
|
||||
i2c.Bus.DATA = sam.RegValue8(data)
|
||||
|
||||
// wait until transmission successful
|
||||
timeout := i2cTimeout
|
||||
for (i2c.Bus.INTFLAG & sam.SERCOM_I2CM_INTFLAG_MB) == 0 {
|
||||
// check for bus error
|
||||
if (sam.SERCOM3_I2CM.STATUS & sam.SERCOM_I2CM_STATUS_BUSERR) > 0 {
|
||||
return errors.New("I2C bus error")
|
||||
}
|
||||
timeout--
|
||||
if timeout == 0 {
|
||||
return errors.New("I2C timeout on write data")
|
||||
}
|
||||
}
|
||||
|
||||
if (i2c.Bus.STATUS & sam.SERCOM_I2CM_STATUS_RXNACK) > 0 {
|
||||
return errors.New("I2C write error: expected ACK not NACK")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// sendAddress sends the address and start signal
|
||||
func (i2c I2C) sendAddress(address uint16, write bool) error {
|
||||
data := (address << 1)
|
||||
if !write {
|
||||
data |= 1 // set read flag
|
||||
}
|
||||
|
||||
// wait until bus ready
|
||||
timeout := i2cTimeout
|
||||
for (i2c.Bus.STATUS&(wireIdleState<<sam.SERCOM_I2CM_STATUS_BUSSTATE_Pos)) == 0 &&
|
||||
(i2c.Bus.STATUS&(wireOwnerState<<sam.SERCOM_I2CM_STATUS_BUSSTATE_Pos)) == 0 {
|
||||
timeout--
|
||||
if timeout == 0 {
|
||||
return errors.New("I2C timeout on bus ready")
|
||||
}
|
||||
}
|
||||
i2c.Bus.ADDR = sam.RegValue(data)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (i2c I2C) signalStop() error {
|
||||
i2c.Bus.CTRLB |= (wireCmdStop << sam.SERCOM_I2CM_CTRLB_CMD_Pos) // Stop command
|
||||
timeout := i2cTimeout
|
||||
for (i2c.Bus.SYNCBUSY & sam.SERCOM_I2CM_SYNCBUSY_SYSOP) > 0 {
|
||||
timeout--
|
||||
if timeout == 0 {
|
||||
return errors.New("I2C timeout on signal stop")
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (i2c I2C) signalRead() error {
|
||||
i2c.Bus.CTRLB |= (wireCmdRead << sam.SERCOM_I2CM_CTRLB_CMD_Pos) // Read command
|
||||
timeout := i2cTimeout
|
||||
for (i2c.Bus.SYNCBUSY & sam.SERCOM_I2CM_SYNCBUSY_SYSOP) > 0 {
|
||||
timeout--
|
||||
if timeout == 0 {
|
||||
return errors.New("I2C timeout on signal read")
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (i2c I2C) readByte() byte {
|
||||
for (i2c.Bus.INTFLAG & sam.SERCOM_I2CM_INTFLAG_SB) == 0 {
|
||||
}
|
||||
return byte(i2c.Bus.DATA)
|
||||
}
|
||||
|
||||
// PWM
|
||||
const period = 0xFFFF
|
||||
|
||||
// InitPWM initializes the PWM interface.
|
||||
func InitPWM() {
|
||||
// turn on timer clocks used for PWM
|
||||
sam.PM.APBCMASK |= sam.PM_APBCMASK_TCC0_ | sam.PM_APBCMASK_TCC1_ | sam.PM_APBCMASK_TCC2_
|
||||
|
||||
// Use GCLK0 for TCC0/TCC1
|
||||
sam.GCLK.CLKCTRL = sam.RegValue16((sam.GCLK_CLKCTRL_ID_TCC0_TCC1 << sam.GCLK_CLKCTRL_ID_Pos) |
|
||||
(sam.GCLK_CLKCTRL_GEN_GCLK0 << sam.GCLK_CLKCTRL_GEN_Pos) |
|
||||
sam.GCLK_CLKCTRL_CLKEN)
|
||||
for (sam.GCLK.STATUS & sam.GCLK_STATUS_SYNCBUSY) > 0 {
|
||||
}
|
||||
|
||||
// Use GCLK0 for TCC2/TC3
|
||||
sam.GCLK.CLKCTRL = sam.RegValue16((sam.GCLK_CLKCTRL_ID_TCC2_TC3 << sam.GCLK_CLKCTRL_ID_Pos) |
|
||||
(sam.GCLK_CLKCTRL_GEN_GCLK0 << sam.GCLK_CLKCTRL_GEN_Pos) |
|
||||
sam.GCLK_CLKCTRL_CLKEN)
|
||||
for (sam.GCLK.STATUS & sam.GCLK_STATUS_SYNCBUSY) > 0 {
|
||||
}
|
||||
}
|
||||
|
||||
// Configure configures a PWM pin for output.
|
||||
func (pwm PWM) Configure() {
|
||||
// figure out which TCCX timer for this pin
|
||||
timer := pwm.getTimer()
|
||||
|
||||
// disable timer
|
||||
timer.CTRLA &^= sam.TCC_CTRLA_ENABLE
|
||||
// Wait for synchronization
|
||||
for (timer.SYNCBUSY & sam.TCC_SYNCBUSY_ENABLE) > 0 {
|
||||
}
|
||||
|
||||
// Use "Normal PWM" (single-slope PWM)
|
||||
timer.WAVE |= sam.TCC_WAVE_WAVEGEN_NPWM
|
||||
// Wait for synchronization
|
||||
for (timer.SYNCBUSY & sam.TCC_SYNCBUSY_WAVE) > 0 {
|
||||
}
|
||||
|
||||
// Set the period (the number to count to (TOP) before resetting timer)
|
||||
//TCC0->PER.reg = period;
|
||||
timer.PER = period
|
||||
// Wait for synchronization
|
||||
for (timer.SYNCBUSY & sam.TCC_SYNCBUSY_PER) > 0 {
|
||||
}
|
||||
|
||||
// Set pin as output
|
||||
sam.PORT.DIRSET0 = (1 << pwm.Pin)
|
||||
// Set pin to low
|
||||
sam.PORT.OUTCLR0 = (1 << pwm.Pin)
|
||||
|
||||
// Enable the port multiplexer for pin
|
||||
pwm.setPinCfg(sam.PORT_PINCFG0_PMUXEN)
|
||||
|
||||
// Connect TCCX timer to pin.
|
||||
// we normally use the F channel aka ALT
|
||||
pwmConfig := GPIO_PWM_ALT
|
||||
|
||||
// in the case of PA6 or PA7 we have to use E channel
|
||||
if pwm.Pin == 6 || pwm.Pin == 7 {
|
||||
pwmConfig = GPIO_PWM
|
||||
}
|
||||
|
||||
if pwm.Pin&1 > 0 {
|
||||
// odd pin, so save the even pins
|
||||
val := pwm.getPMux() & sam.PORT_PMUX0_PMUXE_Msk
|
||||
pwm.setPMux(val | sam.RegValue8(pwmConfig<<sam.PORT_PMUX0_PMUXO_Pos))
|
||||
} else {
|
||||
// even pin, so save the odd pins
|
||||
val := pwm.getPMux() & sam.PORT_PMUX0_PMUXO_Msk
|
||||
pwm.setPMux(val | sam.RegValue8(pwmConfig<<sam.PORT_PMUX0_PMUXE_Pos))
|
||||
}
|
||||
}
|
||||
|
||||
// Set turns on the duty cycle for a PWM pin using the provided value.
|
||||
func (pwm PWM) Set(value uint16) {
|
||||
// figure out which TCCX timer for this pin
|
||||
timer := pwm.getTimer()
|
||||
|
||||
// disable output
|
||||
timer.CTRLA &^= sam.TCC_CTRLA_ENABLE
|
||||
|
||||
// Wait for synchronization
|
||||
for (timer.SYNCBUSY & sam.TCC_SYNCBUSY_ENABLE) > 0 {
|
||||
}
|
||||
|
||||
// Set PWM signal to output duty cycle
|
||||
pwm.setChannel(sam.RegValue(value))
|
||||
|
||||
// Wait for synchronization on all channels
|
||||
for (timer.SYNCBUSY & (sam.TCC_SYNCBUSY_CC0 |
|
||||
sam.TCC_SYNCBUSY_CC1 |
|
||||
sam.TCC_SYNCBUSY_CC2 |
|
||||
sam.TCC_SYNCBUSY_CC3)) > 0 {
|
||||
}
|
||||
|
||||
// enable
|
||||
timer.CTRLA |= sam.TCC_CTRLA_ENABLE
|
||||
// Wait for synchronization
|
||||
for (timer.SYNCBUSY & sam.TCC_SYNCBUSY_ENABLE) > 0 {
|
||||
}
|
||||
}
|
||||
|
||||
// getPMux returns the value for the correct PMUX register for this pin.
|
||||
func (pwm PWM) getPMux() sam.RegValue8 {
|
||||
return getPMux(pwm.Pin)
|
||||
}
|
||||
|
||||
// setPMux sets the value for the correct PMUX register for this pin.
|
||||
func (pwm PWM) setPMux(val sam.RegValue8) {
|
||||
setPMux(pwm.Pin, val)
|
||||
}
|
||||
|
||||
// getPinCfg returns the value for the correct PINCFG register for this pin.
|
||||
func (pwm PWM) getPinCfg() sam.RegValue8 {
|
||||
return getPinCfg(pwm.Pin)
|
||||
}
|
||||
|
||||
// setPinCfg sets the value for the correct PINCFG register for this pin.
|
||||
func (pwm PWM) setPinCfg(val sam.RegValue8) {
|
||||
setPinCfg(pwm.Pin, val)
|
||||
}
|
||||
|
||||
// getPMux returns the value for the correct PMUX register for this pin.
|
||||
func getPMux(p uint8) sam.RegValue8 {
|
||||
pin := p >> 1
|
||||
switch pin {
|
||||
case 0:
|
||||
return sam.PORT.PMUX0_0
|
||||
case 1:
|
||||
return sam.PORT.PMUX0_1
|
||||
case 2:
|
||||
return sam.PORT.PMUX0_2
|
||||
case 3:
|
||||
return sam.PORT.PMUX0_3
|
||||
case 4:
|
||||
return sam.PORT.PMUX0_4
|
||||
case 5:
|
||||
return sam.PORT.PMUX0_5
|
||||
case 6:
|
||||
return sam.PORT.PMUX0_6
|
||||
case 7:
|
||||
return sam.PORT.PMUX0_7
|
||||
case 8:
|
||||
return sam.PORT.PMUX0_8
|
||||
case 9:
|
||||
return sam.PORT.PMUX0_9
|
||||
case 10:
|
||||
return sam.PORT.PMUX0_10
|
||||
case 11:
|
||||
return sam.PORT.PMUX0_11
|
||||
case 12:
|
||||
return sam.PORT.PMUX0_12
|
||||
case 13:
|
||||
return sam.PORT.PMUX0_13
|
||||
case 14:
|
||||
return sam.PORT.PMUX0_14
|
||||
case 15:
|
||||
return sam.PORT.PMUX0_15
|
||||
default:
|
||||
return 0
|
||||
}
|
||||
}
|
||||
|
||||
// setPMux sets the value for the correct PMUX register for this pin.
|
||||
func setPMux(p uint8, val sam.RegValue8) {
|
||||
pin := p >> 1
|
||||
switch pin {
|
||||
case 0:
|
||||
sam.PORT.PMUX0_0 = val
|
||||
case 1:
|
||||
sam.PORT.PMUX0_1 = val
|
||||
case 2:
|
||||
sam.PORT.PMUX0_2 = val
|
||||
case 3:
|
||||
sam.PORT.PMUX0_3 = val
|
||||
case 4:
|
||||
sam.PORT.PMUX0_4 = val
|
||||
case 5:
|
||||
sam.PORT.PMUX0_5 = val
|
||||
case 6:
|
||||
sam.PORT.PMUX0_6 = val
|
||||
case 7:
|
||||
sam.PORT.PMUX0_7 = val
|
||||
case 8:
|
||||
sam.PORT.PMUX0_8 = val
|
||||
case 9:
|
||||
sam.PORT.PMUX0_9 = val
|
||||
case 10:
|
||||
sam.PORT.PMUX0_10 = val
|
||||
case 11:
|
||||
sam.PORT.PMUX0_11 = val
|
||||
case 12:
|
||||
sam.PORT.PMUX0_12 = val
|
||||
case 13:
|
||||
sam.PORT.PMUX0_13 = val
|
||||
case 14:
|
||||
sam.PORT.PMUX0_14 = val
|
||||
case 15:
|
||||
sam.PORT.PMUX0_15 = val
|
||||
}
|
||||
}
|
||||
|
||||
// getPinCfg returns the value for the correct PINCFG register for this pin.
|
||||
func getPinCfg(p uint8) sam.RegValue8 {
|
||||
switch p {
|
||||
case 0:
|
||||
return sam.PORT.PINCFG0_0
|
||||
case 1:
|
||||
return sam.PORT.PINCFG0_1
|
||||
case 2:
|
||||
return sam.PORT.PINCFG0_2
|
||||
case 3:
|
||||
return sam.PORT.PINCFG0_3
|
||||
case 4:
|
||||
return sam.PORT.PINCFG0_4
|
||||
case 5:
|
||||
return sam.PORT.PINCFG0_5
|
||||
case 6:
|
||||
return sam.PORT.PINCFG0_6
|
||||
case 7:
|
||||
return sam.PORT.PINCFG0_7
|
||||
case 8:
|
||||
return sam.PORT.PINCFG0_8
|
||||
case 9:
|
||||
return sam.PORT.PINCFG0_9
|
||||
case 10:
|
||||
return sam.PORT.PINCFG0_10
|
||||
case 11:
|
||||
return sam.PORT.PINCFG0_11
|
||||
case 12:
|
||||
return sam.PORT.PINCFG0_12
|
||||
case 13:
|
||||
return sam.PORT.PINCFG0_13
|
||||
case 14:
|
||||
return sam.PORT.PINCFG0_14
|
||||
case 15:
|
||||
return sam.PORT.PINCFG0_15
|
||||
case 16:
|
||||
return sam.PORT.PINCFG0_16
|
||||
case 17:
|
||||
return sam.PORT.PINCFG0_17
|
||||
case 18:
|
||||
return sam.PORT.PINCFG0_18
|
||||
case 19:
|
||||
return sam.PORT.PINCFG0_19
|
||||
case 20:
|
||||
return sam.PORT.PINCFG0_20
|
||||
case 21:
|
||||
return sam.PORT.PINCFG0_21
|
||||
case 22:
|
||||
return sam.PORT.PINCFG0_22
|
||||
case 23:
|
||||
return sam.PORT.PINCFG0_23
|
||||
case 24:
|
||||
return sam.PORT.PINCFG0_24
|
||||
case 25:
|
||||
return sam.PORT.PINCFG0_25
|
||||
case 26:
|
||||
return sam.PORT.PINCFG0_26
|
||||
case 27:
|
||||
return sam.PORT.PINCFG0_27
|
||||
case 28:
|
||||
return sam.PORT.PINCFG0_28
|
||||
case 29:
|
||||
return sam.PORT.PINCFG0_29
|
||||
case 30:
|
||||
return sam.PORT.PINCFG0_30
|
||||
case 31:
|
||||
return sam.PORT.PINCFG0_31
|
||||
default:
|
||||
return 0
|
||||
}
|
||||
}
|
||||
|
||||
// setPinCfg sets the value for the correct PINCFG register for this pin.
|
||||
func setPinCfg(p uint8, val sam.RegValue8) {
|
||||
switch p {
|
||||
case 0:
|
||||
sam.PORT.PINCFG0_0 = val
|
||||
case 1:
|
||||
sam.PORT.PINCFG0_1 = val
|
||||
case 2:
|
||||
sam.PORT.PINCFG0_2 = val
|
||||
case 3:
|
||||
sam.PORT.PINCFG0_3 = val
|
||||
case 4:
|
||||
sam.PORT.PINCFG0_4 = val
|
||||
case 5:
|
||||
sam.PORT.PINCFG0_5 = val
|
||||
case 6:
|
||||
sam.PORT.PINCFG0_6 = val
|
||||
case 7:
|
||||
sam.PORT.PINCFG0_7 = val
|
||||
case 8:
|
||||
sam.PORT.PINCFG0_8 = val
|
||||
case 9:
|
||||
sam.PORT.PINCFG0_9 = val
|
||||
case 10:
|
||||
sam.PORT.PINCFG0_10 = val
|
||||
case 11:
|
||||
sam.PORT.PINCFG0_11 = val
|
||||
case 12:
|
||||
sam.PORT.PINCFG0_12 = val
|
||||
case 13:
|
||||
sam.PORT.PINCFG0_13 = val
|
||||
case 14:
|
||||
sam.PORT.PINCFG0_14 = val
|
||||
case 15:
|
||||
sam.PORT.PINCFG0_15 = val
|
||||
case 16:
|
||||
sam.PORT.PINCFG0_16 = val
|
||||
case 17:
|
||||
sam.PORT.PINCFG0_17 = val
|
||||
case 18:
|
||||
sam.PORT.PINCFG0_18 = val
|
||||
case 19:
|
||||
sam.PORT.PINCFG0_19 = val
|
||||
case 20:
|
||||
sam.PORT.PINCFG0_20 = val
|
||||
case 21:
|
||||
sam.PORT.PINCFG0_21 = val
|
||||
case 22:
|
||||
sam.PORT.PINCFG0_22 = val
|
||||
case 23:
|
||||
sam.PORT.PINCFG0_23 = val
|
||||
case 24:
|
||||
sam.PORT.PINCFG0_24 = val
|
||||
case 25:
|
||||
sam.PORT.PINCFG0_25 = val
|
||||
case 26:
|
||||
sam.PORT.PINCFG0_26 = val
|
||||
case 27:
|
||||
sam.PORT.PINCFG0_27 = val
|
||||
case 28:
|
||||
sam.PORT.PINCFG0_28 = val
|
||||
case 29:
|
||||
sam.PORT.PINCFG0_29 = val
|
||||
case 30:
|
||||
sam.PORT.PINCFG0_30 = val
|
||||
case 31:
|
||||
sam.PORT.PINCFG0_31 = val
|
||||
}
|
||||
}
|
||||
|
||||
// getTimer returns the timer to be used for PWM on this pin
|
||||
func (pwm PWM) getTimer() *sam.TCC_Type {
|
||||
switch pwm.Pin {
|
||||
case 6:
|
||||
return sam.TCC1
|
||||
case 7:
|
||||
return sam.TCC1
|
||||
case 8:
|
||||
return sam.TCC1
|
||||
case 9:
|
||||
return sam.TCC1
|
||||
case 14:
|
||||
return sam.TCC0
|
||||
case 15:
|
||||
return sam.TCC0
|
||||
case 16:
|
||||
return sam.TCC0
|
||||
case 17:
|
||||
return sam.TCC0
|
||||
case 18:
|
||||
return sam.TCC0
|
||||
case 19:
|
||||
return sam.TCC0
|
||||
case 20:
|
||||
return sam.TCC0
|
||||
case 21:
|
||||
return sam.TCC0
|
||||
default:
|
||||
return nil // not supported on this pin
|
||||
}
|
||||
}
|
||||
|
||||
// setChannel sets the value for the correct channel for PWM on this pin
|
||||
func (pwm PWM) setChannel(val sam.RegValue) {
|
||||
switch pwm.Pin {
|
||||
case 6:
|
||||
pwm.getTimer().CC0 = val
|
||||
case 7:
|
||||
pwm.getTimer().CC1 = val
|
||||
case 8:
|
||||
pwm.getTimer().CC0 = val
|
||||
case 9:
|
||||
pwm.getTimer().CC1 = val
|
||||
case 14:
|
||||
pwm.getTimer().CC0 = val
|
||||
case 15:
|
||||
pwm.getTimer().CC1 = val
|
||||
case 16:
|
||||
pwm.getTimer().CC2 = val
|
||||
case 17:
|
||||
pwm.getTimer().CC3 = val
|
||||
case 18:
|
||||
pwm.getTimer().CC2 = val
|
||||
case 19:
|
||||
pwm.getTimer().CC3 = val
|
||||
case 20:
|
||||
pwm.getTimer().CC2 = val
|
||||
case 21:
|
||||
pwm.getTimer().CC3 = val
|
||||
default:
|
||||
return // not supported on this pin
|
||||
}
|
||||
}
|
||||
@@ -9,19 +9,19 @@ import (
|
||||
// Configure sets the pin to input or output.
|
||||
func (p GPIO) Configure(config GPIOConfig) {
|
||||
if config.Mode == GPIO_OUTPUT { // set output bit
|
||||
*avr.DDRB |= 1 << p.Pin
|
||||
avr.DDRB.SetBits(1 << p.Pin)
|
||||
} else { // configure input: clear output bit
|
||||
*avr.DDRB &^= 1 << p.Pin
|
||||
avr.DDRB.ClearBits(1 << p.Pin)
|
||||
}
|
||||
}
|
||||
|
||||
func (p GPIO) getPortMask() (*avr.RegValue, uint8) {
|
||||
func (p GPIO) getPortMask() (*avr.Register8, uint8) {
|
||||
return avr.PORTB, 1 << p.Pin
|
||||
}
|
||||
|
||||
// Get returns the current value of a GPIO pin.
|
||||
func (p GPIO) Get() bool {
|
||||
val := *avr.PINB & (1 << p.Pin)
|
||||
val := avr.PINB.Get() & (1 << p.Pin)
|
||||
return (val > 0)
|
||||
}
|
||||
|
||||
|
||||
+12
-14
@@ -17,10 +17,10 @@ const (
|
||||
func (p GPIO) Set(value bool) {
|
||||
if value { // set bits
|
||||
port, mask := p.PortMaskSet()
|
||||
*port = mask
|
||||
port.Set(mask)
|
||||
} else { // clear bits
|
||||
port, mask := p.PortMaskClear()
|
||||
*port = mask
|
||||
port.Set(mask)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -30,9 +30,9 @@ func (p GPIO) Set(value bool) {
|
||||
// Warning: there are no separate pin set/clear registers on the AVR. The
|
||||
// returned mask is only valid as long as no other pin in the same port has been
|
||||
// changed.
|
||||
func (p GPIO) PortMaskSet() (*avr.RegValue, avr.RegValue) {
|
||||
func (p GPIO) PortMaskSet() (*avr.Register8, uint8) {
|
||||
port, mask := p.getPortMask()
|
||||
return port, *port | avr.RegValue(mask)
|
||||
return port, port.Get() | mask
|
||||
}
|
||||
|
||||
// Return the register and mask to disable a given port. This can be used to
|
||||
@@ -41,18 +41,18 @@ func (p GPIO) PortMaskSet() (*avr.RegValue, avr.RegValue) {
|
||||
// Warning: there are no separate pin set/clear registers on the AVR. The
|
||||
// returned mask is only valid as long as no other pin in the same port has been
|
||||
// changed.
|
||||
func (p GPIO) PortMaskClear() (*avr.RegValue, avr.RegValue) {
|
||||
func (p GPIO) PortMaskClear() (*avr.Register8, uint8) {
|
||||
port, mask := p.getPortMask()
|
||||
return port, *port &^ avr.RegValue(mask)
|
||||
return port, port.Get() &^ mask
|
||||
}
|
||||
|
||||
// InitADC initializes the registers needed for ADC.
|
||||
func InitADC() {
|
||||
// set a2d prescaler so we are inside the desired 50-200 KHz range at 16MHz.
|
||||
*avr.ADCSRA |= (avr.ADCSRA_ADPS2 | avr.ADCSRA_ADPS1 | avr.ADCSRA_ADPS0)
|
||||
avr.ADCSRA.SetBits(avr.ADCSRA_ADPS2 | avr.ADCSRA_ADPS1 | avr.ADCSRA_ADPS0)
|
||||
|
||||
// enable a2d conversions
|
||||
*avr.ADCSRA |= avr.ADCSRA_ADEN
|
||||
avr.ADCSRA.SetBits(avr.ADCSRA_ADEN)
|
||||
}
|
||||
|
||||
// Configure configures a ADCPin to be able to be used to read data.
|
||||
@@ -68,18 +68,16 @@ func (a ADC) Get() uint16 {
|
||||
// set the ADLAR bit (left-adjusted result) to get a value scaled to 16
|
||||
// bits. This has the same effect as shifting the return value left by 6
|
||||
// bits.
|
||||
*avr.ADMUX = avr.RegValue(avr.ADMUX_REFS0 | avr.ADMUX_ADLAR | (a.Pin & 0x07))
|
||||
avr.ADMUX.Set(avr.ADMUX_REFS0 | avr.ADMUX_ADLAR | (a.Pin & 0x07))
|
||||
|
||||
// start the conversion
|
||||
*avr.ADCSRA |= avr.ADCSRA_ADSC
|
||||
avr.ADCSRA.SetBits(avr.ADCSRA_ADSC)
|
||||
|
||||
// ADSC is cleared when the conversion finishes
|
||||
for ok := true; ok; ok = (*avr.ADCSRA & avr.ADCSRA_ADSC) > 0 {
|
||||
for ok := true; ok; ok = (avr.ADCSRA.Get() & avr.ADCSRA_ADSC) > 0 {
|
||||
}
|
||||
|
||||
low := uint16(*avr.ADCL)
|
||||
high := uint16(*avr.ADCH)
|
||||
return uint16(low) | uint16(high<<8)
|
||||
return uint16(avr.ADCL.Get()) | uint16(avr.ADCH.Get())<<8
|
||||
}
|
||||
|
||||
// I2C on AVR.
|
||||
|
||||
@@ -185,21 +185,20 @@ func (i2c I2C) Tx(addr uint16, w, r []byte) error {
|
||||
}
|
||||
}
|
||||
if len(r) != 0 {
|
||||
// To trigger suspend task when a byte is received
|
||||
i2c.Bus.SHORTS = nrf.TWI_SHORTS_BB_SUSPEND
|
||||
i2c.Bus.TASKS_STARTRX = 1 // re-start transmission for reading
|
||||
for i := range r { // read each char
|
||||
if i+1 == len(r) {
|
||||
// The 'stop' signal must be sent before reading back the last
|
||||
// byte, so that it will be sent by the I2C peripheral right
|
||||
// after the last byte has been read.
|
||||
r[i] = i2c.readLastByte()
|
||||
} else {
|
||||
r[i] = i2c.readByte()
|
||||
// To trigger stop task when last byte is received, set before resume task.
|
||||
i2c.Bus.SHORTS = nrf.TWI_SHORTS_BB_STOP
|
||||
}
|
||||
i2c.Bus.TASKS_RESUME = 1 // re-start transmission for reading
|
||||
r[i] = i2c.readByte()
|
||||
}
|
||||
} else {
|
||||
// Nothing to read back. Stop the transmission.
|
||||
i2c.signalStop()
|
||||
}
|
||||
i2c.signalStop()
|
||||
i2c.Bus.SHORTS = nrf.TWI_SHORTS_BB_SUSPEND_Disabled
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -229,16 +228,6 @@ func (i2c I2C) readByte() byte {
|
||||
return byte(i2c.Bus.RXD)
|
||||
}
|
||||
|
||||
// readLastByte reads a single byte from the I2C bus, sending a stop signal
|
||||
// after it has been read.
|
||||
func (i2c I2C) readLastByte() byte {
|
||||
for i2c.Bus.EVENTS_RXDREADY == 0 {
|
||||
}
|
||||
i2c.Bus.EVENTS_RXDREADY = 0
|
||||
i2c.signalStop() // signal 'stop' now, so it is sent when reading RXD
|
||||
return byte(i2c.Bus.RXD)
|
||||
}
|
||||
|
||||
// SPI on the NRF.
|
||||
type SPI struct {
|
||||
Bus *nrf.SPI_Type
|
||||
|
||||
@@ -6,6 +6,8 @@ import (
|
||||
"device/nrf"
|
||||
)
|
||||
|
||||
const CPU_FREQUENCY = 16000000
|
||||
|
||||
// Get peripheral and pin number for this GPIO pin.
|
||||
func (p GPIO) getPortPin() (*nrf.GPIO_Type, uint8) {
|
||||
return nrf.GPIO, p.Pin
|
||||
|
||||
@@ -7,6 +7,8 @@ import (
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
const CPU_FREQUENCY = 64000000
|
||||
|
||||
// Get peripheral and pin number for this GPIO pin.
|
||||
func (p GPIO) getPortPin() (*nrf.GPIO_Type, uint8) {
|
||||
return nrf.P0, p.Pin
|
||||
|
||||
@@ -7,6 +7,8 @@ import (
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
const CPU_FREQUENCY = 64000000
|
||||
|
||||
// Get peripheral and pin number for this GPIO pin.
|
||||
func (p GPIO) getPortPin() (*nrf.GPIO_Type, uint8) {
|
||||
if p.Pin >= 32 {
|
||||
|
||||
@@ -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
-1
@@ -1,4 +1,4 @@
|
||||
// +build nrf stm32f103xx
|
||||
// +build nrf stm32f103xx atsamd21g18a
|
||||
|
||||
package machine
|
||||
|
||||
|
||||
@@ -0,0 +1,599 @@
|
||||
// +build sam
|
||||
|
||||
package machine
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"device/sam"
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
)
|
||||
|
||||
const deviceDescriptorSize = 18
|
||||
|
||||
// DeviceDescriptor implements the USB standard device descriptor.
|
||||
//
|
||||
// Table 9-8. Standard Device Descriptor
|
||||
// bLength, bDescriptorType, bcdUSB, bDeviceClass, bDeviceSubClass, bDeviceProtocol, bMaxPacketSize0,
|
||||
// idVendor, idProduct, bcdDevice, iManufacturer, iProduct, iSerialNumber, bNumConfigurations */
|
||||
//
|
||||
type DeviceDescriptor struct {
|
||||
bLength uint8 // 18
|
||||
bDescriptorType uint8 // 1 USB_DEVICE_DESCRIPTOR_TYPE
|
||||
bcdUSB uint16 // 0x200
|
||||
bDeviceClass uint8
|
||||
bDeviceSubClass uint8
|
||||
bDeviceProtocol uint8
|
||||
bMaxPacketSize0 uint8 // Packet 0
|
||||
idVendor uint16
|
||||
idProduct uint16
|
||||
bcdDevice uint16 // 0x100
|
||||
iManufacturer uint8
|
||||
iProduct uint8
|
||||
iSerialNumber uint8
|
||||
bNumConfigurations uint8
|
||||
}
|
||||
|
||||
// NewDeviceDescriptor returns a USB DeviceDescriptor.
|
||||
func NewDeviceDescriptor(class, subClass, proto, packetSize0 uint8, vid, pid, version uint16, im, ip, is, configs uint8) DeviceDescriptor {
|
||||
return DeviceDescriptor{deviceDescriptorSize, 1, 0x200, class, subClass, proto, packetSize0, vid, pid, version, im, ip, is, configs}
|
||||
}
|
||||
|
||||
// Bytes returns DeviceDescriptor data
|
||||
func (d DeviceDescriptor) Bytes() []byte {
|
||||
buf := bytes.NewBuffer(make([]byte, 0, deviceDescriptorSize))
|
||||
binary.Write(buf, binary.LittleEndian, d.bLength)
|
||||
binary.Write(buf, binary.LittleEndian, d.bDescriptorType)
|
||||
binary.Write(buf, binary.LittleEndian, d.bcdUSB)
|
||||
binary.Write(buf, binary.LittleEndian, d.bDeviceClass)
|
||||
binary.Write(buf, binary.LittleEndian, d.bDeviceSubClass)
|
||||
binary.Write(buf, binary.LittleEndian, d.bDeviceProtocol)
|
||||
binary.Write(buf, binary.LittleEndian, d.bMaxPacketSize0)
|
||||
binary.Write(buf, binary.LittleEndian, d.idVendor)
|
||||
binary.Write(buf, binary.LittleEndian, d.idProduct)
|
||||
binary.Write(buf, binary.LittleEndian, d.bcdDevice)
|
||||
binary.Write(buf, binary.LittleEndian, d.iManufacturer)
|
||||
binary.Write(buf, binary.LittleEndian, d.iProduct)
|
||||
binary.Write(buf, binary.LittleEndian, d.iSerialNumber)
|
||||
binary.Write(buf, binary.LittleEndian, d.bNumConfigurations)
|
||||
return buf.Bytes()
|
||||
}
|
||||
|
||||
const configDescriptorSize = 9
|
||||
|
||||
// ConfigDescriptor implements the standard USB configuration descriptor.
|
||||
//
|
||||
// Table 9-10. Standard Configuration Descriptor
|
||||
// bLength, bDescriptorType, wTotalLength, bNumInterfaces, bConfigurationValue, iConfiguration
|
||||
// bmAttributes, bMaxPower
|
||||
//
|
||||
type ConfigDescriptor struct {
|
||||
bLength uint8 // 9
|
||||
bDescriptorType uint8 // 2
|
||||
wTotalLength uint16 // total length
|
||||
bNumInterfaces uint8
|
||||
bConfigurationValue uint8
|
||||
iConfiguration uint8
|
||||
bmAttributes uint8
|
||||
bMaxPower uint8
|
||||
}
|
||||
|
||||
// NewConfigDescriptor returns a new USB ConfigDescriptor.
|
||||
func NewConfigDescriptor(totalLength uint16, interfaces uint8) ConfigDescriptor {
|
||||
return ConfigDescriptor{configDescriptorSize, 2, totalLength, interfaces, 1, 0, usb_CONFIG_BUS_POWERED | usb_CONFIG_REMOTE_WAKEUP, 50}
|
||||
}
|
||||
|
||||
// Bytes returns ConfigDescriptor data.
|
||||
func (d ConfigDescriptor) Bytes() []byte {
|
||||
buf := bytes.NewBuffer(make([]byte, 0, configDescriptorSize))
|
||||
binary.Write(buf, binary.LittleEndian, d.bLength)
|
||||
binary.Write(buf, binary.LittleEndian, d.bDescriptorType)
|
||||
binary.Write(buf, binary.LittleEndian, d.wTotalLength)
|
||||
binary.Write(buf, binary.LittleEndian, d.bNumInterfaces)
|
||||
binary.Write(buf, binary.LittleEndian, d.bConfigurationValue)
|
||||
binary.Write(buf, binary.LittleEndian, d.iConfiguration)
|
||||
binary.Write(buf, binary.LittleEndian, d.bmAttributes)
|
||||
binary.Write(buf, binary.LittleEndian, d.bMaxPower)
|
||||
return buf.Bytes()
|
||||
}
|
||||
|
||||
const interfaceDescriptorSize = 9
|
||||
|
||||
// InterfaceDescriptor implements the standard USB interface descriptor.
|
||||
//
|
||||
// Table 9-12. Standard Interface Descriptor
|
||||
// bLength, bDescriptorType, bInterfaceNumber, bAlternateSetting, bNumEndpoints, bInterfaceClass,
|
||||
// bInterfaceSubClass, bInterfaceProtocol, iInterface
|
||||
//
|
||||
type InterfaceDescriptor struct {
|
||||
bLength uint8 // 9
|
||||
bDescriptorType uint8 // 4
|
||||
bInterfaceNumber uint8
|
||||
bAlternateSetting uint8
|
||||
bNumEndpoints uint8
|
||||
bInterfaceClass uint8
|
||||
bInterfaceSubClass uint8
|
||||
bInterfaceProtocol uint8
|
||||
iInterface uint8
|
||||
}
|
||||
|
||||
// NewInterfaceDescriptor returns a new USB InterfaceDescriptor.
|
||||
func NewInterfaceDescriptor(n, numEndpoints, class, subClass, protocol uint8) InterfaceDescriptor {
|
||||
return InterfaceDescriptor{interfaceDescriptorSize, 4, n, 0, numEndpoints, class, subClass, protocol, 0}
|
||||
}
|
||||
|
||||
// Bytes returns InterfaceDescriptor data.
|
||||
func (d InterfaceDescriptor) Bytes() []byte {
|
||||
buf := bytes.NewBuffer(make([]byte, 0, interfaceDescriptorSize))
|
||||
binary.Write(buf, binary.LittleEndian, d.bLength)
|
||||
binary.Write(buf, binary.LittleEndian, d.bDescriptorType)
|
||||
binary.Write(buf, binary.LittleEndian, d.bInterfaceNumber)
|
||||
binary.Write(buf, binary.LittleEndian, d.bAlternateSetting)
|
||||
binary.Write(buf, binary.LittleEndian, d.bNumEndpoints)
|
||||
binary.Write(buf, binary.LittleEndian, d.bInterfaceClass)
|
||||
binary.Write(buf, binary.LittleEndian, d.bInterfaceSubClass)
|
||||
binary.Write(buf, binary.LittleEndian, d.bInterfaceProtocol)
|
||||
binary.Write(buf, binary.LittleEndian, d.iInterface)
|
||||
return buf.Bytes()
|
||||
}
|
||||
|
||||
const endpointDescriptorSize = 7
|
||||
|
||||
// EndpointDescriptor implements the standard USB endpoint descriptor.
|
||||
//
|
||||
// Table 9-13. Standard Endpoint Descriptor
|
||||
// bLength, bDescriptorType, bEndpointAddress, bmAttributes, wMaxPacketSize, bInterval
|
||||
//
|
||||
type EndpointDescriptor struct {
|
||||
bLength uint8 // 7
|
||||
bDescriptorType uint8 // 5
|
||||
bEndpointAddress uint8
|
||||
bmAttributes uint8
|
||||
wMaxPacketSize uint16
|
||||
bInterval uint8
|
||||
}
|
||||
|
||||
// NewEndpointDescriptor returns a new USB EndpointDescriptor.
|
||||
func NewEndpointDescriptor(addr, attr uint8, packetSize uint16, interval uint8) EndpointDescriptor {
|
||||
return EndpointDescriptor{endpointDescriptorSize, 5, addr, attr, packetSize, interval}
|
||||
}
|
||||
|
||||
// Bytes returns EndpointDescriptor data.
|
||||
func (d EndpointDescriptor) Bytes() []byte {
|
||||
buf := bytes.NewBuffer(make([]byte, 0, endpointDescriptorSize))
|
||||
binary.Write(buf, binary.LittleEndian, d.bLength)
|
||||
binary.Write(buf, binary.LittleEndian, d.bDescriptorType)
|
||||
binary.Write(buf, binary.LittleEndian, d.bEndpointAddress)
|
||||
binary.Write(buf, binary.LittleEndian, d.bmAttributes)
|
||||
binary.Write(buf, binary.LittleEndian, d.wMaxPacketSize)
|
||||
binary.Write(buf, binary.LittleEndian, d.bInterval)
|
||||
return buf.Bytes()
|
||||
}
|
||||
|
||||
const iadDescriptorSize = 8
|
||||
|
||||
// IADDescriptor is an Interface Association Descriptor, which is used
|
||||
// to bind 2 interfaces together in CDC composite device.
|
||||
//
|
||||
// Standard Interface Association Descriptor:
|
||||
// bLength, bDescriptorType, bFirstInterface, bInterfaceCount, bFunctionClass, bFunctionSubClass,
|
||||
// bFunctionProtocol, iFunction
|
||||
//
|
||||
type IADDescriptor struct {
|
||||
bLength uint8 // 8
|
||||
bDescriptorType uint8 // 11
|
||||
bFirstInterface uint8
|
||||
bInterfaceCount uint8
|
||||
bFunctionClass uint8
|
||||
bFunctionSubClass uint8
|
||||
bFunctionProtocol uint8
|
||||
iFunction uint8
|
||||
}
|
||||
|
||||
// NewIADDescriptor returns a new USB IADDescriptor.
|
||||
func NewIADDescriptor(firstInterface, count, class, subClass, protocol uint8) IADDescriptor {
|
||||
return IADDescriptor{iadDescriptorSize, 11, firstInterface, count, class, subClass, protocol, 0}
|
||||
}
|
||||
|
||||
// Bytes returns IADDescriptor data.
|
||||
func (d IADDescriptor) Bytes() []byte {
|
||||
buf := bytes.NewBuffer(make([]byte, 0, iadDescriptorSize))
|
||||
binary.Write(buf, binary.LittleEndian, d.bLength)
|
||||
binary.Write(buf, binary.LittleEndian, d.bDescriptorType)
|
||||
binary.Write(buf, binary.LittleEndian, d.bFirstInterface)
|
||||
binary.Write(buf, binary.LittleEndian, d.bInterfaceCount)
|
||||
binary.Write(buf, binary.LittleEndian, d.bFunctionClass)
|
||||
binary.Write(buf, binary.LittleEndian, d.bFunctionSubClass)
|
||||
binary.Write(buf, binary.LittleEndian, d.bFunctionProtocol)
|
||||
binary.Write(buf, binary.LittleEndian, d.iFunction)
|
||||
return buf.Bytes()
|
||||
}
|
||||
|
||||
const cdcCSInterfaceDescriptorSize = 5
|
||||
|
||||
// CDCCSInterfaceDescriptor is a CDC CS interface descriptor.
|
||||
type CDCCSInterfaceDescriptor struct {
|
||||
len uint8 // 5
|
||||
dtype uint8 // 0x24
|
||||
subtype uint8
|
||||
d0 uint8
|
||||
d1 uint8
|
||||
}
|
||||
|
||||
// NewCDCCSInterfaceDescriptor returns a new USB CDCCSInterfaceDescriptor.
|
||||
func NewCDCCSInterfaceDescriptor(subtype, d0, d1 uint8) CDCCSInterfaceDescriptor {
|
||||
return CDCCSInterfaceDescriptor{cdcCSInterfaceDescriptorSize, 0x24, subtype, d0, d1}
|
||||
}
|
||||
|
||||
// Bytes returns CDCCSInterfaceDescriptor data.
|
||||
func (d CDCCSInterfaceDescriptor) Bytes() []byte {
|
||||
buf := bytes.NewBuffer(make([]byte, 0, cdcCSInterfaceDescriptorSize))
|
||||
binary.Write(buf, binary.LittleEndian, d.len)
|
||||
binary.Write(buf, binary.LittleEndian, d.dtype)
|
||||
binary.Write(buf, binary.LittleEndian, d.subtype)
|
||||
binary.Write(buf, binary.LittleEndian, d.d0)
|
||||
binary.Write(buf, binary.LittleEndian, d.d1)
|
||||
return buf.Bytes()
|
||||
}
|
||||
|
||||
const cmFunctionalDescriptorSize = 5
|
||||
|
||||
// CMFunctionalDescriptor is the functional descriptor general format.
|
||||
type CMFunctionalDescriptor struct {
|
||||
bFunctionLength uint8
|
||||
bDescriptorType uint8 // 0x24
|
||||
bDescriptorSubtype uint8 // 1
|
||||
bmCapabilities uint8
|
||||
bDataInterface uint8
|
||||
}
|
||||
|
||||
// NewCMFunctionalDescriptor returns a new USB CMFunctionalDescriptor.
|
||||
func NewCMFunctionalDescriptor(subtype, d0, d1 uint8) CMFunctionalDescriptor {
|
||||
return CMFunctionalDescriptor{5, 0x24, subtype, d0, d1}
|
||||
}
|
||||
|
||||
// Bytes returns the CMFunctionalDescriptor data.
|
||||
func (d CMFunctionalDescriptor) Bytes() []byte {
|
||||
buf := bytes.NewBuffer(make([]byte, 0, cmFunctionalDescriptorSize))
|
||||
binary.Write(buf, binary.LittleEndian, d.bFunctionLength)
|
||||
binary.Write(buf, binary.LittleEndian, d.bDescriptorType)
|
||||
binary.Write(buf, binary.LittleEndian, d.bDescriptorSubtype)
|
||||
binary.Write(buf, binary.LittleEndian, d.bmCapabilities)
|
||||
binary.Write(buf, binary.LittleEndian, d.bDataInterface)
|
||||
return buf.Bytes()
|
||||
}
|
||||
|
||||
const acmFunctionalDescriptorSize = 4
|
||||
|
||||
// ACMFunctionalDescriptor is a Abstract Control Model (ACM) USB descriptor.
|
||||
type ACMFunctionalDescriptor struct {
|
||||
len uint8
|
||||
dtype uint8 // 0x24
|
||||
subtype uint8 // 1
|
||||
bmCapabilities uint8
|
||||
}
|
||||
|
||||
// NewACMFunctionalDescriptor returns a new USB ACMFunctionalDescriptor.
|
||||
func NewACMFunctionalDescriptor(subtype, d0 uint8) ACMFunctionalDescriptor {
|
||||
return ACMFunctionalDescriptor{4, 0x24, subtype, d0}
|
||||
}
|
||||
|
||||
// Bytes returns the ACMFunctionalDescriptor data.
|
||||
func (d ACMFunctionalDescriptor) Bytes() []byte {
|
||||
buf := bytes.NewBuffer(make([]byte, 0, acmFunctionalDescriptorSize))
|
||||
binary.Write(buf, binary.LittleEndian, d.len)
|
||||
binary.Write(buf, binary.LittleEndian, d.dtype)
|
||||
binary.Write(buf, binary.LittleEndian, d.subtype)
|
||||
binary.Write(buf, binary.LittleEndian, d.bmCapabilities)
|
||||
return buf.Bytes()
|
||||
}
|
||||
|
||||
// CDCDescriptor is the Communication Device Class (CDC) descriptor.
|
||||
type CDCDescriptor struct {
|
||||
// IAD
|
||||
iad IADDescriptor // Only needed on compound device
|
||||
|
||||
// Control
|
||||
cif InterfaceDescriptor
|
||||
header CDCCSInterfaceDescriptor
|
||||
|
||||
// CDC control
|
||||
controlManagement ACMFunctionalDescriptor // ACM
|
||||
functionalDescriptor CDCCSInterfaceDescriptor // CDC_UNION
|
||||
callManagement CMFunctionalDescriptor // Call Management
|
||||
cifin EndpointDescriptor
|
||||
|
||||
// CDC Data
|
||||
dif InterfaceDescriptor
|
||||
in EndpointDescriptor
|
||||
out EndpointDescriptor
|
||||
}
|
||||
|
||||
func NewCDCDescriptor(i IADDescriptor, c InterfaceDescriptor,
|
||||
h CDCCSInterfaceDescriptor,
|
||||
cm ACMFunctionalDescriptor,
|
||||
fd CDCCSInterfaceDescriptor,
|
||||
callm CMFunctionalDescriptor,
|
||||
ci EndpointDescriptor,
|
||||
di InterfaceDescriptor,
|
||||
inp EndpointDescriptor,
|
||||
outp EndpointDescriptor) CDCDescriptor {
|
||||
return CDCDescriptor{iad: i,
|
||||
cif: c,
|
||||
header: h,
|
||||
controlManagement: cm,
|
||||
functionalDescriptor: fd,
|
||||
callManagement: callm,
|
||||
cifin: ci,
|
||||
dif: di,
|
||||
in: inp,
|
||||
out: outp}
|
||||
}
|
||||
|
||||
const cdcSize = iadDescriptorSize +
|
||||
interfaceDescriptorSize +
|
||||
cdcCSInterfaceDescriptorSize +
|
||||
acmFunctionalDescriptorSize +
|
||||
cdcCSInterfaceDescriptorSize +
|
||||
cmFunctionalDescriptorSize +
|
||||
endpointDescriptorSize +
|
||||
interfaceDescriptorSize +
|
||||
endpointDescriptorSize +
|
||||
endpointDescriptorSize
|
||||
|
||||
// Bytes returns CDCDescriptor data.
|
||||
func (d CDCDescriptor) Bytes() []byte {
|
||||
buf := bytes.NewBuffer(make([]byte, 0, cdcSize))
|
||||
buf.Write(d.iad.Bytes())
|
||||
buf.Write(d.cif.Bytes())
|
||||
buf.Write(d.header.Bytes())
|
||||
buf.Write(d.controlManagement.Bytes())
|
||||
buf.Write(d.functionalDescriptor.Bytes())
|
||||
buf.Write(d.callManagement.Bytes())
|
||||
buf.Write(d.cifin.Bytes())
|
||||
buf.Write(d.dif.Bytes())
|
||||
buf.Write(d.in.Bytes())
|
||||
buf.Write(d.out.Bytes())
|
||||
return buf.Bytes()
|
||||
}
|
||||
|
||||
// MSCDescriptor is not used yet.
|
||||
type MSCDescriptor struct {
|
||||
msc InterfaceDescriptor
|
||||
in EndpointDescriptor
|
||||
out EndpointDescriptor
|
||||
}
|
||||
|
||||
type cdcLineInfo struct {
|
||||
dwDTERate uint32
|
||||
bCharFormat uint8
|
||||
bParityType uint8
|
||||
bDataBits uint8
|
||||
lineState uint8
|
||||
}
|
||||
|
||||
var (
|
||||
// TODO: allow setting these
|
||||
usb_STRING_LANGUAGE = [2]uint16{(3 << 8) | (2 + 2), 0x0409} // English
|
||||
usb_STRING_PRODUCT = "Arduino Zero"
|
||||
usb_STRING_MANUFACTURER = "Arduino"
|
||||
|
||||
usb_VID uint16 = 0x2341
|
||||
usb_PID uint16 = 0x004d
|
||||
)
|
||||
|
||||
const (
|
||||
usb_IMANUFACTURER = 1
|
||||
usb_IPRODUCT = 2
|
||||
usb_ISERIAL = 3
|
||||
|
||||
usb_ENDPOINT_TYPE_CONTROL = 0x00
|
||||
usb_ENDPOINT_TYPE_ISOCHRONOUS = 0x01
|
||||
usb_ENDPOINT_TYPE_BULK = 0x02
|
||||
usb_ENDPOINT_TYPE_INTERRUPT = 0x03
|
||||
|
||||
usb_DEVICE_DESCRIPTOR_TYPE = 1
|
||||
usb_CONFIGURATION_DESCRIPTOR_TYPE = 2
|
||||
usb_STRING_DESCRIPTOR_TYPE = 3
|
||||
usb_INTERFACE_DESCRIPTOR_TYPE = 4
|
||||
usb_ENDPOINT_DESCRIPTOR_TYPE = 5
|
||||
usb_DEVICE_QUALIFIER = 6
|
||||
usb_OTHER_SPEED_CONFIGURATION = 7
|
||||
|
||||
usbEndpointOut = 0x00
|
||||
usbEndpointIn = 0x80
|
||||
|
||||
usbEndpointPacketSize = 64 // 64 for Full Speed, EPT size max is 1024
|
||||
usb_EPT_NUM = 7
|
||||
|
||||
// standard requests
|
||||
usb_GET_STATUS = 0
|
||||
usb_CLEAR_FEATURE = 1
|
||||
usb_SET_FEATURE = 3
|
||||
usb_SET_ADDRESS = 5
|
||||
usb_GET_DESCRIPTOR = 6
|
||||
usb_SET_DESCRIPTOR = 7
|
||||
usb_GET_CONFIGURATION = 8
|
||||
usb_SET_CONFIGURATION = 9
|
||||
usb_GET_INTERFACE = 10
|
||||
usb_SET_INTERFACE = 11
|
||||
|
||||
usb_DEVICE_CLASS_COMMUNICATIONS = 0x02
|
||||
usb_DEVICE_CLASS_HUMAN_INTERFACE = 0x03
|
||||
usb_DEVICE_CLASS_STORAGE = 0x08
|
||||
usb_DEVICE_CLASS_VENDOR_SPECIFIC = 0xFF
|
||||
|
||||
usb_CONFIG_POWERED_MASK = 0x40
|
||||
usb_CONFIG_BUS_POWERED = 0x80
|
||||
usb_CONFIG_SELF_POWERED = 0xC0
|
||||
usb_CONFIG_REMOTE_WAKEUP = 0x20
|
||||
|
||||
// CDC
|
||||
usb_CDC_ACM_INTERFACE = 0 // CDC ACM
|
||||
usb_CDC_DATA_INTERFACE = 1 // CDC Data
|
||||
usb_CDC_FIRST_ENDPOINT = 1
|
||||
usb_CDC_ENDPOINT_ACM = 1
|
||||
usb_CDC_ENDPOINT_OUT = 2
|
||||
usb_CDC_ENDPOINT_IN = 3
|
||||
|
||||
// bmRequestType
|
||||
usb_REQUEST_HOSTTODEVICE = 0x00
|
||||
usb_REQUEST_DEVICETOHOST = 0x80
|
||||
usb_REQUEST_DIRECTION = 0x80
|
||||
|
||||
usb_REQUEST_STANDARD = 0x00
|
||||
usb_REQUEST_CLASS = 0x20
|
||||
usb_REQUEST_VENDOR = 0x40
|
||||
usb_REQUEST_TYPE = 0x60
|
||||
|
||||
usb_REQUEST_DEVICE = 0x00
|
||||
usb_REQUEST_INTERFACE = 0x01
|
||||
usb_REQUEST_ENDPOINT = 0x02
|
||||
usb_REQUEST_OTHER = 0x03
|
||||
usb_REQUEST_RECIPIENT = 0x1F
|
||||
|
||||
usb_REQUEST_DEVICETOHOST_CLASS_INTERFACE = (usb_REQUEST_DEVICETOHOST | usb_REQUEST_CLASS | usb_REQUEST_INTERFACE)
|
||||
usb_REQUEST_HOSTTODEVICE_CLASS_INTERFACE = (usb_REQUEST_HOSTTODEVICE | usb_REQUEST_CLASS | usb_REQUEST_INTERFACE)
|
||||
usb_REQUEST_DEVICETOHOST_STANDARD_INTERFACE = (usb_REQUEST_DEVICETOHOST | usb_REQUEST_STANDARD | usb_REQUEST_INTERFACE)
|
||||
|
||||
// CDC Class requests
|
||||
usb_CDC_SET_LINE_CODING = 0x20
|
||||
usb_CDC_GET_LINE_CODING = 0x21
|
||||
usb_CDC_SET_CONTROL_LINE_STATE = 0x22
|
||||
usb_CDC_SEND_BREAK = 0x23
|
||||
|
||||
usb_CDC_V1_10 = 0x0110
|
||||
usb_CDC_COMMUNICATION_INTERFACE_CLASS = 0x02
|
||||
|
||||
usb_CDC_CALL_MANAGEMENT = 0x01
|
||||
usb_CDC_ABSTRACT_CONTROL_MODEL = 0x02
|
||||
usb_CDC_HEADER = 0x00
|
||||
usb_CDC_ABSTRACT_CONTROL_MANAGEMENT = 0x02
|
||||
usb_CDC_UNION = 0x06
|
||||
usb_CDC_CS_INTERFACE = 0x24
|
||||
usb_CDC_CS_ENDPOINT = 0x25
|
||||
usb_CDC_DATA_INTERFACE_CLASS = 0x0A
|
||||
)
|
||||
|
||||
// usbDeviceDescBank is the USB device endpoint descriptor.
|
||||
// typedef struct {
|
||||
// __IO USB_DEVICE_ADDR_Type ADDR; /**< \brief Offset: 0x000 (R/W 32) DEVICE_DESC_BANK Endpoint Bank, Adress of Data Buffer */
|
||||
// __IO USB_DEVICE_PCKSIZE_Type PCKSIZE; /**< \brief Offset: 0x004 (R/W 32) DEVICE_DESC_BANK Endpoint Bank, Packet Size */
|
||||
// __IO USB_DEVICE_EXTREG_Type EXTREG; /**< \brief Offset: 0x008 (R/W 16) DEVICE_DESC_BANK Endpoint Bank, Extended */
|
||||
// __IO USB_DEVICE_STATUS_BK_Type STATUS_BK; /**< \brief Offset: 0x00A (R/W 8) DEVICE_DESC_BANK Enpoint Bank, Status of Bank */
|
||||
// RoReg8 Reserved1[0x5];
|
||||
// } UsbDeviceDescBank;
|
||||
type usbDeviceDescBank struct {
|
||||
ADDR sam.RegValue
|
||||
PCKSIZE sam.RegValue
|
||||
EXTREG sam.RegValue16
|
||||
STATUS_BK sam.RegValue8
|
||||
_reserved [5]sam.RegValue8
|
||||
}
|
||||
|
||||
type usbDeviceDescriptor struct {
|
||||
DeviceDescBank [2]usbDeviceDescBank
|
||||
}
|
||||
|
||||
// typedef struct {
|
||||
// union {
|
||||
// uint8_t bmRequestType;
|
||||
// struct {
|
||||
// uint8_t direction : 5;
|
||||
// uint8_t type : 2;
|
||||
// uint8_t transferDirection : 1;
|
||||
// };
|
||||
// };
|
||||
// uint8_t bRequest;
|
||||
// uint8_t wValueL;
|
||||
// uint8_t wValueH;
|
||||
// uint16_t wIndex;
|
||||
// uint16_t wLength;
|
||||
// } USBSetup;
|
||||
type usbSetup struct {
|
||||
bmRequestType uint8
|
||||
bRequest uint8
|
||||
wValueL uint8
|
||||
wValueH uint8
|
||||
wIndex uint16
|
||||
wLength uint16
|
||||
}
|
||||
|
||||
func newUSBSetup(data []byte) usbSetup {
|
||||
buf := bytes.NewBuffer(data)
|
||||
u := usbSetup{}
|
||||
binary.Read(buf, binary.LittleEndian, &(u.bmRequestType))
|
||||
binary.Read(buf, binary.LittleEndian, &(u.bRequest))
|
||||
binary.Read(buf, binary.LittleEndian, &(u.wValueL))
|
||||
binary.Read(buf, binary.LittleEndian, &(u.wValueH))
|
||||
binary.Read(buf, binary.LittleEndian, &(u.wIndex))
|
||||
binary.Read(buf, binary.LittleEndian, &(u.wLength))
|
||||
return u
|
||||
}
|
||||
|
||||
// USBCDC is the serial interface that works over the USB port.
|
||||
// To implement the USBCDC interface for a board, you must declare a concrete type as follows:
|
||||
//
|
||||
// type USBCDC struct {
|
||||
// Buffer *RingBuffer
|
||||
// }
|
||||
//
|
||||
// You can also add additional members to this struct depending on your implementation,
|
||||
// but the *RingBuffer is required.
|
||||
// When you are declaring the USBCDC for your board, make sure that you also declare the
|
||||
// RingBuffer using the NewRingBuffer() function:
|
||||
//
|
||||
// USBCDC{Buffer: NewRingBuffer()}
|
||||
//
|
||||
|
||||
// Read from the RX buffer.
|
||||
func (usbcdc USBCDC) Read(data []byte) (n int, err error) {
|
||||
// check if RX buffer is empty
|
||||
size := usbcdc.Buffered()
|
||||
if size == 0 {
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
// Make sure we do not read more from buffer than the data slice can hold.
|
||||
if len(data) < size {
|
||||
size = len(data)
|
||||
}
|
||||
|
||||
// only read number of bytes used from buffer
|
||||
for i := 0; i < size; i++ {
|
||||
v, _ := usbcdc.ReadByte()
|
||||
data[i] = v
|
||||
}
|
||||
|
||||
return size, nil
|
||||
}
|
||||
|
||||
// Write data to the USBCDC.
|
||||
func (usbcdc USBCDC) Write(data []byte) (n int, err error) {
|
||||
for _, v := range data {
|
||||
usbcdc.WriteByte(v)
|
||||
}
|
||||
return len(data), nil
|
||||
}
|
||||
|
||||
// ReadByte reads a single byte from the RX buffer.
|
||||
// If there is no data in the buffer, returns an error.
|
||||
func (usbcdc USBCDC) ReadByte() (byte, error) {
|
||||
// check if RX buffer is empty
|
||||
buf, ok := usbcdc.Buffer.Get()
|
||||
if !ok {
|
||||
return 0, errors.New("Buffer empty")
|
||||
}
|
||||
return buf, nil
|
||||
}
|
||||
|
||||
// Buffered returns the number of bytes currently stored in the RX buffer.
|
||||
func (usbcdc USBCDC) Buffered() int {
|
||||
return int(usbcdc.Buffer.Used())
|
||||
}
|
||||
|
||||
// Receive handles adding data to the UART's data buffer.
|
||||
// Usually called by the IRQ handler for a machine.
|
||||
func (usbcdc USBCDC) Receive(data byte) {
|
||||
usbcdc.Buffer.Put(data)
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
// Package os implements a subset of the Go "os" package. See
|
||||
// https://godoc.org/os for details.
|
||||
//
|
||||
// Note that the current implementation is blocking. This limitation should be
|
||||
// removed in a future version.
|
||||
package os
|
||||
|
||||
import (
|
||||
"errors"
|
||||
)
|
||||
|
||||
// Portable analogs of some common system call errors.
|
||||
var (
|
||||
ErrUnsupported = errors.New("operation not supported")
|
||||
)
|
||||
|
||||
// Stdin, Stdout, and Stderr are open Files pointing to the standard input,
|
||||
// standard output, and standard error file descriptors.
|
||||
var (
|
||||
Stdin = &File{0, "/dev/stdin"}
|
||||
Stdout = &File{1, "/dev/stdout"}
|
||||
Stderr = &File{2, "/dev/stderr"}
|
||||
)
|
||||
|
||||
// File represents an open file descriptor.
|
||||
type File struct {
|
||||
fd uintptr
|
||||
name string
|
||||
}
|
||||
|
||||
// NewFile returns a new File with the given file descriptor and name.
|
||||
func NewFile(fd uintptr, name string) *File {
|
||||
return &File{fd, name}
|
||||
}
|
||||
|
||||
// Fd returns the integer Unix file descriptor referencing the open file. The
|
||||
// file descriptor is valid only until f.Close is called.
|
||||
func (f *File) Fd() uintptr {
|
||||
return f.fd
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
// +build avr cortexm wasm
|
||||
|
||||
package os
|
||||
|
||||
import (
|
||||
_ "unsafe"
|
||||
)
|
||||
|
||||
// Read is unsupported on this system.
|
||||
func (f *File) Read(b []byte) (n int, err error) {
|
||||
return 0, ErrUnsupported
|
||||
}
|
||||
|
||||
// Write writes len(b) bytes to the output. It returns the number of bytes
|
||||
// written or an error if this file is not stdout or stderr.
|
||||
func (f *File) Write(b []byte) (n int, err error) {
|
||||
switch f.fd {
|
||||
case Stdout.fd, Stderr.fd:
|
||||
for _, c := range b {
|
||||
putchar(c)
|
||||
}
|
||||
return len(b), nil
|
||||
default:
|
||||
return 0, ErrUnsupported
|
||||
}
|
||||
}
|
||||
|
||||
// Close is unsupported on this system.
|
||||
func (f *File) Close() error {
|
||||
return ErrUnsupported
|
||||
}
|
||||
|
||||
//go:linkname putchar runtime.putchar
|
||||
func putchar(c byte)
|
||||
@@ -0,0 +1,24 @@
|
||||
// +build darwin linux,!avr,!cortexm
|
||||
|
||||
package os
|
||||
|
||||
import (
|
||||
"syscall"
|
||||
)
|
||||
|
||||
// Read reads up to len(b) bytes from the File. It returns the number of bytes
|
||||
// read and any error encountered. At end of file, Read returns 0, io.EOF.
|
||||
func (f *File) Read(b []byte) (n int, err error) {
|
||||
return syscall.Read(int(f.fd), b)
|
||||
}
|
||||
|
||||
// Write writes len(b) bytes to the File. It returns the number of bytes written
|
||||
// and an error, if any. Write returns a non-nil error when n != len(b).
|
||||
func (f *File) Write(b []byte) (n int, err error) {
|
||||
return syscall.Write(int(f.fd), b)
|
||||
}
|
||||
|
||||
// Close closes the File, rendering it unusable for I/O.
|
||||
func (f *File) Close() error {
|
||||
return syscall.Close(int(f.fd))
|
||||
}
|
||||
+137
-11
@@ -1,9 +1,29 @@
|
||||
package reflect
|
||||
|
||||
// A Kind is the number that the compiler uses for this type.
|
||||
import (
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
// The compiler uses a compact encoding to store type information. Unlike the
|
||||
// main Go compiler, most of the types are stored directly in the type code.
|
||||
//
|
||||
// Not used directly. These types are all replaced with the number the compiler
|
||||
// uses internally for the type.
|
||||
// Type code bit allocation:
|
||||
// xxxxx0: basic types, where xxxxx is the basic type number (never 0).
|
||||
// The higher bits indicate the named type, if any.
|
||||
// nxxx1: complex types, where n indicates whether this is a named type (named
|
||||
// if set) and xxx contains the type kind number:
|
||||
// 0 (0001): Chan
|
||||
// 1 (0011): Interface
|
||||
// 2 (0101): Ptr
|
||||
// 3 (0111): Slice
|
||||
// 4 (1001): Array
|
||||
// 5 (1011): Func
|
||||
// 6 (1101): Map
|
||||
// 7 (1111): Struct
|
||||
// The higher bits are either the contents of the type depending on the
|
||||
// type (if n is clear) or indicate the number of the named type (if n
|
||||
// is set).
|
||||
|
||||
type Kind uintptr
|
||||
|
||||
// Copied from reflect/type.go
|
||||
@@ -26,18 +46,82 @@ const (
|
||||
Float64
|
||||
Complex64
|
||||
Complex128
|
||||
Array
|
||||
String
|
||||
UnsafePointer
|
||||
Chan
|
||||
Func
|
||||
Interface
|
||||
Map
|
||||
Ptr
|
||||
Slice
|
||||
String
|
||||
Array
|
||||
Func
|
||||
Map
|
||||
Struct
|
||||
UnsafePointer
|
||||
)
|
||||
|
||||
func (k Kind) String() string {
|
||||
switch k {
|
||||
case Bool:
|
||||
return "bool"
|
||||
case Int:
|
||||
return "int"
|
||||
case Int8:
|
||||
return "int8"
|
||||
case Int16:
|
||||
return "int16"
|
||||
case Int32:
|
||||
return "int32"
|
||||
case Int64:
|
||||
return "int64"
|
||||
case Uint:
|
||||
return "uint"
|
||||
case Uint8:
|
||||
return "uint8"
|
||||
case Uint16:
|
||||
return "uint16"
|
||||
case Uint32:
|
||||
return "uint32"
|
||||
case Uint64:
|
||||
return "uint64"
|
||||
case Uintptr:
|
||||
return "uintptr"
|
||||
case Float32:
|
||||
return "float32"
|
||||
case Float64:
|
||||
return "float64"
|
||||
case Complex64:
|
||||
return "complex64"
|
||||
case Complex128:
|
||||
return "complex128"
|
||||
case String:
|
||||
return "string"
|
||||
case UnsafePointer:
|
||||
return "unsafe.Pointer"
|
||||
case Chan:
|
||||
return "chan"
|
||||
case Interface:
|
||||
return "interface"
|
||||
case Ptr:
|
||||
return "ptr"
|
||||
case Slice:
|
||||
return "slice"
|
||||
case Array:
|
||||
return "array"
|
||||
case Func:
|
||||
return "func"
|
||||
case Map:
|
||||
return "map"
|
||||
case Struct:
|
||||
return "struct"
|
||||
default:
|
||||
return "invalid"
|
||||
}
|
||||
}
|
||||
|
||||
// basicType returns a new Type for this kind if Kind is a basic type.
|
||||
func (k Kind) basicType() Type {
|
||||
return Type(k << 1)
|
||||
}
|
||||
|
||||
// The typecode as used in an interface{}.
|
||||
type Type uintptr
|
||||
|
||||
@@ -50,11 +134,24 @@ func (t Type) String() string {
|
||||
}
|
||||
|
||||
func (t Type) Kind() Kind {
|
||||
return Invalid // TODO
|
||||
if t%2 == 0 {
|
||||
// basic type
|
||||
return Kind((t >> 1) % 32)
|
||||
} else {
|
||||
return Kind(t>>1)%8 + 19
|
||||
}
|
||||
}
|
||||
|
||||
func (t Type) Elem() Type {
|
||||
panic("unimplemented: (reflect.Type).Elem()")
|
||||
switch t.Kind() {
|
||||
case Chan, Ptr, Slice:
|
||||
if (t>>4)%2 != 0 {
|
||||
panic("unimplemented: (reflect.Type).Elem() for named types")
|
||||
}
|
||||
return t >> 5
|
||||
default: // not implemented: Array, Map
|
||||
panic("unimplemented: (reflect.Type).Elem()")
|
||||
}
|
||||
}
|
||||
|
||||
func (t Type) Field(i int) StructField {
|
||||
@@ -74,7 +171,36 @@ func (t Type) NumField() int {
|
||||
}
|
||||
|
||||
func (t Type) Size() uintptr {
|
||||
panic("unimplemented: (reflect.Type).Size()")
|
||||
switch t.Kind() {
|
||||
case Bool, Int8, Uint8:
|
||||
return 1
|
||||
case Int16, Uint16:
|
||||
return 2
|
||||
case Int32, Uint32:
|
||||
return 4
|
||||
case Int64, Uint64:
|
||||
return 8
|
||||
case Int, Uint:
|
||||
return unsafe.Sizeof(int(0))
|
||||
case Uintptr:
|
||||
return unsafe.Sizeof(uintptr(0))
|
||||
case Float32:
|
||||
return 4
|
||||
case Float64:
|
||||
return 8
|
||||
case Complex64:
|
||||
return 8
|
||||
case Complex128:
|
||||
return 16
|
||||
case String:
|
||||
return unsafe.Sizeof(StringHeader{})
|
||||
case UnsafePointer, Chan, Map, Ptr:
|
||||
return unsafe.Sizeof(uintptr(0))
|
||||
case Slice:
|
||||
return unsafe.Sizeof(SliceHeader{})
|
||||
default:
|
||||
panic("unimplemented: size of type")
|
||||
}
|
||||
}
|
||||
|
||||
type StructField struct {
|
||||
|
||||
+415
-31
@@ -1,42 +1,94 @@
|
||||
package reflect
|
||||
|
||||
import (
|
||||
_ "unsafe" // for go:linkname
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
// This is the same thing as an interface{}.
|
||||
type Value struct {
|
||||
typecode Type
|
||||
value *uint8
|
||||
value unsafe.Pointer
|
||||
indirect bool
|
||||
}
|
||||
|
||||
func Indirect(v Value) Value {
|
||||
return v
|
||||
if v.Kind() != Ptr {
|
||||
return v
|
||||
}
|
||||
return v.Elem()
|
||||
}
|
||||
|
||||
func ValueOf(i interface{}) Value
|
||||
|
||||
//go:linkname _ValueOf reflect.ValueOf
|
||||
func _ValueOf(i Value) Value {
|
||||
return i
|
||||
func ValueOf(i interface{}) Value {
|
||||
v := (*interfaceHeader)(unsafe.Pointer(&i))
|
||||
return Value{
|
||||
typecode: v.typecode,
|
||||
value: v.value,
|
||||
}
|
||||
}
|
||||
|
||||
func (v Value) Interface() interface{}
|
||||
func (v Value) Interface() interface{} {
|
||||
i := interfaceHeader{
|
||||
typecode: v.typecode,
|
||||
value: v.value,
|
||||
}
|
||||
if v.indirect && v.Type().Size() <= unsafe.Sizeof(uintptr(0)) {
|
||||
// Value was indirect but must be put back directly in the interface
|
||||
// value.
|
||||
var value uintptr
|
||||
for j := v.Type().Size(); j != 0; j-- {
|
||||
value = (value << 8) | uintptr(*(*uint8)(unsafe.Pointer(uintptr(v.value) + j - 1)))
|
||||
}
|
||||
i.value = unsafe.Pointer(value)
|
||||
}
|
||||
return *(*interface{})(unsafe.Pointer(&i))
|
||||
}
|
||||
|
||||
func (v Value) Type() Type {
|
||||
return v.typecode
|
||||
}
|
||||
|
||||
func (v Value) Kind() Kind {
|
||||
return Invalid // TODO
|
||||
return v.Type().Kind()
|
||||
}
|
||||
|
||||
func (v Value) IsNil() bool {
|
||||
panic("unimplemented: (reflect.Value).IsNil()")
|
||||
switch v.Kind() {
|
||||
case Chan, Map, Ptr:
|
||||
return v.value == nil
|
||||
case Func:
|
||||
if v.value == nil {
|
||||
return true
|
||||
}
|
||||
fn := (*funcHeader)(v.value)
|
||||
return fn.Code == nil
|
||||
case Slice:
|
||||
if v.value == nil {
|
||||
return true
|
||||
}
|
||||
slice := (*SliceHeader)(v.value)
|
||||
return slice.Data == 0
|
||||
case Interface:
|
||||
if v.value == nil {
|
||||
return true
|
||||
}
|
||||
itf := (*interfaceHeader)(v.value)
|
||||
return itf.value == nil
|
||||
default:
|
||||
panic(&ValueError{"IsNil"})
|
||||
}
|
||||
}
|
||||
|
||||
func (v Value) Pointer() uintptr {
|
||||
panic("unimplemented: (reflect.Value).Pointer()")
|
||||
switch v.Kind() {
|
||||
case Chan, Map, Ptr, UnsafePointer:
|
||||
return uintptr(v.value)
|
||||
case Slice:
|
||||
slice := (*SliceHeader)(v.value)
|
||||
return slice.Data
|
||||
case Func:
|
||||
panic("unimplemented: (reflect.Value).Pointer()")
|
||||
default: // not implemented: Func
|
||||
panic(&ValueError{"Pointer"})
|
||||
}
|
||||
}
|
||||
|
||||
func (v Value) IsValid() bool {
|
||||
@@ -44,7 +96,8 @@ func (v Value) IsValid() bool {
|
||||
}
|
||||
|
||||
func (v Value) CanInterface() bool {
|
||||
panic("unimplemented: (reflect.Value).CanInterface()")
|
||||
// No Value types of private data can be constructed at the moment.
|
||||
return true
|
||||
}
|
||||
|
||||
func (v Value) CanAddr() bool {
|
||||
@@ -56,31 +109,160 @@ func (v Value) Addr() Value {
|
||||
}
|
||||
|
||||
func (v Value) CanSet() bool {
|
||||
panic("unimplemented: (reflect.Value).CanSet()")
|
||||
return v.indirect
|
||||
}
|
||||
|
||||
func (v Value) Bool() bool {
|
||||
panic("unimplemented: (reflect.Value).Bool()")
|
||||
switch v.Kind() {
|
||||
case Bool:
|
||||
if v.indirect {
|
||||
return *((*bool)(v.value))
|
||||
} else {
|
||||
return uintptr(v.value) != 0
|
||||
}
|
||||
default:
|
||||
panic(&ValueError{"Bool"})
|
||||
}
|
||||
}
|
||||
|
||||
func (v Value) Int() int64 {
|
||||
panic("unimplemented: (reflect.Value).Int()")
|
||||
switch v.Kind() {
|
||||
case Int:
|
||||
if v.indirect || unsafe.Sizeof(int(0)) > unsafe.Sizeof(uintptr(0)) {
|
||||
return int64(*(*int)(v.value))
|
||||
} else {
|
||||
return int64(int(uintptr(v.value)))
|
||||
}
|
||||
case Int8:
|
||||
if v.indirect {
|
||||
return int64(*(*int8)(v.value))
|
||||
} else {
|
||||
return int64(int8(uintptr(v.value)))
|
||||
}
|
||||
case Int16:
|
||||
if v.indirect {
|
||||
return int64(*(*int16)(v.value))
|
||||
} else {
|
||||
return int64(int16(uintptr(v.value)))
|
||||
}
|
||||
case Int32:
|
||||
if v.indirect || unsafe.Sizeof(int32(0)) > unsafe.Sizeof(uintptr(0)) {
|
||||
return int64(*(*int32)(v.value))
|
||||
} else {
|
||||
return int64(int32(uintptr(v.value)))
|
||||
}
|
||||
case Int64:
|
||||
if v.indirect || unsafe.Sizeof(int64(0)) > unsafe.Sizeof(uintptr(0)) {
|
||||
return int64(*(*int64)(v.value))
|
||||
} else {
|
||||
return int64(int64(uintptr(v.value)))
|
||||
}
|
||||
default:
|
||||
panic(&ValueError{"Int"})
|
||||
}
|
||||
}
|
||||
|
||||
func (v Value) Uint() uint64 {
|
||||
panic("unimplemented: (reflect.Value).Uint()")
|
||||
switch v.Kind() {
|
||||
case Uintptr:
|
||||
if v.indirect {
|
||||
return uint64(*(*uintptr)(v.value))
|
||||
} else {
|
||||
return uint64(uintptr(v.value))
|
||||
}
|
||||
case Uint8:
|
||||
if v.indirect {
|
||||
return uint64(*(*uint8)(v.value))
|
||||
} else {
|
||||
return uint64(uintptr(v.value))
|
||||
}
|
||||
case Uint16:
|
||||
if v.indirect {
|
||||
return uint64(*(*uint16)(v.value))
|
||||
} else {
|
||||
return uint64(uintptr(v.value))
|
||||
}
|
||||
case Uint:
|
||||
if v.indirect || unsafe.Sizeof(uint(0)) > unsafe.Sizeof(uintptr(0)) {
|
||||
return uint64(*(*uint)(v.value))
|
||||
} else {
|
||||
return uint64(uintptr(v.value))
|
||||
}
|
||||
case Uint32:
|
||||
if v.indirect || unsafe.Sizeof(uint32(0)) > unsafe.Sizeof(uintptr(0)) {
|
||||
return uint64(*(*uint32)(v.value))
|
||||
} else {
|
||||
return uint64(uintptr(v.value))
|
||||
}
|
||||
case Uint64:
|
||||
if v.indirect || unsafe.Sizeof(uint64(0)) > unsafe.Sizeof(uintptr(0)) {
|
||||
return uint64(*(*uint64)(v.value))
|
||||
} else {
|
||||
return uint64(uintptr(v.value))
|
||||
}
|
||||
default:
|
||||
panic(&ValueError{"Uint"})
|
||||
}
|
||||
}
|
||||
|
||||
func (v Value) Float() float64 {
|
||||
panic("unimplemented: (reflect.Value).Float()")
|
||||
switch v.Kind() {
|
||||
case Float32:
|
||||
if v.indirect || unsafe.Sizeof(float32(0)) > unsafe.Sizeof(uintptr(0)) {
|
||||
// The float is stored as an external value on systems with 16-bit
|
||||
// pointers.
|
||||
return float64(*(*float32)(v.value))
|
||||
} else {
|
||||
// The float is directly stored in the interface value on systems
|
||||
// with 32-bit and 64-bit pointers.
|
||||
return float64(*(*float32)(unsafe.Pointer(&v.value)))
|
||||
}
|
||||
case Float64:
|
||||
if v.indirect || unsafe.Sizeof(float64(0)) > unsafe.Sizeof(uintptr(0)) {
|
||||
// For systems with 16-bit and 32-bit pointers.
|
||||
return *(*float64)(v.value)
|
||||
} else {
|
||||
// The float is directly stored in the interface value on systems
|
||||
// with 64-bit pointers.
|
||||
return *(*float64)(unsafe.Pointer(&v.value))
|
||||
}
|
||||
default:
|
||||
panic(&ValueError{"Float"})
|
||||
}
|
||||
}
|
||||
|
||||
func (v Value) Complex() complex128 {
|
||||
panic("unimplemented: (reflect.Value).Complex()")
|
||||
switch v.Kind() {
|
||||
case Complex64:
|
||||
if v.indirect || unsafe.Sizeof(complex64(0)) > unsafe.Sizeof(uintptr(0)) {
|
||||
// The complex number is stored as an external value on systems with
|
||||
// 16-bit and 32-bit pointers.
|
||||
return complex128(*(*complex64)(v.value))
|
||||
} else {
|
||||
// The complex number is directly stored in the interface value on
|
||||
// systems with 64-bit pointers.
|
||||
return complex128(*(*complex64)(unsafe.Pointer(&v.value)))
|
||||
}
|
||||
case Complex128:
|
||||
// This is a 128-bit value, which is always stored as an external value.
|
||||
// It may be stored in the pointer directly on very uncommon
|
||||
// architectures with 128-bit pointers, however.
|
||||
return *(*complex128)(v.value)
|
||||
default:
|
||||
panic(&ValueError{"Complex"})
|
||||
}
|
||||
}
|
||||
|
||||
func (v Value) String() string {
|
||||
panic("unimplemented: (reflect.Value).String()")
|
||||
switch v.Kind() {
|
||||
case String:
|
||||
// A string value is always bigger than a pointer as it is made of a
|
||||
// pointer and a length.
|
||||
return *(*string)(v.value)
|
||||
default:
|
||||
// Special case because of the special treatment of .String() in Go.
|
||||
return "<T>"
|
||||
}
|
||||
}
|
||||
|
||||
func (v Value) Bytes() []byte {
|
||||
@@ -92,7 +274,25 @@ func (v Value) Slice(i, j int) Value {
|
||||
}
|
||||
|
||||
func (v Value) Len() int {
|
||||
panic("unimplemented: (reflect.Value).Len()")
|
||||
t := v.Type()
|
||||
switch t.Kind() {
|
||||
case Slice:
|
||||
return int((*SliceHeader)(v.value).Len)
|
||||
case String:
|
||||
return int((*StringHeader)(v.value).Len)
|
||||
default: // Array, Chan, Map
|
||||
panic("unimplemented: (reflect.Value).Len()")
|
||||
}
|
||||
}
|
||||
|
||||
func (v Value) Cap() int {
|
||||
t := v.Type()
|
||||
switch t.Kind() {
|
||||
case Slice:
|
||||
return int((*SliceHeader)(v.value).Cap)
|
||||
default: // Array, Chan
|
||||
panic("unimplemented: (reflect.Value).Cap()")
|
||||
}
|
||||
}
|
||||
|
||||
func (v Value) NumField() int {
|
||||
@@ -100,7 +300,23 @@ func (v Value) NumField() int {
|
||||
}
|
||||
|
||||
func (v Value) Elem() Value {
|
||||
panic("unimplemented: (reflect.Value).Elem()")
|
||||
switch v.Kind() {
|
||||
case Ptr:
|
||||
ptr := v.value
|
||||
if v.indirect {
|
||||
ptr = *(*unsafe.Pointer)(ptr)
|
||||
}
|
||||
if ptr == nil {
|
||||
return Value{}
|
||||
}
|
||||
return Value{
|
||||
typecode: v.Type().Elem(),
|
||||
value: ptr,
|
||||
indirect: true,
|
||||
}
|
||||
default: // not implemented: Interface
|
||||
panic(&ValueError{"Elem"})
|
||||
}
|
||||
}
|
||||
|
||||
func (v Value) Field(i int) Value {
|
||||
@@ -108,7 +324,37 @@ func (v Value) Field(i int) Value {
|
||||
}
|
||||
|
||||
func (v Value) Index(i int) Value {
|
||||
panic("unimplemented: (reflect.Value).Index()")
|
||||
switch v.Kind() {
|
||||
case Slice:
|
||||
// Extract an element from the slice.
|
||||
slice := *(*SliceHeader)(v.value)
|
||||
if uint(i) >= uint(slice.Len) {
|
||||
panic("reflect: slice index out of range")
|
||||
}
|
||||
elem := Value{
|
||||
typecode: v.Type().Elem(),
|
||||
indirect: true,
|
||||
}
|
||||
addr := uintptr(slice.Data) + elem.Type().Size()*uintptr(i) // pointer to new value
|
||||
elem.value = unsafe.Pointer(addr)
|
||||
return elem
|
||||
case String:
|
||||
// Extract a character from a string.
|
||||
// A string is never stored directly in the interface, but always as a
|
||||
// pointer to the string value.
|
||||
s := *(*StringHeader)(v.value)
|
||||
if uint(i) >= uint(s.Len) {
|
||||
panic("reflect: string index out of range")
|
||||
}
|
||||
return Value{
|
||||
typecode: Uint8.basicType(),
|
||||
value: unsafe.Pointer(uintptr(*(*uint8)(unsafe.Pointer(s.Data + uintptr(i))))),
|
||||
}
|
||||
case Array:
|
||||
panic("unimplemented: (reflect.Value).Index()")
|
||||
default:
|
||||
panic(&ValueError{"Index"})
|
||||
}
|
||||
}
|
||||
|
||||
func (v Value) MapKeys() []Value {
|
||||
@@ -119,34 +365,172 @@ 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) {
|
||||
panic("unimplemented: (reflect.Value).Set()")
|
||||
if !v.indirect {
|
||||
panic("reflect: value is not addressable")
|
||||
}
|
||||
if v.Type() != x.Type() {
|
||||
if v.Kind() == Interface {
|
||||
panic("reflect: unimplemented: assigning to interface of different type")
|
||||
} else {
|
||||
panic("reflect: cannot assign")
|
||||
}
|
||||
}
|
||||
size := v.Type().Size()
|
||||
xptr := x.value
|
||||
if size <= unsafe.Sizeof(uintptr(0)) && !x.indirect {
|
||||
value := x.value
|
||||
xptr = unsafe.Pointer(&value)
|
||||
}
|
||||
memcpy(v.value, xptr, size)
|
||||
}
|
||||
|
||||
func (v Value) SetBool(x bool) {
|
||||
panic("unimplemented: (reflect.Value).SetBool()")
|
||||
if !v.indirect {
|
||||
panic("reflect: value is not addressable")
|
||||
}
|
||||
switch v.Kind() {
|
||||
case Bool:
|
||||
*(*bool)(v.value) = x
|
||||
default:
|
||||
panic(&ValueError{"SetBool"})
|
||||
}
|
||||
}
|
||||
|
||||
func (v Value) SetInt(x int64) {
|
||||
panic("unimplemented: (reflect.Value).SetInt()")
|
||||
if !v.indirect {
|
||||
panic("reflect: value is not addressable")
|
||||
}
|
||||
switch v.Kind() {
|
||||
case Int:
|
||||
*(*int)(v.value) = int(x)
|
||||
case Int8:
|
||||
*(*int8)(v.value) = int8(x)
|
||||
case Int16:
|
||||
*(*int16)(v.value) = int16(x)
|
||||
case Int32:
|
||||
*(*int32)(v.value) = int32(x)
|
||||
case Int64:
|
||||
*(*int64)(v.value) = x
|
||||
default:
|
||||
panic(&ValueError{"SetInt"})
|
||||
}
|
||||
}
|
||||
|
||||
func (v Value) SetUint(x uint64) {
|
||||
panic("unimplemented: (reflect.Value).SetUint()")
|
||||
if !v.indirect {
|
||||
panic("reflect: value is not addressable")
|
||||
}
|
||||
switch v.Kind() {
|
||||
case Uint:
|
||||
*(*uint)(v.value) = uint(x)
|
||||
case Uint8:
|
||||
*(*uint8)(v.value) = uint8(x)
|
||||
case Uint16:
|
||||
*(*uint16)(v.value) = uint16(x)
|
||||
case Uint32:
|
||||
*(*uint32)(v.value) = uint32(x)
|
||||
case Uint64:
|
||||
*(*uint64)(v.value) = x
|
||||
case Uintptr:
|
||||
*(*uintptr)(v.value) = uintptr(x)
|
||||
default:
|
||||
panic(&ValueError{"SetUint"})
|
||||
}
|
||||
}
|
||||
|
||||
func (v Value) SetFloat(x float64) {
|
||||
panic("unimplemented: (reflect.Value).SetFloat()")
|
||||
if !v.indirect {
|
||||
panic("reflect: value is not addressable")
|
||||
}
|
||||
switch v.Kind() {
|
||||
case Float32:
|
||||
*(*float32)(v.value) = float32(x)
|
||||
case Float64:
|
||||
*(*float64)(v.value) = x
|
||||
default:
|
||||
panic(&ValueError{"SetFloat"})
|
||||
}
|
||||
}
|
||||
|
||||
func (v Value) SetComplex(x complex128) {
|
||||
panic("unimplemented: (reflect.Value).SetComplex()")
|
||||
if !v.indirect {
|
||||
panic("reflect: value is not addressable")
|
||||
}
|
||||
switch v.Kind() {
|
||||
case Complex64:
|
||||
*(*complex64)(v.value) = complex64(x)
|
||||
case Complex128:
|
||||
*(*complex128)(v.value) = x
|
||||
default:
|
||||
panic(&ValueError{"SetComplex"})
|
||||
}
|
||||
}
|
||||
|
||||
func (v Value) SetString(x string) {
|
||||
panic("unimplemented: (reflect.Value).SetString()")
|
||||
if !v.indirect {
|
||||
panic("reflect: value is not addressable")
|
||||
}
|
||||
switch v.Kind() {
|
||||
case String:
|
||||
*(*string)(v.value) = x
|
||||
default:
|
||||
panic(&ValueError{"SetString"})
|
||||
}
|
||||
}
|
||||
|
||||
func MakeSlice(typ Type, len, cap int) Value {
|
||||
panic("unimplemented: reflect.MakeSlice()")
|
||||
}
|
||||
|
||||
type funcHeader struct {
|
||||
Context unsafe.Pointer
|
||||
Code unsafe.Pointer
|
||||
}
|
||||
|
||||
// This is the same thing as an interface{}.
|
||||
type interfaceHeader struct {
|
||||
typecode Type
|
||||
value unsafe.Pointer
|
||||
}
|
||||
|
||||
type SliceHeader struct {
|
||||
Data uintptr
|
||||
Len uintptr
|
||||
Cap uintptr
|
||||
}
|
||||
|
||||
type StringHeader struct {
|
||||
Data uintptr
|
||||
Len uintptr
|
||||
}
|
||||
|
||||
type ValueError struct {
|
||||
Method string
|
||||
}
|
||||
|
||||
func (e *ValueError) Error() string {
|
||||
return "reflect: call of reflect.Value." + e.Method + " on invalid type"
|
||||
}
|
||||
|
||||
//go:linkname memcpy runtime.memcpy
|
||||
func memcpy(dst, src unsafe.Pointer, size uintptr)
|
||||
|
||||
@@ -5,7 +5,9 @@ const GOARCH = "amd64"
|
||||
// The bitness of the CPU (e.g. 8, 32, 64).
|
||||
const TargetBits = 64
|
||||
|
||||
// Align on word boundary.
|
||||
// Align a pointer.
|
||||
// Note that some amd64 instructions (like movaps) expect 16-byte aligned
|
||||
// memory, thus the result must be 16-byte aligned.
|
||||
func align(ptr uintptr) uintptr {
|
||||
return (ptr + 7) &^ 7
|
||||
return (ptr + 15) &^ 15
|
||||
}
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
// +build arm,!avr,!cortexm
|
||||
|
||||
package runtime
|
||||
|
||||
const GOARCH = "arm"
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user