mirror of
https://github.com/tinygo-org/tinygo.git
synced 2026-08-04 11:07:46 +00:00
Compare commits
117 Commits
v0.4.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 |
+204
-16
@@ -18,14 +18,12 @@ commands:
|
|||||||
wget -O - https://apt.llvm.org/llvm-snapshot.gpg.key|sudo apt-key add -
|
wget -O - https://apt.llvm.org/llvm-snapshot.gpg.key|sudo apt-key add -
|
||||||
sudo apt-get update
|
sudo apt-get update
|
||||||
sudo apt-get install \
|
sudo apt-get install \
|
||||||
llvm \
|
|
||||||
python3 \
|
python3 \
|
||||||
llvm<<parameters.llvm>>-dev \
|
llvm<<parameters.llvm>>-dev \
|
||||||
clang<<parameters.llvm>> \
|
clang<<parameters.llvm>> \
|
||||||
libclang<<parameters.llvm>>-dev \
|
libclang<<parameters.llvm>>-dev \
|
||||||
lld<<parameters.llvm>> \
|
lld<<parameters.llvm>> \
|
||||||
gcc-arm-linux-gnueabihf \
|
gcc-arm-linux-gnueabihf \
|
||||||
binutils-arm-none-eabi \
|
|
||||||
libc6-dev-armel-cross \
|
libc6-dev-armel-cross \
|
||||||
gcc-aarch64-linux-gnu \
|
gcc-aarch64-linux-gnu \
|
||||||
libc6-dev-arm64-cross \
|
libc6-dev-arm64-cross \
|
||||||
@@ -49,45 +47,62 @@ commands:
|
|||||||
command: |
|
command: |
|
||||||
curl https://raw.githubusercontent.com/golang/dep/master/install.sh | sh
|
curl https://raw.githubusercontent.com/golang/dep/master/install.sh | sh
|
||||||
dep ensure --vendor-only
|
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:
|
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:
|
steps:
|
||||||
- run: tinygo build -size short -o test.elf -target=pca10040 examples/blinky1
|
- run: tinygo build -size short -o test.elf -target=pca10040 examples/blinky1
|
||||||
- run: tinygo build -size short -o test.elf -target=pca10040 examples/blinky2
|
- run: tinygo build -size short -o test.elf -target=pca10040 examples/blinky2
|
||||||
- run: tinygo build -size short -o blinky2 examples/blinky2
|
- run: tinygo build -o blinky2 examples/blinky2 # TODO: re-enable -size flag with MachO support
|
||||||
- run: tinygo build -size short -o test.elf -target=pca10040 examples/test
|
- run: tinygo build -size short -o test.elf -target=pca10040 examples/test
|
||||||
- run: tinygo build -size short -o test.elf -target=microbit examples/echo
|
- 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=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=pca10031 examples/blinky1
|
||||||
- run: tinygo build -size short -o test.elf -target=bluepill examples/blinky1
|
- run: tinygo build -size short -o test.elf -target=bluepill examples/blinky1
|
||||||
- run: tinygo build -size short -o test.elf -target=arduino examples/blinky1
|
|
||||||
- run: tinygo build -size short -o test.elf -target=digispark examples/blinky1
|
|
||||||
- run: tinygo build -size short -o test.elf -target=reelboard examples/blinky1
|
- run: tinygo build -size short -o test.elf -target=reelboard examples/blinky1
|
||||||
- run: tinygo build -size short -o test.elf -target=reelboard examples/blinky2
|
- 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/blinky1
|
||||||
- run: tinygo build -size short -o test.elf -target=pca10056 examples/blinky2
|
- 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=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=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
|
||||||
jobs:
|
- run: tinygo build -size short -o test.elf -target=circuitplay-express examples/i2s
|
||||||
test-llvm7-go111:
|
- run: tinygo build -o wasm.wasm -target=wasm examples/wasm/export
|
||||||
docker:
|
- run: tinygo build -o wasm.wasm -target=wasm examples/wasm/main
|
||||||
- image: circleci/golang:1.11
|
test-linux:
|
||||||
|
parameters:
|
||||||
working_directory: /go/src/github.com/tinygo-org/tinygo
|
llvm:
|
||||||
|
type: string
|
||||||
steps:
|
steps:
|
||||||
- checkout
|
- checkout
|
||||||
- submodules
|
- submodules
|
||||||
- apt-dependencies:
|
- apt-dependencies:
|
||||||
llvm: "-7"
|
llvm: <<parameters.llvm>>
|
||||||
- install-node
|
- install-node
|
||||||
- restore_cache:
|
- restore_cache:
|
||||||
keys:
|
keys:
|
||||||
- go-cache-{{ checksum "Gopkg.lock" }}-{{ .Environment.CIRCLE_PREVIOUS_BUILD_NUM }}
|
- go-cache-{{ checksum "Gopkg.lock" }}-{{ .Environment.CIRCLE_PREVIOUS_BUILD_NUM }}
|
||||||
- go-cache-{{ checksum "Gopkg.lock" }}
|
- go-cache-{{ checksum "Gopkg.lock" }}
|
||||||
|
- llvm-source-linux
|
||||||
- dep
|
- dep
|
||||||
- run: go install .
|
- run: go install .
|
||||||
- run: make test
|
- run: go test -v
|
||||||
- run: make gen-device -j4
|
- run: make gen-device -j4
|
||||||
- smoketest
|
- smoketest
|
||||||
- save_cache:
|
- save_cache:
|
||||||
@@ -95,8 +110,181 @@ jobs:
|
|||||||
paths:
|
paths:
|
||||||
- ~/.cache/go-build
|
- ~/.cache/go-build
|
||||||
- ~/.cache/tinygo
|
- ~/.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:
|
workflows:
|
||||||
test-all:
|
test-all:
|
||||||
jobs:
|
jobs:
|
||||||
- test-llvm7-go111
|
- 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/*.go
|
||||||
src/device/sam/*.s
|
src/device/sam/*.s
|
||||||
vendor
|
vendor
|
||||||
|
llvm
|
||||||
|
llvm-build
|
||||||
|
|||||||
+1
-1
@@ -13,4 +13,4 @@
|
|||||||
[submodule "lib/compiler-rt"]
|
[submodule "lib/compiler-rt"]
|
||||||
path = lib/compiler-rt
|
path = lib/compiler-rt
|
||||||
url = https://github.com/llvm-mirror/compiler-rt.git
|
url = https://github.com/llvm-mirror/compiler-rt.git
|
||||||
branch = release_70
|
branch = release_80
|
||||||
|
|||||||
-40
@@ -1,40 +0,0 @@
|
|||||||
language: go
|
|
||||||
|
|
||||||
matrix:
|
|
||||||
include:
|
|
||||||
- os: osx
|
|
||||||
go: "1.11"
|
|
||||||
env: PATH="/usr/local/opt/llvm/bin:$PATH"
|
|
||||||
before_install:
|
|
||||||
- mkdir -p /Users/travis/gopath/bin
|
|
||||||
|
|
||||||
addons:
|
|
||||||
homebrew:
|
|
||||||
update: true
|
|
||||||
taps: ArmMbed/homebrew-formulae
|
|
||||||
packages:
|
|
||||||
- llvm@7
|
|
||||||
- qemu
|
|
||||||
- arm-none-eabi-gcc
|
|
||||||
|
|
||||||
install:
|
|
||||||
- curl https://raw.githubusercontent.com/golang/dep/master/install.sh | sh
|
|
||||||
- dep ensure --vendor-only
|
|
||||||
|
|
||||||
script:
|
|
||||||
- go install github.com/tinygo-org/tinygo
|
|
||||||
- go test -v .
|
|
||||||
- make gen-device
|
|
||||||
- tinygo build -size short -o blinky1.nrf.elf -target=pca10040 examples/blinky1
|
|
||||||
- tinygo build -size short -o blinky2.nrf.elf -target=pca10040 examples/blinky2
|
|
||||||
- tinygo build -o blinky2 examples/blinky2 # TODO: re-enable -size flag with MachO support
|
|
||||||
- tinygo build -size short -o test.nrf.elf -target=pca10040 examples/test
|
|
||||||
- tinygo build -size short -o blinky1.nrf51.elf -target=microbit examples/echo
|
|
||||||
- tinygo build -size short -o test.nrf.elf -target=nrf52840-mdk examples/blinky1
|
|
||||||
- tinygo build -size short -o blinky1.nrf51d.elf -target=pca10031 examples/blinky1
|
|
||||||
- tinygo build -size short -o blinky1.stm32.elf -target=bluepill examples/blinky1
|
|
||||||
- tinygo build -size short -o blinky1.reel.elf -target=reelboard examples/blinky1
|
|
||||||
- tinygo build -size short -o blinky2.reel.elf -target=reelboard examples/blinky2
|
|
||||||
- tinygo build -size short -o blinky1.pca10056.elf -target=pca10056 examples/blinky1
|
|
||||||
- tinygo build -size short -o blinky2.pca10056.elf -target=pca10056 examples/blinky2
|
|
||||||
- tinygo build -size short -o blinky1.samd21.elf -target=itsybitsy-m0 examples/blinky1
|
|
||||||
+26
-60
@@ -2,9 +2,9 @@
|
|||||||
|
|
||||||
TinyGo depends on LLVM and libclang, which are both big C++ libraries. It can
|
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
|
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
|
these can be linked: dynamically and statically. An install with `go install` is
|
||||||
because it is fast and works almost out of the box on Debian-based systems with
|
dynamic linking because it is fast and works almost out of the box on
|
||||||
the right libraries installed.
|
Debian-based systems with the right packages installed.
|
||||||
|
|
||||||
This guide describes how to statically link TinyGo against LLVM, libclang and
|
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
|
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+)
|
* Go (1.11+)
|
||||||
* [dep](https://golang.github.io/dep/)
|
* [dep](https://golang.github.io/dep/)
|
||||||
* Standard build tools (gcc/clang)
|
* Standard build tools (gcc/clang)
|
||||||
* git or subversion
|
* git
|
||||||
* CMake
|
* 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
|
The rest of this guide assumes you're running Linux, but it should be equivalent
|
||||||
on a different system like Mac.
|
on a different system like Mac.
|
||||||
|
|
||||||
## Download the source
|
## Download the source
|
||||||
|
|
||||||
The first step is to get the source code. Place it in some directory, assuming
|
The first step is to download the TinyGo sources. Then, inside the directory,
|
||||||
`$HOME/src` here, but you can pick a different one of course:
|
perform these steps:
|
||||||
|
|
||||||
git clone -b release_70 https://github.com/llvm-mirror/llvm.git $HOME/src/llvm
|
dep ensure -vendor-only # download Go dependencies
|
||||||
git clone -b release_70 https://github.com/llvm-mirror/clang.git $HOME/src/llvm/tools/clang
|
make llvm-source # download LLVM
|
||||||
git clone -b release_70 https://github.com/llvm-mirror/lld.git $HOME/src/llvm/tools/lld
|
|
||||||
go get -d github.com/tinygo-org/tinygo
|
|
||||||
cd $HOME/go/src/github.com/tinygo-org/tinygo
|
|
||||||
dep ensure -vendor-only # download dependencies
|
|
||||||
|
|
||||||
Note that Clang and LLD must be placed inside the tools subdirectory of LLVM to
|
You can also store LLVM outside of the TinyGo root directory by setting the
|
||||||
be automatically built with the rest of the system.
|
`LLVM_BUILDDIR`, `CLANG_SRC` and `LLD_SRC` make variables, but that is not
|
||||||
|
covered by this guide.
|
||||||
|
|
||||||
## Build LLVM, Clang, LLD
|
## Build LLVM, Clang, LLD
|
||||||
|
|
||||||
Building LLVM is quite easy compared to some other software packages. However,
|
Before starting the build, you may want to set the following environment
|
||||||
the default configuration is _not_ optimized for distribution. It is optimized
|
variables to speed up the build. Most Linux distributions ship with GCC as the
|
||||||
for development, meaning that binaries produce accurate error messages at the
|
default compiler, but Clang is significantly faster and uses much less memory
|
||||||
cost of huge binaries and slow compiles.
|
while producing binaries that are about as fast.
|
||||||
|
|
||||||
Before configuring, you may want to set the following environment variables to
|
|
||||||
speed up the build. Most Linux distributions ship with GCC as the default
|
|
||||||
compiler, but Clang is significantly faster and uses much less memory while
|
|
||||||
producing binaries that are about as fast.
|
|
||||||
|
|
||||||
export CC=clang
|
export CC=clang
|
||||||
export CXX=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
|
make llvm-build
|
||||||
cd $HOME/src/llvm-build
|
|
||||||
|
|
||||||
Configure LLVM with CMake:
|
|
||||||
|
|
||||||
cmake -G Ninja ../llvm "-DLLVM_TARGETS_TO_BUILD=X86;ARM;AArch64" "-DLLVM_EXPERIMENTAL_TARGETS_TO_BUILD=AVR;WebAssembly" -DCMAKE_BUILD_TYPE=Release -DLLVM_ENABLE_ASSERTIONS=OFF -DLIBCLANG_BUILD_STATIC=ON
|
|
||||||
|
|
||||||
You can also choose a different build system than Ninja, but Ninja is fast.
|
|
||||||
|
|
||||||
There are various options you can tune here, but the options given above are
|
|
||||||
preferable for releases. Here is what they do:
|
|
||||||
|
|
||||||
* `LLVM_TARGETS_TO_BUILD` and `LLVM_EXPERIMENTAL_TARGETS_TO_BUILD`: the
|
|
||||||
targets that are natively supported by the LLVM code generators. The targets
|
|
||||||
listed here are the ones supported by TinyGo. Note that LLVM is a cross
|
|
||||||
compiler by default, unlike some other compilers.
|
|
||||||
* `CMAKE_BUILD_TYPE`: the default is Debug, which produces large inefficient
|
|
||||||
binaries that are easy to debug. We want small and fast binaries.
|
|
||||||
* `LLVM_ENABLE_ASSERTIONS`: the default is ON, which greatly slows down LLVM
|
|
||||||
and is only really useful during development. Disable them here.
|
|
||||||
* `LIBCLANG_BUILD_STATIC`: unlike LLVM, libclang is built as a shared library
|
|
||||||
by default. We want a static library for easy distribution.
|
|
||||||
|
|
||||||
Now build it:
|
|
||||||
|
|
||||||
ninja # or make, if you choose make in the previous step
|
|
||||||
|
|
||||||
This can take over an hour depending on the speed of your system.
|
This can take over an hour depending on the speed of your system.
|
||||||
|
|
||||||
## Build TinyGo
|
## Build TinyGo
|
||||||
|
|
||||||
Now that you have a working version of LLVM, build TinyGo using it. You need to
|
The last step of course is to build TinyGo itself. This can again be done with
|
||||||
specify the directories to the LLVM build directory and to the Clang and LLD source.
|
make:
|
||||||
|
|
||||||
cd $HOME/go/src/github.com/tinygo-org/tinygo
|
make
|
||||||
make static LLVM_BUILDDIR=$HOME/src/llvm-build CLANG_SRC=$HOME/src/llvm/tools/clang LLD_SRC=$HOME/src/llvm/tools/lld
|
|
||||||
|
|
||||||
## Verify TinyGo
|
## Verify TinyGo
|
||||||
|
|
||||||
@@ -109,14 +77,12 @@ The result should not contain libclang or libLLVM.
|
|||||||
|
|
||||||
## Make a release tarball
|
## Make a release tarball
|
||||||
|
|
||||||
Now that we have a working static build, it's time to make a release tarball.
|
Now that we have a working static build, it's time to make a release tarball:
|
||||||
This is just a slight change from the command to build TinyGo:
|
|
||||||
|
|
||||||
cd $HOME/go/src/github.com/tinygo-org/tinygo
|
make release
|
||||||
make release LLVM_BUILDDIR=$HOME/src/llvm-build CLANG_SRC=$HOME/src/llvm/tools/clang LLD_SRC=$HOME/src/llvm/tools/lld
|
|
||||||
|
|
||||||
The release tarball is stored in build/release.tar.gz, and can be extracted with
|
The 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
|
tar -xvf path/to/release.tar.gz
|
||||||
|
|
||||||
|
|||||||
@@ -1,3 +1,38 @@
|
|||||||
|
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
|
0.4.1
|
||||||
---
|
---
|
||||||
- **compiler**
|
- **compiler**
|
||||||
|
|||||||
+11
-8
@@ -1,17 +1,20 @@
|
|||||||
# 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
|
FROM golang:latest AS tinygo-base
|
||||||
|
|
||||||
RUN wget -O- https://apt.llvm.org/llvm-snapshot.gpg.key| apt-key add - && \
|
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 update && \
|
||||||
apt-get install -y llvm-7-dev libclang-7-dev git
|
apt-get install -y llvm-8-dev libclang-8-dev git
|
||||||
|
|
||||||
RUN wget -O- https://raw.githubusercontent.com/golang/dep/master/install.sh | sh
|
RUN wget -O- https://raw.githubusercontent.com/golang/dep/master/install.sh | sh
|
||||||
|
|
||||||
COPY . /go/src/github.com/tinygo-org/tinygo
|
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/ && \
|
RUN cd /go/src/github.com/tinygo-org/tinygo/ && \
|
||||||
git submodule update --init
|
rm -rf ./lib/* && \
|
||||||
|
git submodule update --init --recursive --force
|
||||||
|
|
||||||
RUN cd /go/src/github.com/tinygo-org/tinygo/ && \
|
RUN cd /go/src/github.com/tinygo-org/tinygo/ && \
|
||||||
dep ensure --vendor-only && \
|
dep ensure --vendor-only && \
|
||||||
@@ -25,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
|
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 - && \
|
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 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.
|
# tinygo-avr stage installs the needed dependencies to compile TinyGo programs for AVR microcontrollers.
|
||||||
FROM tinygo-base AS tinygo-avr
|
FROM tinygo-base AS tinygo-avr
|
||||||
@@ -59,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/ && \
|
RUN cd /go/src/github.com/tinygo-org/tinygo/ && \
|
||||||
apt-get update && \
|
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 && \
|
make gen-device-nrf && make gen-device-stm32 && \
|
||||||
apt-get remove -y python3 make && \
|
apt-get remove -y python3 make && \
|
||||||
apt-get autoremove -y && \
|
apt-get autoremove -y && \
|
||||||
@@ -74,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/ && \
|
RUN cd /go/src/github.com/tinygo-org/tinygo/ && \
|
||||||
apt-get update && \
|
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 && \
|
make gen-device && \
|
||||||
apt-get remove -y python3 make && \
|
apt-get remove -y python3 make && \
|
||||||
apt-get autoremove -y && \
|
apt-get autoremove -y && \
|
||||||
|
|||||||
Generated
+13
-4
@@ -1,6 +1,14 @@
|
|||||||
# This file is autogenerated, do not edit; changes may be undone by the next 'dep ensure'.
|
# This file is autogenerated, do not edit; changes may be undone by the next 'dep ensure'.
|
||||||
|
|
||||||
|
|
||||||
|
[[projects]]
|
||||||
|
branch = "master"
|
||||||
|
digest = "1:06519a2ec1d59040eaccec40206f9d0b59dc662db2a032f974d6d6b9a2bcb839"
|
||||||
|
name = "github.com/blakesmith/ar"
|
||||||
|
packages = ["."]
|
||||||
|
pruneopts = "UT"
|
||||||
|
revision = "8bd4349a67f2533b078dbc524689d15dba0f4659"
|
||||||
|
|
||||||
[[projects]]
|
[[projects]]
|
||||||
branch = "master"
|
branch = "master"
|
||||||
digest = "1:00b45e06c7843541372fc17d982242bd6adfc2fc382b6f2e9ef9ce53d87a50b9"
|
digest = "1:00b45e06c7843541372fc17d982242bd6adfc2fc382b6f2e9ef9ce53d87a50b9"
|
||||||
@@ -19,20 +27,21 @@
|
|||||||
"go/types/typeutil",
|
"go/types/typeutil",
|
||||||
]
|
]
|
||||||
pruneopts = "UT"
|
pruneopts = "UT"
|
||||||
revision = "3744606dbb67b99c60d3f11cb10bd3f9e6dad472"
|
revision = "8dcc6e70cdefe9a82236b6e195e4f4e2108fcb9f"
|
||||||
|
|
||||||
[[projects]]
|
[[projects]]
|
||||||
branch = "master"
|
branch = "llvm8"
|
||||||
digest = "1:a6a25fd8906c74978f1ed811bc9fd3422da8093be863b458874b02a782b6ae3e"
|
digest = "1:bf5539bdf6b3cc3ec1e45926db05d81180da11ce722fa1edcce3f0b4e1967da5"
|
||||||
name = "tinygo.org/x/go-llvm"
|
name = "tinygo.org/x/go-llvm"
|
||||||
packages = ["."]
|
packages = ["."]
|
||||||
pruneopts = "UT"
|
pruneopts = "UT"
|
||||||
revision = "d5f730401f5069618b275a5241c6417eb0c38a65"
|
revision = "7707ae5d1261a8929edea7336c8087ca8b520d8d"
|
||||||
|
|
||||||
[solve-meta]
|
[solve-meta]
|
||||||
analyzer-name = "dep"
|
analyzer-name = "dep"
|
||||||
analyzer-version = 1
|
analyzer-version = 1
|
||||||
input-imports = [
|
input-imports = [
|
||||||
|
"github.com/blakesmith/ar",
|
||||||
"github.com/marcinbor85/gohex",
|
"github.com/marcinbor85/gohex",
|
||||||
"golang.org/x/tools/go/ast/astutil",
|
"golang.org/x/tools/go/ast/astutil",
|
||||||
"golang.org/x/tools/go/ssa",
|
"golang.org/x/tools/go/ssa",
|
||||||
|
|||||||
+1
-1
@@ -1,5 +1,5 @@
|
|||||||
[[constraint]]
|
[[constraint]]
|
||||||
branch = "master"
|
branch = "llvm8"
|
||||||
name = "tinygo.org/x/go-llvm"
|
name = "tinygo.org/x/go-llvm"
|
||||||
|
|
||||||
[[constraint]]
|
[[constraint]]
|
||||||
|
|||||||
@@ -1,5 +1,8 @@
|
|||||||
Copyright (c) 2018-2019 TinyGo Authors. 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
|
Redistribution and use in source and binary forms, with or without
|
||||||
modification, are permitted provided that the following conditions are
|
modification, are permitted provided that the following conditions are
|
||||||
met:
|
met:
|
||||||
|
|||||||
@@ -3,48 +3,24 @@
|
|||||||
all: tinygo
|
all: tinygo
|
||||||
tinygo: build/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
|
# Default build and source directories, as created by `make llvm-build`.
|
||||||
|
LLVM_BUILDDIR ?= llvm-build
|
||||||
ifeq ($(TARGET),unix)
|
CLANG_SRC ?= llvm/tools/clang
|
||||||
# Regular *nix system.
|
LLD_SRC ?= llvm/tools/lld
|
||||||
|
|
||||||
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
|
|
||||||
|
|
||||||
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
|
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.
|
# 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))
|
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:
|
clean:
|
||||||
@rm -rf build
|
@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:
|
fmt:
|
||||||
@go fmt . ./compiler ./interp ./loader ./ir ./src/device/arm ./src/examples/* ./src/machine ./src/os ./src/runtime ./src/sync
|
@gofmt -l -w $(FMT_PATHS)
|
||||||
@go fmt ./testdata/*.go
|
fmt-check:
|
||||||
|
@unformatted=$$(gofmt -l $(FMT_PATHS)); [ -z "$$unformatted" ] && exit 0; echo "Unformatted:"; for fn in $$unformatted; do echo " $$fn"; done; exit 1
|
||||||
test:
|
|
||||||
@go test -v .
|
|
||||||
|
|
||||||
gen-device: gen-device-avr gen-device-nrf gen-device-sam gen-device-stm32
|
gen-device: gen-device-avr gen-device-nrf gen-device-sam gen-device-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
|
./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
|
go fmt ./src/device/stm32
|
||||||
|
|
||||||
|
|
||||||
|
# Get LLVM sources.
|
||||||
|
llvm/README.txt:
|
||||||
|
git clone -b release_80 https://github.com/llvm-mirror/llvm.git llvm
|
||||||
|
llvm/tools/clang/README.txt:
|
||||||
|
git clone -b release_80 https://github.com/llvm-mirror/clang.git llvm/tools/clang
|
||||||
|
llvm/tools/lld/README.md:
|
||||||
|
git clone -b release_80 https://github.com/llvm-mirror/lld.git llvm/tools/lld
|
||||||
|
llvm-source: llvm/README.txt llvm/tools/clang/README.txt llvm/tools/lld/README.md
|
||||||
|
|
||||||
|
# Configure LLVM.
|
||||||
|
llvm-build/build.ninja: llvm-source
|
||||||
|
mkdir -p llvm-build; cd llvm-build; cmake -G Ninja ../llvm "-DLLVM_TARGETS_TO_BUILD=X86;ARM;AArch64;WebAssembly" "-DLLVM_EXPERIMENTAL_TARGETS_TO_BUILD=AVR" -DCMAKE_BUILD_TYPE=Release -DLLVM_ENABLE_ASSERTIONS=OFF -DLIBCLANG_BUILD_STATIC=ON -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 the Go compiler.
|
||||||
build/tinygo:
|
build/tinygo:
|
||||||
@mkdir -p build
|
@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
|
||||||
go build -o build/tinygo .
|
|
||||||
|
|
||||||
static:
|
|
||||||
CGO_CPPFLAGS="$(CGO_CPPFLAGS)" CGO_CXXFLAGS="$(CGO_CXXFLAGS)" CGO_LDFLAGS="$(CGO_LDFLAGS)" go build -o build/tinygo -tags byollvm .
|
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/bin
|
||||||
|
@mkdir -p build/release/tinygo/lib/clang/include
|
||||||
@mkdir -p build/release/tinygo/lib/CMSIS/CMSIS
|
@mkdir -p build/release/tinygo/lib/CMSIS/CMSIS
|
||||||
@mkdir -p build/release/tinygo/lib/compiler-rt/lib
|
@mkdir -p build/release/tinygo/lib/compiler-rt/lib
|
||||||
@mkdir -p build/release/tinygo/lib/nrfx
|
@mkdir -p build/release/tinygo/lib/nrfx
|
||||||
@mkdir -p build/release/tinygo/pkg/armv6m-none-eabi
|
@mkdir -p build/release/tinygo/pkg/armv6m-none-eabi
|
||||||
@mkdir -p build/release/tinygo/pkg/armv7m-none-eabi
|
@mkdir -p build/release/tinygo/pkg/armv7m-none-eabi
|
||||||
@mkdir -p build/release/tinygo/pkg/armv7em-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 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/CMSIS/Include build/release/tinygo/lib/CMSIS/CMSIS
|
||||||
@cp -rp lib/CMSIS/README.md build/release/tinygo/lib/CMSIS
|
@cp -rp lib/CMSIS/README.md build/release/tinygo/lib/CMSIS
|
||||||
@cp -rp lib/compiler-rt/lib/builtins build/release/tinygo/lib/compiler-rt/lib
|
@cp -rp lib/compiler-rt/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=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
|
./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
|
tar -czf build/release.tar.gz -C build/release tinygo
|
||||||
|
|
||||||
# Binary that can run on the host.
|
|
||||||
build/%: src/examples/% src/examples/%/*.go build/tinygo src/runtime/*.go
|
|
||||||
./build/tinygo build $(TGOFLAGS) -size=short -o $@ $(subst src/,,$<)
|
|
||||||
|
|
||||||
# ELF file that can run on a microcontroller.
|
|
||||||
build/%.elf: src/examples/% src/examples/%/*.go build/tinygo src/runtime/*.go
|
|
||||||
./build/tinygo build $(TGOFLAGS) -size=short -o $@ $(subst src/,,$<)
|
|
||||||
|
|
||||||
# Convert executable to Intel hex file (for flashing).
|
|
||||||
build/%.hex: build/%.elf
|
|
||||||
$(OBJCOPY) -O ihex $^ $@
|
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
# TinyGo - Go compiler for small places
|
# TinyGo - Go compiler for small places
|
||||||
|
|
||||||
[](https://travis-ci.com/tinygo-org/tinygo)
|
|
||||||
[](https://circleci.com/gh/tinygo-org/tinygo/tree/dev)
|
[](https://circleci.com/gh/tinygo-org/tinygo/tree/dev)
|
||||||
|
|
||||||
TinyGo is a Go compiler intended for use in small places such as microcontrollers, WebAssembly (WASM), and command-line tools.
|
TinyGo is a Go compiler intended for use in small places such as microcontrollers, WebAssembly (WASM), and command-line tools.
|
||||||
|
|||||||
+5
-3
@@ -9,9 +9,11 @@ import (
|
|||||||
|
|
||||||
// Get the cache directory, usually ~/.cache/tinygo
|
// Get the cache directory, usually ~/.cache/tinygo
|
||||||
func cacheDir() string {
|
func cacheDir() string {
|
||||||
home := getHomeDir()
|
dir, err := os.UserCacheDir()
|
||||||
dir := filepath.Join(home, ".cache", "tinygo")
|
if err != nil {
|
||||||
return dir
|
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
|
// Return the newest timestamp of all the file paths passed in. Used to check
|
||||||
|
|||||||
+46
-14
@@ -1,11 +1,15 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"errors"
|
||||||
|
"io"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/blakesmith/ar"
|
||||||
)
|
)
|
||||||
|
|
||||||
// These are the GENERIC_SOURCES according to CMakeList.txt.
|
// 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)
|
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
|
return path, err
|
||||||
}
|
}
|
||||||
|
|
||||||
var cachepath string
|
var cachepath string
|
||||||
err = compileBuiltins(target, func(path string) error {
|
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
|
cachepath = path
|
||||||
return err
|
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
|
// Note: -fdebug-prefix-map is necessary to make the output archive
|
||||||
// reproducible. Otherwise the temporary directory is stored in the
|
// reproducible. Otherwise the temporary directory is stored in the
|
||||||
// archive itself, which varies each run.
|
// 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)
|
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)
|
||||||
cmd.Stdout = os.Stdout
|
|
||||||
cmd.Stderr = os.Stderr
|
|
||||||
cmd.Dir = dir
|
|
||||||
err = cmd.Run()
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return &commandError{"failed to build", srcpath, err}
|
return &commandError{"failed to build", srcpath, err}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Put all builtins in an archive to link as a static library.
|
// 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")
|
arpath := filepath.Join(dir, "librt.a")
|
||||||
cmd := exec.Command(commands["ar"], append([]string{"cr", arpath}, objs...)...)
|
arfile, err := os.Create(arpath)
|
||||||
cmd.Stdout = os.Stdout
|
|
||||||
cmd.Stderr = os.Stderr
|
|
||||||
cmd.Dir = dir
|
|
||||||
err = cmd.Run()
|
|
||||||
if err != nil {
|
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,
|
// Give the caller the resulting file. The callback must copy the file,
|
||||||
// because after it returns the temporary directory will be removed.
|
// because after it returns the temporary directory will be removed.
|
||||||
|
arfile.Close()
|
||||||
return callback(arpath)
|
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
|
||||||
|
}
|
||||||
@@ -1,11 +1,11 @@
|
|||||||
// +build !byollvm
|
// +build !byollvm
|
||||||
|
|
||||||
package loader
|
package cgo
|
||||||
|
|
||||||
/*
|
/*
|
||||||
#cgo linux CFLAGS: -I/usr/lib/llvm-7/include
|
#cgo linux CFLAGS: -I/usr/lib/llvm-8/include
|
||||||
#cgo darwin CFLAGS: -I/usr/local/opt/llvm/include
|
#cgo darwin CFLAGS: -I/usr/local/opt/llvm/include
|
||||||
#cgo linux LDFLAGS: -L/usr/lib/llvm-7/lib -lclang
|
#cgo linux LDFLAGS: -L/usr/lib/llvm-8/lib -lclang
|
||||||
#cgo darwin LDFLAGS: -L/usr/local/opt/llvm/lib -lclang -lffi
|
#cgo darwin LDFLAGS: -L/usr/local/opt/llvm/lib -lclang -lffi
|
||||||
*/
|
*/
|
||||||
import "C"
|
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)
|
||||||
|
}
|
||||||
+30
-8
@@ -1,11 +1,33 @@
|
|||||||
// +build !darwin
|
|
||||||
|
|
||||||
package main
|
package main
|
||||||
|
|
||||||
// commands used by the compilation process might have different file names on Linux than those used on macOS.
|
import (
|
||||||
var commands = map[string]string{
|
"errors"
|
||||||
"ar": "llvm-ar-7",
|
"os"
|
||||||
"clang": "clang-7",
|
"os/exec"
|
||||||
"ld.lld": "ld.lld-7",
|
"strings"
|
||||||
"wasm-ld": "wasm-ld-7",
|
)
|
||||||
|
|
||||||
|
// 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, " "))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,11 +0,0 @@
|
|||||||
// +build darwin
|
|
||||||
|
|
||||||
package main
|
|
||||||
|
|
||||||
// commands used by the compilation process might have different file names on macOS than those used on Linux.
|
|
||||||
var commands = map[string]string{
|
|
||||||
"ar": "llvm-ar",
|
|
||||||
"clang": "clang-7",
|
|
||||||
"ld.lld": "ld.lld-7",
|
|
||||||
"wasm-ld": "wasm-ld-7",
|
|
||||||
}
|
|
||||||
+22
-2
@@ -51,6 +51,11 @@ func (c *Compiler) emitLookupBoundsCheck(frame *Frame, arrayLen, index llvm.Valu
|
|||||||
|
|
||||||
// emitSliceBoundsCheck emits a bounds check before a slicing operation to make
|
// emitSliceBoundsCheck emits a bounds check before a slicing operation to make
|
||||||
// sure it is within bounds.
|
// 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) {
|
func (c *Compiler) emitSliceBoundsCheck(frame *Frame, capacity, low, high llvm.Value, lowType, highType *types.Basic) {
|
||||||
if frame.fn.IsNoBounds() {
|
if frame.fn.IsNoBounds() {
|
||||||
// The //go:nobounds pragma was added to the function to avoid bounds
|
// The //go:nobounds pragma was added to the function to avoid bounds
|
||||||
@@ -115,8 +120,23 @@ func (c *Compiler) emitNilCheck(frame *Frame, ptr llvm.Value, blockPrefix string
|
|||||||
frame.blockExits[frame.currentBlock] = nextBlock // adjust outgoing block for phi nodes
|
frame.blockExits[frame.currentBlock] = nextBlock // adjust outgoing block for phi nodes
|
||||||
|
|
||||||
// Compare against nil.
|
// Compare against nil.
|
||||||
nilptr := llvm.ConstPointerNull(ptr.Type())
|
var isnil llvm.Value
|
||||||
isnil := c.builder.CreateICmp(llvm.IntEQ, ptr, nilptr, "")
|
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)
|
c.builder.CreateCondBr(isnil, faultBlock, nextBlock)
|
||||||
|
|
||||||
// Fail: this is a nil pointer, exit with a panic.
|
// Fail: this is a nil pointer, exit with a panic.
|
||||||
|
|||||||
+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.
|
// Equivalent of expandFormalParamType for parameter values.
|
||||||
func (c *Compiler) expandFormalParam(v llvm.Value) []llvm.Value {
|
func (c *Compiler) expandFormalParam(v llvm.Value) []llvm.Value {
|
||||||
switch v.Type().TypeKind() {
|
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
|
// Break down a struct into its elementary types for argument passing. The value
|
||||||
// equivalent of flattenAggregateType
|
// equivalent of flattenAggregateType
|
||||||
func (c *Compiler) flattenAggregate(v llvm.Value) []llvm.Value {
|
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() {
|
switch t.TypeKind() {
|
||||||
case llvm.StructTypeKind:
|
case llvm.StructTypeKind:
|
||||||
if len(c.flattenAggregateType(t)) <= MaxFieldsPerParam {
|
if len(c.flattenAggregateType(t)) <= MaxFieldsPerParam {
|
||||||
value, err := c.getZeroValue(t)
|
value := c.getZeroValue(t)
|
||||||
if err != nil {
|
|
||||||
panic("could not get zero value of struct: " + err.Error())
|
|
||||||
}
|
|
||||||
for i, subtyp := range t.StructElementTypes() {
|
for i, subtyp := range t.StructElementTypes() {
|
||||||
structField, remaining := c.collapseFormalParamInternal(subtyp, fields)
|
structField, remaining := c.collapseFormalParamInternal(subtyp, fields)
|
||||||
fields = remaining
|
fields = remaining
|
||||||
|
|||||||
+37
-49
@@ -12,16 +12,6 @@ import (
|
|||||||
|
|
||||||
// emitMakeChan returns a new channel value for the given channel type.
|
// emitMakeChan returns a new channel value for the given channel type.
|
||||||
func (c *Compiler) emitMakeChan(expr *ssa.MakeChan) (llvm.Value, error) {
|
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")
|
chanType := c.mod.GetTypeByName("runtime.channel")
|
||||||
size := c.targetData.TypeAllocSize(chanType)
|
size := c.targetData.TypeAllocSize(chanType)
|
||||||
sizeValue := llvm.ConstInt(c.uintptrType, size, false)
|
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
|
// emitChanSend emits a pseudo chan send operation. It is lowered to the actual
|
||||||
// channel send operation during goroutine lowering.
|
// channel send operation during goroutine lowering.
|
||||||
func (c *Compiler) emitChanSend(frame *Frame, instr *ssa.Send) error {
|
func (c *Compiler) emitChanSend(frame *Frame, instr *ssa.Send) {
|
||||||
valueType, err := c.getLLVMType(instr.Chan.Type().(*types.Chan).Elem())
|
valueType := c.getLLVMType(instr.X.Type())
|
||||||
if err != nil {
|
ch := c.getValue(frame, instr.Chan)
|
||||||
return err
|
chanValue := c.getValue(frame, instr.X)
|
||||||
}
|
|
||||||
ch, err := c.parseExpr(frame, instr.Chan)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
chanValue, err := c.parseExpr(frame, instr.X)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
valueSize := llvm.ConstInt(c.uintptrType, c.targetData.TypeAllocSize(chanValue.Type()), false)
|
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")
|
valueAlloca := c.builder.CreateAlloca(valueType, "chan.value")
|
||||||
|
c.builder.SetInsertPointBefore(coroutine)
|
||||||
|
c.builder.SetInsertPointAtEnd(coroutine.InstructionParent())
|
||||||
c.builder.CreateStore(chanValue, valueAlloca)
|
c.builder.CreateStore(chanValue, valueAlloca)
|
||||||
valueAllocaCast := c.builder.CreateBitCast(valueAlloca, c.i8ptrType, "chan.value.i8ptr")
|
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
|
// emitChanRecv emits a pseudo chan receive operation. It is lowered to the
|
||||||
// actual channel receive operation during goroutine lowering.
|
// actual channel receive operation during goroutine lowering.
|
||||||
func (c *Compiler) emitChanRecv(frame *Frame, unop *ssa.UnOp) (llvm.Value, error) {
|
func (c *Compiler) emitChanRecv(frame *Frame, unop *ssa.UnOp) llvm.Value {
|
||||||
valueType, err := c.getLLVMType(unop.X.Type().(*types.Chan).Elem())
|
valueType := c.getLLVMType(unop.X.Type().(*types.Chan).Elem())
|
||||||
if err != nil {
|
|
||||||
return llvm.Value{}, err
|
|
||||||
}
|
|
||||||
valueSize := llvm.ConstInt(c.uintptrType, c.targetData.TypeAllocSize(valueType), false)
|
valueSize := llvm.ConstInt(c.uintptrType, c.targetData.TypeAllocSize(valueType), false)
|
||||||
ch, err := c.parseExpr(frame, unop.X)
|
ch := c.getValue(frame, unop.X)
|
||||||
if err != nil {
|
coroutine := c.createRuntimeCall("getCoroutine", nil, "")
|
||||||
return llvm.Value{}, err
|
|
||||||
}
|
// Allocate memory to receive into.
|
||||||
|
c.builder.SetInsertPointBefore(coroutine.InstructionParent().Parent().EntryBasicBlock().FirstInstruction())
|
||||||
valueAlloca := c.builder.CreateAlloca(valueType, "chan.value")
|
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")
|
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")
|
received := c.builder.CreateLoad(valueAlloca, "chan.received")
|
||||||
if unop.CommaOk {
|
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 := llvm.Undef(c.ctx.StructType([]llvm.Type{valueType, c.ctx.Int1Type()}, false))
|
||||||
tuple = c.builder.CreateInsertValue(tuple, received, 0, "")
|
tuple = c.builder.CreateInsertValue(tuple, received, 0, "")
|
||||||
tuple = c.builder.CreateInsertValue(tuple, commaOk, 1, "")
|
tuple = c.builder.CreateInsertValue(tuple, commaOk, 1, "")
|
||||||
return tuple, nil
|
return tuple
|
||||||
} else {
|
} else {
|
||||||
return received, nil
|
return received
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// emitChanClose closes the given channel.
|
// emitChanClose closes the given channel.
|
||||||
func (c *Compiler) emitChanClose(frame *Frame, param ssa.Value) error {
|
func (c *Compiler) emitChanClose(frame *Frame, param ssa.Value) {
|
||||||
valueType, err := c.getLLVMType(param.Type().(*types.Chan).Elem())
|
valueType := c.getLLVMType(param.Type().(*types.Chan).Elem())
|
||||||
valueSize := llvm.ConstInt(c.uintptrType, c.targetData.TypeAllocSize(valueType), false)
|
valueSize := llvm.ConstInt(c.uintptrType, c.targetData.TypeAllocSize(valueType), false)
|
||||||
if err != nil {
|
ch := c.getValue(frame, param)
|
||||||
return err
|
|
||||||
}
|
|
||||||
ch, err := c.parseExpr(frame, param)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
c.createRuntimeCall("chanClose", []llvm.Value{ch, valueSize}, "")
|
c.createRuntimeCall("chanClose", []llvm.Value{ch, valueSize}, "")
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|||||||
+791
-934
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
|
// emitDefer emits a single defer instruction, to be run when this function
|
||||||
// returns.
|
// 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
|
// The pointer to the previous defer struct, which we will replace to
|
||||||
// make a linked list.
|
// make a linked list.
|
||||||
next := c.builder.CreateLoad(frame.deferPtr, "defer.next")
|
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
|
// Collect all values to be put in the struct (starting with
|
||||||
// runtime._defer fields, followed by the call parameters).
|
// runtime._defer fields, followed by the call parameters).
|
||||||
itf, err := c.parseExpr(frame, instr.Call.Value) // interface
|
itf := c.getValue(frame, instr.Call.Value) // interface
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
receiverValue := c.builder.CreateExtractValue(itf, 1, "invoke.func.receiver")
|
receiverValue := c.builder.CreateExtractValue(itf, 1, "invoke.func.receiver")
|
||||||
values = []llvm.Value{callback, next, receiverValue}
|
values = []llvm.Value{callback, next, receiverValue}
|
||||||
valueTypes = append(valueTypes, c.i8ptrType)
|
valueTypes = append(valueTypes, c.i8ptrType)
|
||||||
for _, arg := range instr.Call.Args {
|
for _, arg := range instr.Call.Args {
|
||||||
val, err := c.parseExpr(frame, arg)
|
val := c.getValue(frame, arg)
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
values = append(values, val)
|
values = append(values, val)
|
||||||
valueTypes = append(valueTypes, val.Type())
|
valueTypes = append(valueTypes, val.Type())
|
||||||
}
|
}
|
||||||
@@ -86,10 +80,7 @@ func (c *Compiler) emitDefer(frame *Frame, instr *ssa.Defer) error {
|
|||||||
// runtime._defer fields).
|
// runtime._defer fields).
|
||||||
values = []llvm.Value{callback, next}
|
values = []llvm.Value{callback, next}
|
||||||
for _, param := range instr.Call.Args {
|
for _, param := range instr.Call.Args {
|
||||||
llvmParam, err := c.parseExpr(frame, param)
|
llvmParam := c.getValue(frame, param)
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
values = append(values, llvmParam)
|
values = append(values, llvmParam)
|
||||||
valueTypes = append(valueTypes, llvmParam.Type())
|
valueTypes = append(valueTypes, llvmParam.Type())
|
||||||
}
|
}
|
||||||
@@ -101,10 +92,7 @@ func (c *Compiler) emitDefer(frame *Frame, instr *ssa.Defer) error {
|
|||||||
// pointer.
|
// pointer.
|
||||||
// TODO: ignore this closure entirely and put pointers to the free
|
// TODO: ignore this closure entirely and put pointers to the free
|
||||||
// variables directly in the defer struct, avoiding a memory allocation.
|
// variables directly in the defer struct, avoiding a memory allocation.
|
||||||
closure, err := c.parseExpr(frame, instr.Call.Value)
|
closure := c.getValue(frame, instr.Call.Value)
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
context := c.builder.CreateExtractValue(closure, 0, "")
|
context := c.builder.CreateExtractValue(closure, 0, "")
|
||||||
|
|
||||||
// Get the callback number.
|
// Get the callback number.
|
||||||
@@ -120,10 +108,7 @@ func (c *Compiler) emitDefer(frame *Frame, instr *ssa.Defer) error {
|
|||||||
// context pointer).
|
// context pointer).
|
||||||
values = []llvm.Value{callback, next}
|
values = []llvm.Value{callback, next}
|
||||||
for _, param := range instr.Call.Args {
|
for _, param := range instr.Call.Args {
|
||||||
llvmParam, err := c.parseExpr(frame, param)
|
llvmParam := c.getValue(frame, param)
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
values = append(values, llvmParam)
|
values = append(values, llvmParam)
|
||||||
valueTypes = append(valueTypes, llvmParam.Type())
|
valueTypes = append(valueTypes, llvmParam.Type())
|
||||||
}
|
}
|
||||||
@@ -131,15 +116,13 @@ func (c *Compiler) emitDefer(frame *Frame, instr *ssa.Defer) error {
|
|||||||
valueTypes = append(valueTypes, context.Type())
|
valueTypes = append(valueTypes, context.Type())
|
||||||
|
|
||||||
} else {
|
} 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.
|
// Make a struct out of the collected values to put in the defer frame.
|
||||||
deferFrameType := c.ctx.StructType(valueTypes, false)
|
deferFrameType := c.ctx.StructType(valueTypes, false)
|
||||||
deferFrame, err := c.getZeroValue(deferFrameType)
|
deferFrame := c.getZeroValue(deferFrameType)
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
for i, value := range values {
|
for i, value := range values {
|
||||||
deferFrame = c.builder.CreateInsertValue(deferFrame, value, i, "")
|
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.
|
// Push it on top of the linked list by replacing deferPtr.
|
||||||
allocaCast := c.builder.CreateBitCast(alloca, next.Type(), "defer.alloca.cast")
|
allocaCast := c.builder.CreateBitCast(alloca, next.Type(), "defer.alloca.cast")
|
||||||
c.builder.CreateStore(allocaCast, frame.deferPtr)
|
c.builder.CreateStore(allocaCast, frame.deferPtr)
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// emitRunDefers emits code to run all deferred functions.
|
// 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:
|
// Add a loop like the following:
|
||||||
// for stack != nil {
|
// for stack != nil {
|
||||||
// _stack := stack
|
// _stack := stack
|
||||||
@@ -190,13 +172,13 @@ func (c *Compiler) emitRunDefers(frame *Frame) error {
|
|||||||
// stack = stack.next
|
// stack = stack.next
|
||||||
// switch stack.callback {
|
// switch stack.callback {
|
||||||
c.builder.SetInsertPointAtEnd(loop)
|
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(), 0, false),
|
||||||
llvm.ConstInt(c.ctx.Int32Type(), 1, false), // .next field
|
llvm.ConstInt(c.ctx.Int32Type(), 1, false), // .next field
|
||||||
}, "stack.next.gep")
|
}, "stack.next.gep")
|
||||||
nextStack := c.builder.CreateLoad(nextStackGEP, "stack.next")
|
nextStack := c.builder.CreateLoad(nextStackGEP, "stack.next")
|
||||||
c.builder.CreateStore(nextStack, frame.deferPtr)
|
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),
|
||||||
llvm.ConstInt(c.ctx.Int32Type(), 0, false), // .callback field
|
llvm.ConstInt(c.ctx.Int32Type(), 0, false), // .callback field
|
||||||
}, "callback.gep")
|
}, "callback.gep")
|
||||||
@@ -220,11 +202,7 @@ func (c *Compiler) emitRunDefers(frame *Frame) error {
|
|||||||
// Get the real defer struct type and cast to it.
|
// Get the real defer struct type and cast to it.
|
||||||
valueTypes := []llvm.Type{c.uintptrType, llvm.PointerType(c.mod.GetTypeByName("runtime._defer"), 0), c.i8ptrType}
|
valueTypes := []llvm.Type{c.uintptrType, llvm.PointerType(c.mod.GetTypeByName("runtime._defer"), 0), c.i8ptrType}
|
||||||
for _, arg := range callback.Args {
|
for _, arg := range callback.Args {
|
||||||
llvmType, err := c.getLLVMType(arg.Type())
|
valueTypes = append(valueTypes, c.getLLVMType(arg.Type()))
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
valueTypes = append(valueTypes, llvmType)
|
|
||||||
}
|
}
|
||||||
deferFrameType := c.ctx.StructType(valueTypes, false)
|
deferFrameType := c.ctx.StructType(valueTypes, false)
|
||||||
deferFramePtr := c.builder.CreateBitCast(deferData, llvm.PointerType(deferFrameType, 0), "deferFrame")
|
deferFramePtr := c.builder.CreateBitCast(deferData, llvm.PointerType(deferFrameType, 0), "deferFrame")
|
||||||
@@ -233,7 +211,7 @@ func (c *Compiler) emitRunDefers(frame *Frame) error {
|
|||||||
forwardParams := []llvm.Value{}
|
forwardParams := []llvm.Value{}
|
||||||
zero := llvm.ConstInt(c.ctx.Int32Type(), 0, false)
|
zero := llvm.ConstInt(c.ctx.Int32Type(), 0, false)
|
||||||
for i := 2; i < len(valueTypes); i++ {
|
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")
|
forwardParam := c.builder.CreateLoad(gep, "param")
|
||||||
forwardParams = append(forwardParams, forwardParam)
|
forwardParams = append(forwardParams, forwardParam)
|
||||||
}
|
}
|
||||||
@@ -246,10 +224,7 @@ func (c *Compiler) emitRunDefers(frame *Frame) error {
|
|||||||
// Parent coroutine handle.
|
// Parent coroutine handle.
|
||||||
forwardParams = append(forwardParams, llvm.Undef(c.i8ptrType))
|
forwardParams = append(forwardParams, llvm.Undef(c.i8ptrType))
|
||||||
|
|
||||||
fnPtr, _, err := c.getInvokeCall(frame, callback)
|
fnPtr, _ := c.getInvokeCall(frame, callback)
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
c.createCall(fnPtr, forwardParams, "")
|
c.createCall(fnPtr, forwardParams, "")
|
||||||
|
|
||||||
case *ir.Function:
|
case *ir.Function:
|
||||||
@@ -258,11 +233,7 @@ func (c *Compiler) emitRunDefers(frame *Frame) error {
|
|||||||
// Get the real defer struct type and cast to it.
|
// Get the real defer struct type and cast to it.
|
||||||
valueTypes := []llvm.Type{c.uintptrType, llvm.PointerType(c.mod.GetTypeByName("runtime._defer"), 0)}
|
valueTypes := []llvm.Type{c.uintptrType, llvm.PointerType(c.mod.GetTypeByName("runtime._defer"), 0)}
|
||||||
for _, param := range callback.Params {
|
for _, param := range callback.Params {
|
||||||
llvmType, err := c.getLLVMType(param.Type())
|
valueTypes = append(valueTypes, c.getLLVMType(param.Type()))
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
valueTypes = append(valueTypes, llvmType)
|
|
||||||
}
|
}
|
||||||
deferFrameType := c.ctx.StructType(valueTypes, false)
|
deferFrameType := c.ctx.StructType(valueTypes, false)
|
||||||
deferFramePtr := c.builder.CreateBitCast(deferData, llvm.PointerType(deferFrameType, 0), "deferFrame")
|
deferFramePtr := c.builder.CreateBitCast(deferData, llvm.PointerType(deferFrameType, 0), "deferFrame")
|
||||||
@@ -271,7 +242,7 @@ func (c *Compiler) emitRunDefers(frame *Frame) error {
|
|||||||
forwardParams := []llvm.Value{}
|
forwardParams := []llvm.Value{}
|
||||||
zero := llvm.ConstInt(c.ctx.Int32Type(), 0, false)
|
zero := llvm.ConstInt(c.ctx.Int32Type(), 0, false)
|
||||||
for i := range callback.Params {
|
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")
|
forwardParam := c.builder.CreateLoad(gep, "param")
|
||||||
forwardParams = append(forwardParams, forwardParam)
|
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)}
|
valueTypes := []llvm.Type{c.uintptrType, llvm.PointerType(c.mod.GetTypeByName("runtime._defer"), 0)}
|
||||||
params := fn.Signature.Params()
|
params := fn.Signature.Params()
|
||||||
for i := 0; i < params.Len(); i++ {
|
for i := 0; i < params.Len(); i++ {
|
||||||
llvmType, err := c.getLLVMType(params.At(i).Type())
|
valueTypes = append(valueTypes, c.getLLVMType(params.At(i).Type()))
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
valueTypes = append(valueTypes, llvmType)
|
|
||||||
}
|
}
|
||||||
valueTypes = append(valueTypes, c.i8ptrType) // closure
|
valueTypes = append(valueTypes, c.i8ptrType) // closure
|
||||||
deferFrameType := c.ctx.StructType(valueTypes, false)
|
deferFrameType := c.ctx.StructType(valueTypes, false)
|
||||||
@@ -306,7 +273,7 @@ func (c *Compiler) emitRunDefers(frame *Frame) error {
|
|||||||
forwardParams := []llvm.Value{}
|
forwardParams := []llvm.Value{}
|
||||||
zero := llvm.ConstInt(c.ctx.Int32Type(), 0, false)
|
zero := llvm.ConstInt(c.ctx.Int32Type(), 0, false)
|
||||||
for i := 2; i < len(valueTypes); i++ {
|
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")
|
forwardParam := c.builder.CreateLoad(gep, "param")
|
||||||
forwardParams = append(forwardParams, forwardParam)
|
forwardParams = append(forwardParams, forwardParam)
|
||||||
}
|
}
|
||||||
@@ -334,5 +301,4 @@ func (c *Compiler) emitRunDefers(frame *Frame) error {
|
|||||||
|
|
||||||
// End of loop.
|
// End of loop.
|
||||||
c.builder.SetInsertPointAtEnd(end)
|
c.builder.SetInsertPointAtEnd(end)
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,3 +12,7 @@ func (c *Compiler) makeError(pos token.Pos, msg string) types.Error {
|
|||||||
Msg: msg,
|
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()
|
// go foo()
|
||||||
// time.Sleep(2 * time.Second)
|
// time.Sleep(2 * time.Second)
|
||||||
// println("some other operation")
|
// println("some other operation")
|
||||||
// bar()
|
// i := bar()
|
||||||
// println("done")
|
// println("done", *i)
|
||||||
// }
|
// }
|
||||||
//
|
//
|
||||||
// func foo() {
|
// func foo() {
|
||||||
@@ -21,9 +21,10 @@ package compiler
|
|||||||
// }
|
// }
|
||||||
// }
|
// }
|
||||||
//
|
//
|
||||||
// func bar() {
|
// func bar() *int {
|
||||||
// time.Sleep(time.Second)
|
// time.Sleep(time.Second)
|
||||||
// println("blocking operation completed)
|
// println("blocking operation completed)
|
||||||
|
// return new(int)
|
||||||
// }
|
// }
|
||||||
//
|
//
|
||||||
// It is transformed by the IR generator in compiler.go into the following
|
// It is transformed by the IR generator in compiler.go into the following
|
||||||
@@ -34,8 +35,8 @@ package compiler
|
|||||||
// fn()
|
// fn()
|
||||||
// time.Sleep(2 * time.Second)
|
// time.Sleep(2 * time.Second)
|
||||||
// println("some other operation")
|
// println("some other operation")
|
||||||
// bar() // imagine an 'await' keyword in front of this call
|
// i := bar() // imagine an 'await' keyword in front of this call
|
||||||
// println("done")
|
// println("done", *i)
|
||||||
// }
|
// }
|
||||||
//
|
//
|
||||||
// func foo() {
|
// func foo() {
|
||||||
@@ -45,9 +46,10 @@ package compiler
|
|||||||
// }
|
// }
|
||||||
// }
|
// }
|
||||||
//
|
//
|
||||||
// func bar() {
|
// func bar() *int {
|
||||||
// time.Sleep(time.Second)
|
// time.Sleep(time.Second)
|
||||||
// println("blocking operation completed)
|
// println("blocking operation completed)
|
||||||
|
// return new(int)
|
||||||
// }
|
// }
|
||||||
//
|
//
|
||||||
// The pass in this file transforms this code even further, to the following
|
// 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
|
// runtime.sleepTask(hdl, 2 * time.Second) // ask the scheduler to re-activate this coroutine at the right time
|
||||||
// llvm.suspend(hdl) // suspend point
|
// llvm.suspend(hdl) // suspend point
|
||||||
// println("some other operation")
|
// 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
|
// bar(hdl) // await, pass a continuation (hdl) to bar
|
||||||
// llvm.suspend(hdl) // suspend point, wait for the callee to re-activate
|
// 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)
|
// 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)
|
realMain.SetLinkage(llvm.InternalLinkage)
|
||||||
c.mod.NamedFunction("runtime.alloc").SetLinkage(llvm.InternalLinkage)
|
c.mod.NamedFunction("runtime.alloc").SetLinkage(llvm.InternalLinkage)
|
||||||
c.mod.NamedFunction("runtime.free").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.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)
|
c.mod.NamedFunction("runtime.scheduler").SetLinkage(llvm.InternalLinkage)
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
@@ -170,13 +173,17 @@ func (c *Compiler) markAsyncFunctions() (needsScheduler bool, err error) {
|
|||||||
if !sleep.IsNil() {
|
if !sleep.IsNil() {
|
||||||
worklist = append(worklist, sleep)
|
worklist = append(worklist, sleep)
|
||||||
}
|
}
|
||||||
chanSendStub := c.mod.NamedFunction("runtime.chanSendStub")
|
deadlockStub := c.mod.NamedFunction("runtime.deadlockStub")
|
||||||
if !chanSendStub.IsNil() {
|
if !deadlockStub.IsNil() {
|
||||||
worklist = append(worklist, chanSendStub)
|
worklist = append(worklist, deadlockStub)
|
||||||
}
|
}
|
||||||
chanRecvStub := c.mod.NamedFunction("runtime.chanRecvStub")
|
chanSend := c.mod.NamedFunction("runtime.chanSend")
|
||||||
if !chanRecvStub.IsNil() {
|
if !chanSend.IsNil() {
|
||||||
worklist = append(worklist, chanRecvStub)
|
worklist = append(worklist, chanSend)
|
||||||
|
}
|
||||||
|
chanRecv := c.mod.NamedFunction("runtime.chanRecv")
|
||||||
|
if !chanRecv.IsNil() {
|
||||||
|
worklist = append(worklist, chanRecv)
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(worklist) == 0 {
|
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)
|
coroBeginType := llvm.FunctionType(c.i8ptrType, []llvm.Type{c.ctx.TokenType(), c.i8ptrType}, false)
|
||||||
coroBeginFunc := llvm.AddFunction(c.mod, "llvm.coro.begin", coroBeginType)
|
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)
|
coroSuspendType := llvm.FunctionType(c.ctx.Int8Type(), []llvm.Type{c.ctx.TokenType(), c.ctx.Int1Type()}, false)
|
||||||
coroSuspendFunc := llvm.AddFunction(c.mod, "llvm.coro.suspend", coroSuspendType)
|
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.
|
// Transform all async functions into coroutines.
|
||||||
for _, f := range asyncList {
|
for _, f := range asyncList {
|
||||||
if f == sleep || f == chanSendStub || f == chanRecvStub {
|
if f == sleep || f == deadlockStub || f == chanSend || f == chanRecv {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -305,7 +309,7 @@ func (c *Compiler) markAsyncFunctions() (needsScheduler bool, err error) {
|
|||||||
for inst := bb.FirstInstruction(); !inst.IsNil(); inst = llvm.NextInstruction(inst) {
|
for inst := bb.FirstInstruction(); !inst.IsNil(); inst = llvm.NextInstruction(inst) {
|
||||||
if !inst.IsACallInst().IsNil() {
|
if !inst.IsACallInst().IsNil() {
|
||||||
callee := inst.CalledValue()
|
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
|
continue
|
||||||
}
|
}
|
||||||
asyncCalls = append(asyncCalls, inst)
|
asyncCalls = append(asyncCalls, inst)
|
||||||
@@ -343,10 +347,18 @@ func (c *Compiler) markAsyncFunctions() (needsScheduler bool, err error) {
|
|||||||
// Split this basic block.
|
// Split this basic block.
|
||||||
await := c.splitBasicBlock(inst, llvm.NextBasicBlock(c.builder.GetInsertBlock()), "task.await")
|
await := c.splitBasicBlock(inst, llvm.NextBasicBlock(c.builder.GetInsertBlock()), "task.await")
|
||||||
|
|
||||||
// Set task state to TASK_STATE_CALL.
|
// Allocate space for the return value.
|
||||||
c.builder.SetInsertPointAtEnd(inst.InstructionParent())
|
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.
|
// Suspend.
|
||||||
|
c.builder.SetInsertPointAtEnd(inst.InstructionParent())
|
||||||
continuePoint := c.builder.CreateCall(coroSuspendFunc, []llvm.Value{
|
continuePoint := c.builder.CreateCall(coroSuspendFunc, []llvm.Value{
|
||||||
llvm.ConstNull(c.ctx.TokenType()),
|
llvm.ConstNull(c.ctx.TokenType()),
|
||||||
llvm.ConstInt(c.ctx.Int1Type(), 0, false),
|
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 := c.builder.CreateSwitch(continuePoint, frame.suspendBlock, 2)
|
||||||
sw.AddCase(llvm.ConstInt(c.ctx.Int8Type(), 0, false), await)
|
sw.AddCase(llvm.ConstInt(c.ctx.Int8Type(), 0, false), await)
|
||||||
sw.AddCase(llvm.ConstInt(c.ctx.Int8Type(), 1, false), frame.cleanupBlock)
|
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
|
// Replace return instructions with suspend points that should
|
||||||
// reactivate the parent coroutine.
|
// reactivate the parent coroutine.
|
||||||
for _, inst := range returns {
|
for _, inst := range returns {
|
||||||
if inst.OperandsCount() == 0 {
|
// These properties were added by the functionattrs pass. Remove
|
||||||
// These properties were added by the functionattrs pass.
|
// them, because now we start using the parameter.
|
||||||
// Remove them, because now we start using the parameter.
|
// https://llvm.org/docs/Passes.html#functionattrs-deduce-function-attributes
|
||||||
// https://llvm.org/docs/Passes.html#functionattrs-deduce-function-attributes
|
for _, kind := range []string{"nocapture", "readnone"} {
|
||||||
for _, kind := range []string{"nocapture", "readnone"} {
|
kindID := llvm.AttributeKindID(kind)
|
||||||
kindID := llvm.AttributeKindID(kind)
|
f.RemoveEnumAttributeAtIndex(f.ParamsCount(), kindID)
|
||||||
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")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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.
|
// Coroutine cleanup. Free resources associated with this coroutine.
|
||||||
@@ -416,6 +447,14 @@ func (c *Compiler) markAsyncFunctions() (needsScheduler bool, err error) {
|
|||||||
c.builder.CreateUnreachable()
|
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.
|
// Transform calls to time.Sleep() into coroutine suspend points.
|
||||||
for _, sleepCall := range getUses(sleep) {
|
for _, sleepCall := range getUses(sleep) {
|
||||||
// sleepCall must be a call instruction.
|
// sleepCall must be a call instruction.
|
||||||
@@ -439,37 +478,31 @@ func (c *Compiler) markAsyncFunctions() (needsScheduler bool, err error) {
|
|||||||
sleepCall.EraseFromParentAsInstruction()
|
sleepCall.EraseFromParentAsInstruction()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Transform calls to runtime.chanSendStub into channel send operations.
|
// Transform calls to runtime.deadlockStub into coroutine suspends (without
|
||||||
for _, sendOp := range getUses(chanSendStub) {
|
// 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.
|
// sendOp must be a call instruction.
|
||||||
frame := asyncFuncs[sendOp.InstructionParent().Parent()]
|
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.
|
// Yield to scheduler.
|
||||||
c.builder.SetInsertPointBefore(llvm.NextInstruction(sendOp))
|
c.builder.SetInsertPointBefore(llvm.NextInstruction(sendOp))
|
||||||
continuePoint := c.builder.CreateCall(coroSuspendFunc, []llvm.Value{
|
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)
|
sw.AddCase(llvm.ConstInt(c.ctx.Int8Type(), 1, false), frame.cleanupBlock)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Transform calls to runtime.chanRecvStub into channel receive operations.
|
// Transform calls to runtime.chanRecv into channel receive operations.
|
||||||
for _, recvOp := range getUses(chanRecvStub) {
|
for _, recvOp := range getUses(chanRecv) {
|
||||||
// recvOp must be a call instruction.
|
// recvOp must be a call instruction.
|
||||||
frame := asyncFuncs[recvOp.InstructionParent().Parent()]
|
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.
|
// Yield to scheduler.
|
||||||
c.builder.SetInsertPointBefore(llvm.NextInstruction(recvOp))
|
c.builder.SetInsertPointBefore(llvm.NextInstruction(recvOp))
|
||||||
continuePoint := c.builder.CreateCall(coroSuspendFunc, []llvm.Value{
|
continuePoint := c.builder.CreateCall(coroSuspendFunc, []llvm.Value{
|
||||||
@@ -508,32 +531,6 @@ func (c *Compiler) markAsyncFunctions() (needsScheduler bool, err error) {
|
|||||||
c.builder.SetInsertPointAtEnd(recvOp.InstructionParent())
|
c.builder.SetInsertPointAtEnd(recvOp.InstructionParent())
|
||||||
sw.AddCase(llvm.ConstInt(c.ctx.Int8Type(), 0, false), wakeup)
|
sw.AddCase(llvm.ConstInt(c.ctx.Int8Type(), 0, false), wakeup)
|
||||||
sw.AddCase(llvm.ConstInt(c.ctx.Int8Type(), 1, false), frame.cleanupBlock)
|
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()
|
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
|
||||||
|
}
|
||||||
@@ -4,7 +4,6 @@ package compiler
|
|||||||
// form, optimizing them in the process.
|
// form, optimizing them in the process.
|
||||||
//
|
//
|
||||||
// During SSA construction, the following pseudo-calls are created:
|
// During SSA construction, the following pseudo-calls are created:
|
||||||
// runtime.makeInterface(typecode, methodSet)
|
|
||||||
// runtime.typeAssert(typecode, assertedType)
|
// runtime.typeAssert(typecode, assertedType)
|
||||||
// runtime.interfaceImplements(typecode, interfaceMethodSet)
|
// runtime.interfaceImplements(typecode, interfaceMethodSet)
|
||||||
// runtime.interfaceMethod(typecode, interfaceMethodSet, signature)
|
// runtime.interfaceMethod(typecode, interfaceMethodSet, signature)
|
||||||
@@ -14,16 +13,13 @@ package compiler
|
|||||||
//
|
//
|
||||||
// This pass lowers the above functions to their final form:
|
// This pass lowers the above functions to their final form:
|
||||||
//
|
//
|
||||||
// makeInterface:
|
|
||||||
// Replaced with a constant typecode.
|
|
||||||
//
|
|
||||||
// typeAssert:
|
// typeAssert:
|
||||||
// Replaced with an icmp instruction so it can be directly used in a type
|
// 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
|
// switch. This is very easy to optimize for LLVM: it will often translate a
|
||||||
// type switch into a regular switch statement.
|
// type switch into a regular switch statement.
|
||||||
// When this type assert is not possible (the type is never used in an
|
// When this type assert is not possible (the type is never used in an
|
||||||
// interface with makeInterface), this call is replaced with a constant
|
// interface), this call is replaced with a constant false to optimize the
|
||||||
// false to optimize the type assert away completely.
|
// type assert away completely.
|
||||||
//
|
//
|
||||||
// interfaceImplements:
|
// interfaceImplements:
|
||||||
// This call is translated into a call that checks whether the underlying
|
// This call is translated into a call that checks whether the underlying
|
||||||
@@ -166,25 +162,36 @@ func (c *Compiler) LowerInterfaces() {
|
|||||||
|
|
||||||
// run runs the pass itself.
|
// run runs the pass itself.
|
||||||
func (p *lowerInterfacesPass) run() {
|
func (p *lowerInterfacesPass) run() {
|
||||||
// Count per type how often it is put in an interface. Also, collect all
|
// Collect all type codes.
|
||||||
// methods this type has (if it is named).
|
typecodeIDPtr := llvm.PointerType(p.mod.GetTypeByName("runtime.typecodeID"), 0)
|
||||||
makeInterface := p.mod.NamedFunction("runtime.makeInterface")
|
typeInInterfacePtr := llvm.PointerType(p.mod.GetTypeByName("runtime.typeInInterface"), 0)
|
||||||
makeInterfaceUses := getUses(makeInterface)
|
var typesInInterfaces []llvm.Value
|
||||||
for _, use := range makeInterfaceUses {
|
for global := p.mod.FirstGlobal(); !global.IsNil(); global = llvm.NextGlobal(global) {
|
||||||
typecode := use.Operand(0)
|
switch global.Type() {
|
||||||
name := typecode.Name()
|
case typecodeIDPtr:
|
||||||
if t, ok := p.types[name]; !ok {
|
// Retrieve Go type information based on an opaque global variable.
|
||||||
// This is the first time this type has been seen, add it to the
|
// Only the name of the global is relevant, the object itself is
|
||||||
// list of types.
|
// discarded afterwards.
|
||||||
t = p.addType(typecode)
|
name := global.Name()
|
||||||
p.addTypeMethods(t, use.Operand(1))
|
t := &typeInfo{
|
||||||
} else {
|
name: name,
|
||||||
p.addTypeMethods(t, use.Operand(1))
|
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
|
// Count the number of MakeInterface instructions, for sorting the
|
||||||
// typecodes later.
|
// typecodes later.
|
||||||
p.types[name].countMakeInterfaces++
|
t.countMakeInterfaces += len(getUses(global))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Count per type how often it is type asserted on (e.g. in a switch
|
// Count per type how often it is type asserted on (e.g. in a switch
|
||||||
@@ -194,9 +201,6 @@ func (p *lowerInterfacesPass) run() {
|
|||||||
for _, use := range typeAssertUses {
|
for _, use := range typeAssertUses {
|
||||||
typecode := use.Operand(1)
|
typecode := use.Operand(1)
|
||||||
name := typecode.Name()
|
name := typecode.Name()
|
||||||
if _, ok := p.types[name]; !ok {
|
|
||||||
p.addType(typecode)
|
|
||||||
}
|
|
||||||
p.types[name].countTypeAsserts++
|
p.types[name].countTypeAsserts++
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -286,16 +290,6 @@ func (p *lowerInterfacesPass) run() {
|
|||||||
typecode := use.Operand(0)
|
typecode := use.Operand(0)
|
||||||
signature := p.signatures[use.Operand(2).Name()]
|
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
|
methodSet := use.Operand(1).Operand(0) // global variable
|
||||||
itf := p.interfaces[methodSet.Name()]
|
itf := p.interfaces[methodSet.Name()]
|
||||||
if len(itf.types) == 0 {
|
if len(itf.types) == 0 {
|
||||||
@@ -356,20 +350,6 @@ func (p *lowerInterfacesPass) run() {
|
|||||||
// types, if possible.
|
// types, if possible.
|
||||||
for _, use := range interfaceImplementsUses {
|
for _, use := range interfaceImplementsUses {
|
||||||
actualType := use.Operand(0)
|
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
|
methodSet := use.Operand(1).Operand(0) // global variable
|
||||||
itf := p.interfaces[methodSet.Name()]
|
itf := p.interfaces[methodSet.Name()]
|
||||||
@@ -416,12 +396,14 @@ func (p *lowerInterfacesPass) run() {
|
|||||||
// Assign a type code for each type.
|
// Assign a type code for each type.
|
||||||
p.assignTypeCodes(typeSlice)
|
p.assignTypeCodes(typeSlice)
|
||||||
|
|
||||||
// Replace each call to runtime.makeInterface with the constant type code.
|
// Replace each use of a runtime.typeInInterface with the constant type
|
||||||
for _, use := range makeInterfaceUses {
|
// code.
|
||||||
global := use.Operand(0)
|
for _, global := range typesInInterfaces {
|
||||||
t := p.types[global.Name()]
|
for _, use := range getUses(global) {
|
||||||
use.ReplaceAllUsesWith(llvm.ConstPtrToInt(t.typecode, p.uintptrType))
|
t := p.types[llvm.ConstExtractValue(global.Initializer(), []uint32{0}).Name()]
|
||||||
use.EraseFromParentAsInstruction()
|
typecode := llvm.ConstInt(p.uintptrType, t.num, false)
|
||||||
|
use.ReplaceAllUsesWith(typecode)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Replace each type assert with an actual type comparison or (if the type
|
// Replace each type assert with an actual type comparison or (if the type
|
||||||
@@ -458,12 +440,18 @@ func (p *lowerInterfacesPass) run() {
|
|||||||
// numbers.
|
// numbers.
|
||||||
for _, typ := range p.types {
|
for _, typ := range p.types {
|
||||||
for _, use := range getUses(typ.typecode) {
|
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))
|
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
|
// Remove method sets of types. Unnecessary, but cleans up the IR for
|
||||||
// inspection.
|
// inspection.
|
||||||
for _, typ := range p.types {
|
for _, typ := range p.types {
|
||||||
@@ -474,19 +462,6 @@ func (p *lowerInterfacesPass) run() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// addType retrieves Go type information based on a i16 global variable.
|
|
||||||
// Only the name of the i16 is relevant, the object itself is const-propagated
|
|
||||||
// and discared afterwards.
|
|
||||||
func (p *lowerInterfacesPass) addType(typecode llvm.Value) *typeInfo {
|
|
||||||
name := typecode.Name()
|
|
||||||
t := &typeInfo{
|
|
||||||
name: name,
|
|
||||||
typecode: typecode,
|
|
||||||
}
|
|
||||||
p.types[name] = t
|
|
||||||
return t
|
|
||||||
}
|
|
||||||
|
|
||||||
// addTypeMethods reads the method set of the given type info struct. It
|
// addTypeMethods reads the method set of the given type info struct. It
|
||||||
// retrieves the signatures and the references to the method functions
|
// retrieves the signatures and the references to the method functions
|
||||||
// themselves for later type<->interface matching.
|
// themselves for later type<->interface matching.
|
||||||
@@ -552,11 +527,40 @@ func (p *lowerInterfacesPass) replaceInvokeWithCall(use llvm.Value, typ *typeInf
|
|||||||
}
|
}
|
||||||
inttoptr := inttoptrs[0]
|
inttoptr := inttoptrs[0]
|
||||||
function := typ.getMethod(signature).function
|
function := typ.getMethod(signature).function
|
||||||
if inttoptr.Type() != function.Type() {
|
if inttoptr.Type() == function.Type() {
|
||||||
p.builder.SetInsertPointBefore(use)
|
// Easy case: the types are the same. Simply replace the inttoptr
|
||||||
function = p.builder.CreateBitCast(function, inttoptr.Type(), "")
|
// 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()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
inttoptr.ReplaceAllUsesWith(function)
|
|
||||||
inttoptr.EraseFromParentAsInstruction()
|
inttoptr.EraseFromParentAsInstruction()
|
||||||
use.EraseFromParentAsInstruction()
|
use.EraseFromParentAsInstruction()
|
||||||
}
|
}
|
||||||
|
|||||||
+42
-145
@@ -22,48 +22,23 @@ import (
|
|||||||
// value field.
|
// value field.
|
||||||
//
|
//
|
||||||
// An interface value is a {typecode, value} tuple, or {i16, i8*} to be exact.
|
// An interface value is a {typecode, value} tuple, or {i16, i8*} to be exact.
|
||||||
func (c *Compiler) parseMakeInterface(val llvm.Value, typ types.Type, pos token.Pos) (llvm.Value, error) {
|
func (c *Compiler) parseMakeInterface(val llvm.Value, typ types.Type, pos token.Pos) llvm.Value {
|
||||||
var itfValue llvm.Value
|
itfValue := c.emitPointerPack([]llvm.Value{val})
|
||||||
size := c.targetData.TypeAllocSize(val.Type())
|
|
||||||
if size > c.targetData.TypeAllocSize(c.i8ptrType) {
|
|
||||||
// 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, llvm.FloatTypeKind, llvm.DoubleTypeKind:
|
|
||||||
// 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*")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
itfTypeCodeGlobal := c.getTypeCode(typ)
|
itfTypeCodeGlobal := c.getTypeCode(typ)
|
||||||
itfMethodSetGlobal, err := c.getTypeMethodSet(typ)
|
itfMethodSetGlobal := c.getTypeMethodSet(typ)
|
||||||
if err != nil {
|
itfConcreteTypeGlobal := c.mod.NamedGlobal("typeInInterface:" + itfTypeCodeGlobal.Name())
|
||||||
return llvm.Value{}, nil
|
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 := llvm.Undef(c.mod.GetTypeByName("runtime._interface"))
|
||||||
itf = c.builder.CreateInsertValue(itf, itfTypeCode, 0, "")
|
itf = c.builder.CreateInsertValue(itf, itfTypeCode, 0, "")
|
||||||
itf = c.builder.CreateInsertValue(itf, itfValue, 1, "")
|
itf = c.builder.CreateInsertValue(itf, itfValue, 1, "")
|
||||||
return itf, nil
|
return itf
|
||||||
}
|
}
|
||||||
|
|
||||||
// getTypeCode returns a reference to a type code.
|
// getTypeCode returns a reference to a type code.
|
||||||
@@ -73,7 +48,7 @@ func (c *Compiler) getTypeCode(typ types.Type) llvm.Value {
|
|||||||
globalName := "type:" + getTypeCodeName(typ)
|
globalName := "type:" + getTypeCodeName(typ)
|
||||||
global := c.mod.NamedGlobal(globalName)
|
global := c.mod.NamedGlobal(globalName)
|
||||||
if global.IsNil() {
|
if global.IsNil() {
|
||||||
global = llvm.AddGlobal(c.mod, c.ctx.Int8Type(), globalName)
|
global = llvm.AddGlobal(c.mod, c.mod.GetTypeByName("runtime.typecodeID"), globalName)
|
||||||
global.SetGlobalConstant(true)
|
global.SetGlobalConstant(true)
|
||||||
}
|
}
|
||||||
return global
|
return global
|
||||||
@@ -162,6 +137,10 @@ func getTypeCodeName(t types.Type) string {
|
|||||||
return "slice:" + name + getTypeCodeName(t.Elem())
|
return "slice:" + name + getTypeCodeName(t.Elem())
|
||||||
case *types.Struct:
|
case *types.Struct:
|
||||||
elems := make([]string, t.NumFields())
|
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++ {
|
for i := 0; i < t.NumFields(); i++ {
|
||||||
elems[i] = getTypeCodeName(t.Field(i).Type())
|
elems[i] = getTypeCodeName(t.Field(i).Type())
|
||||||
}
|
}
|
||||||
@@ -173,18 +152,18 @@ func getTypeCodeName(t types.Type) string {
|
|||||||
|
|
||||||
// getTypeMethodSet returns a reference (GEP) to a global method set. This
|
// getTypeMethodSet returns a reference (GEP) to a global method set. This
|
||||||
// method set should be unreferenced after the interface lowering pass.
|
// 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")
|
global := c.mod.NamedGlobal(typ.String() + "$methodset")
|
||||||
zero := llvm.ConstInt(c.ctx.Int32Type(), 0, false)
|
zero := llvm.ConstInt(c.ctx.Int32Type(), 0, false)
|
||||||
if !global.IsNil() {
|
if !global.IsNil() {
|
||||||
// the method set already exists
|
// 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)
|
ms := c.ir.Program.MethodSets.MethodSet(typ)
|
||||||
if ms.Len() == 0 {
|
if ms.Len() == 0 {
|
||||||
// no methods, so can leave that one out
|
// 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())
|
methods := make([]llvm.Value, ms.Len())
|
||||||
@@ -197,10 +176,7 @@ func (c *Compiler) getTypeMethodSet(typ types.Type) (llvm.Value, error) {
|
|||||||
// compiler error, so panic
|
// compiler error, so panic
|
||||||
panic("cannot find function: " + f.LinkName())
|
panic("cannot find function: " + f.LinkName())
|
||||||
}
|
}
|
||||||
fn, err := c.getInterfaceInvokeWrapper(f)
|
fn := c.getInterfaceInvokeWrapper(f)
|
||||||
if err != nil {
|
|
||||||
return llvm.Value{}, err
|
|
||||||
}
|
|
||||||
methodInfo := llvm.ConstNamedStruct(interfaceMethodInfoType, []llvm.Value{
|
methodInfo := llvm.ConstNamedStruct(interfaceMethodInfoType, []llvm.Value{
|
||||||
signatureGlobal,
|
signatureGlobal,
|
||||||
llvm.ConstPtrToInt(fn, c.uintptrType),
|
llvm.ConstPtrToInt(fn, c.uintptrType),
|
||||||
@@ -213,7 +189,7 @@ func (c *Compiler) getTypeMethodSet(typ types.Type) (llvm.Value, error) {
|
|||||||
global.SetInitializer(value)
|
global.SetInitializer(value)
|
||||||
global.SetGlobalConstant(true)
|
global.SetGlobalConstant(true)
|
||||||
global.SetLinkage(llvm.PrivateLinkage)
|
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
|
// getInterfaceMethodSet returns a global variable with the method set of the
|
||||||
@@ -262,19 +238,9 @@ func (c *Compiler) getMethodSignature(method *types.Func) llvm.Value {
|
|||||||
//
|
//
|
||||||
// Type asserts on concrete types are trivial: just compare type numbers. Type
|
// Type asserts on concrete types are trivial: just compare type numbers. Type
|
||||||
// asserts on interfaces are more difficult, see the comments in the function.
|
// asserts on interfaces are more difficult, see the comments in the function.
|
||||||
func (c *Compiler) parseTypeAssert(frame *Frame, expr *ssa.TypeAssert) (llvm.Value, error) {
|
func (c *Compiler) parseTypeAssert(frame *Frame, expr *ssa.TypeAssert) llvm.Value {
|
||||||
itf, err := c.parseExpr(frame, expr.X)
|
itf := c.getValue(frame, expr.X)
|
||||||
if err != nil {
|
assertedType := c.getLLVMType(expr.AssertedType)
|
||||||
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
|
|
||||||
}
|
|
||||||
|
|
||||||
actualTypeNum := c.builder.CreateExtractValue(itf, 0, "interface.type")
|
actualTypeNum := c.builder.CreateExtractValue(itf, 0, "interface.type")
|
||||||
commaOk := llvm.Value{}
|
commaOk := llvm.Value{}
|
||||||
@@ -327,71 +293,35 @@ func (c *Compiler) parseTypeAssert(frame *Frame, expr *ssa.TypeAssert) (llvm.Val
|
|||||||
// Type assert on concrete type. Extract the underlying type from
|
// Type assert on concrete type. Extract the underlying type from
|
||||||
// the interface (but only after checking it matches).
|
// the interface (but only after checking it matches).
|
||||||
valuePtr := c.builder.CreateExtractValue(itf, 1, "typeassert.value.ptr")
|
valuePtr := c.builder.CreateExtractValue(itf, 1, "typeassert.value.ptr")
|
||||||
size := c.targetData.TypeAllocSize(assertedType)
|
valueOk = c.emitPointerUnpack(valuePtr, []llvm.Type{assertedType})[0]
|
||||||
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")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
c.builder.CreateBr(nextBlock)
|
c.builder.CreateBr(nextBlock)
|
||||||
|
|
||||||
// Continue after the if statement.
|
// Continue after the if statement.
|
||||||
c.builder.SetInsertPointAtEnd(nextBlock)
|
c.builder.SetInsertPointAtEnd(nextBlock)
|
||||||
phi := c.builder.CreatePHI(assertedType, "typeassert.value")
|
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 {
|
if expr.CommaOk {
|
||||||
tuple := c.ctx.ConstStruct([]llvm.Value{llvm.Undef(assertedType), llvm.Undef(c.ctx.Int1Type())}, false) // create empty tuple
|
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, phi, 0, "") // insert value
|
||||||
tuple = c.builder.CreateInsertValue(tuple, commaOk, 1, "") // insert 'comma ok' boolean
|
tuple = c.builder.CreateInsertValue(tuple, commaOk, 1, "") // insert 'comma ok' boolean
|
||||||
return tuple, nil
|
return tuple
|
||||||
} else {
|
} else {
|
||||||
// This is kind of dirty as the branch above becomes mostly useless,
|
// This is kind of dirty as the branch above becomes mostly useless,
|
||||||
// but hopefully this gets optimized away.
|
// but hopefully this gets optimized away.
|
||||||
c.createRuntimeCall("interfaceTypeAssert", []llvm.Value{commaOk}, "")
|
c.createRuntimeCall("interfaceTypeAssert", []llvm.Value{commaOk}, "")
|
||||||
return phi, nil
|
return phi
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// getInvokeCall creates and returns the function pointer and parameters of an
|
// getInvokeCall creates and returns the function pointer and parameters of an
|
||||||
// interface call. It can be used in a call or defer instruction.
|
// 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.
|
// Call an interface method with dynamic dispatch.
|
||||||
itf, err := c.parseExpr(frame, instr.Value) // interface
|
itf := c.getValue(frame, instr.Value) // interface
|
||||||
if err != nil {
|
|
||||||
return llvm.Value{}, nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
llvmFnType, err := c.getLLVMType(instr.Method.Type())
|
llvmFnType := c.getRawFuncType(instr.Method.Type().(*types.Signature))
|
||||||
if err != nil {
|
|
||||||
return llvm.Value{}, nil, err
|
|
||||||
}
|
|
||||||
// getLLVMType() has created a closure type for us, but we don't actually
|
|
||||||
// want a closure type as an interface call can never be a closure call. So
|
|
||||||
// extract the function pointer type from the closure.
|
|
||||||
llvmFnType = llvmFnType.Subtypes()[1]
|
|
||||||
|
|
||||||
typecode := c.builder.CreateExtractValue(itf, 0, "invoke.typecode")
|
typecode := c.builder.CreateExtractValue(itf, 0, "invoke.typecode")
|
||||||
values := []llvm.Value{
|
values := []llvm.Value{
|
||||||
@@ -405,11 +335,7 @@ func (c *Compiler) getInvokeCall(frame *Frame, instr *ssa.CallCommon) (llvm.Valu
|
|||||||
|
|
||||||
args := []llvm.Value{receiverValue}
|
args := []llvm.Value{receiverValue}
|
||||||
for _, arg := range instr.Args {
|
for _, arg := range instr.Args {
|
||||||
val, err := c.parseExpr(frame, arg)
|
args = append(args, c.getValue(frame, arg))
|
||||||
if err != nil {
|
|
||||||
return llvm.Value{}, nil, err
|
|
||||||
}
|
|
||||||
args = append(args, val)
|
|
||||||
}
|
}
|
||||||
// Add the context parameter. An interface call never takes a context but we
|
// Add the context parameter. An interface call never takes a context but we
|
||||||
// have to supply the parameter anyway.
|
// have to supply the parameter anyway.
|
||||||
@@ -417,7 +343,7 @@ func (c *Compiler) getInvokeCall(frame *Frame, instr *ssa.CallCommon) (llvm.Valu
|
|||||||
// Add the parent goroutine handle.
|
// Add the parent goroutine handle.
|
||||||
args = append(args, llvm.Undef(c.i8ptrType))
|
args = append(args, llvm.Undef(c.i8ptrType))
|
||||||
|
|
||||||
return fnCast, args, nil
|
return fnCast, args
|
||||||
}
|
}
|
||||||
|
|
||||||
// interfaceInvokeWrapper keeps some state between getInterfaceInvokeWrapper and
|
// interfaceInvokeWrapper keeps some state between getInterfaceInvokeWrapper and
|
||||||
@@ -433,19 +359,16 @@ type interfaceInvokeWrapper struct {
|
|||||||
// the underlying value, dereferences it, and calls the real method. This
|
// 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
|
// wrapper is only needed when the interface value actually doesn't fit in a
|
||||||
// pointer and a pointer to the value must be created.
|
// 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"
|
wrapperName := f.LinkName() + "$invoke"
|
||||||
wrapper := c.mod.NamedFunction(wrapperName)
|
wrapper := c.mod.NamedFunction(wrapperName)
|
||||||
if !wrapper.IsNil() {
|
if !wrapper.IsNil() {
|
||||||
// Wrapper already created. Return it directly.
|
// Wrapper already created. Return it directly.
|
||||||
return wrapper, nil
|
return wrapper
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get the expanded receiver type.
|
// Get the expanded receiver type.
|
||||||
receiverType, err := c.getLLVMType(f.Params[0].Type())
|
receiverType := c.getLLVMType(f.Params[0].Type())
|
||||||
if err != nil {
|
|
||||||
return llvm.Value{}, err
|
|
||||||
}
|
|
||||||
expandedReceiverType := c.expandFormalParamType(receiverType)
|
expandedReceiverType := c.expandFormalParamType(receiverType)
|
||||||
|
|
||||||
// Does this method even need any wrapping?
|
// Does this method even need any wrapping?
|
||||||
@@ -454,7 +377,7 @@ func (c *Compiler) getInterfaceInvokeWrapper(f *ir.Function) (llvm.Value, error)
|
|||||||
// Casting a function signature to a different signature and calling it
|
// Casting a function signature to a different signature and calling it
|
||||||
// with a receiver pointer bitcasted to *i8 (as done in calls on an
|
// with a receiver pointer bitcasted to *i8 (as done in calls on an
|
||||||
// interface) is hopefully a safe (defined) operation.
|
// interface) is hopefully a safe (defined) operation.
|
||||||
return f.LLVMFn, nil
|
return f.LLVMFn
|
||||||
}
|
}
|
||||||
|
|
||||||
// create wrapper function
|
// create wrapper function
|
||||||
@@ -467,12 +390,12 @@ func (c *Compiler) getInterfaceInvokeWrapper(f *ir.Function) (llvm.Value, error)
|
|||||||
wrapper: wrapper,
|
wrapper: wrapper,
|
||||||
receiverType: receiverType,
|
receiverType: receiverType,
|
||||||
})
|
})
|
||||||
return wrapper, nil
|
return wrapper
|
||||||
}
|
}
|
||||||
|
|
||||||
// createInterfaceInvokeWrapper finishes the work of getInterfaceInvokeWrapper,
|
// createInterfaceInvokeWrapper finishes the work of getInterfaceInvokeWrapper,
|
||||||
// see that function for details.
|
// see that function for details.
|
||||||
func (c *Compiler) createInterfaceInvokeWrapper(state interfaceInvokeWrapper) error {
|
func (c *Compiler) createInterfaceInvokeWrapper(state interfaceInvokeWrapper) {
|
||||||
wrapper := state.wrapper
|
wrapper := state.wrapper
|
||||||
fn := state.fn
|
fn := state.fn
|
||||||
receiverType := state.receiverType
|
receiverType := state.receiverType
|
||||||
@@ -482,10 +405,7 @@ func (c *Compiler) createInterfaceInvokeWrapper(state interfaceInvokeWrapper) er
|
|||||||
// add debug info if needed
|
// add debug info if needed
|
||||||
if c.Debug {
|
if c.Debug {
|
||||||
pos := c.ir.Program.Fset.Position(fn.Pos())
|
pos := c.ir.Program.Fset.Position(fn.Pos())
|
||||||
difunc, err := c.attachDebugInfoRaw(fn, wrapper, "$invoke", pos.Filename, pos.Line)
|
difunc := c.attachDebugInfoRaw(fn, wrapper, "$invoke", pos.Filename, pos.Line)
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
c.builder.SetCurrentDebugLocation(uint(pos.Line), uint(pos.Column), difunc, llvm.Metadata{})
|
c.builder.SetCurrentDebugLocation(uint(pos.Line), uint(pos.Column), difunc, llvm.Metadata{})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -493,28 +413,7 @@ func (c *Compiler) createInterfaceInvokeWrapper(state interfaceInvokeWrapper) er
|
|||||||
block := c.ctx.AddBasicBlock(wrapper, "entry")
|
block := c.ctx.AddBasicBlock(wrapper, "entry")
|
||||||
c.builder.SetInsertPointAtEnd(block)
|
c.builder.SetInsertPointAtEnd(block)
|
||||||
|
|
||||||
var receiverPtr llvm.Value
|
receiverValue := c.emitPointerUnpack(wrapper.Param(0), []llvm.Type{receiverType})[0]
|
||||||
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")
|
|
||||||
params := append(c.expandFormalParam(receiverValue), wrapper.Params()[1:]...)
|
params := append(c.expandFormalParam(receiverValue), wrapper.Params()[1:]...)
|
||||||
if fn.LLVMFn.Type().ElementType().ReturnType().TypeKind() == llvm.VoidTypeKind {
|
if fn.LLVMFn.Type().ElementType().ReturnType().TypeKind() == llvm.VoidTypeKind {
|
||||||
c.builder.CreateCall(fn.LLVMFn, params, "")
|
c.builder.CreateCall(fn.LLVMFn, params, "")
|
||||||
@@ -523,6 +422,4 @@ func (c *Compiler) createInterfaceInvokeWrapper(state interfaceInvokeWrapper) er
|
|||||||
ret := c.builder.CreateCall(fn.LLVMFn, params, "ret")
|
ret := c.builder.CreateCall(fn.LLVMFn, params, "ret")
|
||||||
c.builder.CreateRet(ret)
|
c.builder.CreateRet(ret)
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,6 +22,47 @@ func getUses(value llvm.Value) []llvm.Value {
|
|||||||
return uses
|
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
|
// splitBasicBlock splits a LLVM basic block into two parts. All instructions
|
||||||
// after afterInst are moved into a new basic block (created right after the
|
// after afterInst are moved into a new basic block (created right after the
|
||||||
// current one) with the given name.
|
// 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) {
|
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)
|
llvmValueType := c.getLLVMType(valueType)
|
||||||
if err != nil {
|
|
||||||
return llvm.Value{}, err
|
// 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
|
||||||
mapValueAlloca := c.builder.CreateAlloca(llvmValueType, "hashmap.value")
|
// present in the map.
|
||||||
mapValuePtr := c.builder.CreateBitCast(mapValueAlloca, c.i8ptrType, "hashmap.valueptr")
|
mapValueAlloca, mapValuePtr, mapValueSize := c.createEntryBlockAlloca(llvmValueType, "hashmap.value")
|
||||||
|
|
||||||
|
// Do the lookup. How it is done depends on the key type.
|
||||||
var commaOkValue llvm.Value
|
var commaOkValue llvm.Value
|
||||||
if t, ok := keyType.(*types.Basic); ok && t.Info()&types.IsString != 0 {
|
if t, ok := keyType.(*types.Basic); ok && t.Info()&types.IsString != 0 {
|
||||||
// key is a string
|
// 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, "")
|
commaOkValue = c.createRuntimeCall("hashmapStringGet", params, "")
|
||||||
} else if hashmapIsBinaryKey(keyType) {
|
} else if hashmapIsBinaryKey(keyType) {
|
||||||
// key can be compared with runtime.memequal
|
// key can be compared with runtime.memequal
|
||||||
keyAlloca := c.builder.CreateAlloca(key.Type(), "hashmap.key")
|
// Store the key in an alloca, in the entry block to avoid dynamic stack
|
||||||
c.builder.CreateStore(key, keyAlloca)
|
// growth.
|
||||||
keyPtr := c.builder.CreateBitCast(keyAlloca, c.i8ptrType, "hashmap.keyptr")
|
mapKeyAlloca, mapKeyPtr, mapKeySize := c.createEntryBlockAlloca(key.Type(), "hashmap.key")
|
||||||
params := []llvm.Value{m, keyPtr, mapValuePtr}
|
c.builder.CreateStore(key, mapKeyAlloca)
|
||||||
|
// Fetch the value from the hashmap.
|
||||||
|
params := []llvm.Value{m, mapKeyPtr, mapValuePtr}
|
||||||
commaOkValue = c.createRuntimeCall("hashmapBinaryGet", params, "")
|
commaOkValue = c.createRuntimeCall("hashmapBinaryGet", params, "")
|
||||||
|
c.builder.CreateCall(c.getLifetimeEndFunc(), []llvm.Value{mapKeySize, mapKeyPtr}, "")
|
||||||
} else {
|
} else {
|
||||||
|
// Not trivially comparable using memcmp.
|
||||||
return llvm.Value{}, c.makeError(pos, "only strings, bools, ints or structs of bools/ints are supported as map keys, but got: "+keyType.String())
|
return llvm.Value{}, c.makeError(pos, "only strings, bools, ints 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, "")
|
mapValue := c.builder.CreateLoad(mapValueAlloca, "")
|
||||||
|
c.builder.CreateCall(c.getLifetimeEndFunc(), []llvm.Value{mapValueSize, mapValuePtr}, "")
|
||||||
|
|
||||||
if commaOk {
|
if commaOk {
|
||||||
tuple := llvm.Undef(c.ctx.StructType([]llvm.Type{llvmValueType, c.ctx.Int1Type()}, false))
|
tuple := llvm.Undef(c.ctx.StructType([]llvm.Type{llvmValueType, c.ctx.Int1Type()}, false))
|
||||||
tuple = c.builder.CreateInsertValue(tuple, mapValue, 0, "")
|
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 {
|
func (c *Compiler) emitMapUpdate(keyType types.Type, m, key, value llvm.Value, pos token.Pos) {
|
||||||
valueAlloca := c.builder.CreateAlloca(value.Type(), "hashmap.value")
|
valueAlloca, valuePtr, valueSize := c.createEntryBlockAlloca(value.Type(), "hashmap.value")
|
||||||
c.builder.CreateStore(value, valueAlloca)
|
c.builder.CreateStore(value, valueAlloca)
|
||||||
valuePtr := c.builder.CreateBitCast(valueAlloca, c.i8ptrType, "hashmap.valueptr")
|
|
||||||
keyType = keyType.Underlying()
|
keyType = keyType.Underlying()
|
||||||
if t, ok := keyType.(*types.Basic); ok && t.Info()&types.IsString != 0 {
|
if t, ok := keyType.(*types.Basic); ok && t.Info()&types.IsString != 0 {
|
||||||
// key is a string
|
// key is a string
|
||||||
params := []llvm.Value{m, key, valuePtr}
|
params := []llvm.Value{m, key, valuePtr}
|
||||||
c.createRuntimeCall("hashmapStringSet", params, "")
|
c.createRuntimeCall("hashmapStringSet", params, "")
|
||||||
return nil
|
|
||||||
} else if hashmapIsBinaryKey(keyType) {
|
} else if hashmapIsBinaryKey(keyType) {
|
||||||
// key can be compared with runtime.memequal
|
// 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)
|
c.builder.CreateStore(key, keyAlloca)
|
||||||
keyPtr := c.builder.CreateBitCast(keyAlloca, c.i8ptrType, "hashmap.keyptr")
|
|
||||||
params := []llvm.Value{m, keyPtr, valuePtr}
|
params := []llvm.Value{m, keyPtr, valuePtr}
|
||||||
c.createRuntimeCall("hashmapBinarySet", params, "")
|
c.createRuntimeCall("hashmapBinarySet", params, "")
|
||||||
return nil
|
c.builder.CreateCall(c.getLifetimeEndFunc(), []llvm.Value{keySize, keyPtr}, "")
|
||||||
} else {
|
} 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 {
|
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, "")
|
c.createRuntimeCall("hashmapStringDelete", params, "")
|
||||||
return nil
|
return nil
|
||||||
} else if hashmapIsBinaryKey(keyType) {
|
} 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)
|
c.builder.CreateStore(key, keyAlloca)
|
||||||
keyPtr := c.builder.CreateBitCast(keyAlloca, c.i8ptrType, "hashmap.keyptr")
|
|
||||||
params := []llvm.Value{m, keyPtr}
|
params := []llvm.Value{m, keyPtr}
|
||||||
c.createRuntimeCall("hashmapBinaryDelete", params, "")
|
c.createRuntimeCall("hashmapBinaryDelete", params, "")
|
||||||
|
c.builder.CreateCall(c.getLifetimeEndFunc(), []llvm.Value{keySize, keyPtr}, "")
|
||||||
return nil
|
return nil
|
||||||
} else {
|
} else {
|
||||||
return c.makeError(pos, "only strings, bools, ints or structs of bools/ints are supported as map keys, but got: "+keyType.String())
|
return c.makeError(pos, "only strings, bools, ints 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()
|
builder.AddCoroutinePassesToExtensionPoints()
|
||||||
|
|
||||||
|
if c.PanicStrategy == "trap" {
|
||||||
|
c.replacePanicsWithTrap() // -panic=trap
|
||||||
|
}
|
||||||
|
|
||||||
// Run function passes for each function.
|
// Run function passes for each function.
|
||||||
funcPasses := llvm.NewFunctionPassManagerForModule(c.mod)
|
funcPasses := llvm.NewFunctionPassManagerForModule(c.mod)
|
||||||
defer funcPasses.Dispose()
|
defer funcPasses.Dispose()
|
||||||
@@ -33,6 +37,7 @@ func (c *Compiler) Optimize(optLevel, sizeLevel int, inlinerThreshold uint) erro
|
|||||||
goPasses := llvm.NewPassManager()
|
goPasses := llvm.NewPassManager()
|
||||||
defer goPasses.Dispose()
|
defer goPasses.Dispose()
|
||||||
goPasses.AddGlobalOptimizerPass()
|
goPasses.AddGlobalOptimizerPass()
|
||||||
|
goPasses.AddGlobalDCEPass()
|
||||||
goPasses.AddConstantPropagationPass()
|
goPasses.AddConstantPropagationPass()
|
||||||
goPasses.AddAggressiveDCEPass()
|
goPasses.AddAggressiveDCEPass()
|
||||||
goPasses.AddFunctionAttrsPass()
|
goPasses.AddFunctionAttrsPass()
|
||||||
@@ -43,6 +48,7 @@ func (c *Compiler) Optimize(optLevel, sizeLevel int, inlinerThreshold uint) erro
|
|||||||
c.OptimizeStringToBytes()
|
c.OptimizeStringToBytes()
|
||||||
c.OptimizeAllocs()
|
c.OptimizeAllocs()
|
||||||
c.LowerInterfaces()
|
c.LowerInterfaces()
|
||||||
|
c.LowerFuncValues()
|
||||||
|
|
||||||
// After interfaces are lowered, there are many more opportunities for
|
// After interfaces are lowered, there are many more opportunities for
|
||||||
// interprocedural optimizations. To get them to work, function
|
// 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.OptimizeAllocs()
|
||||||
c.OptimizeStringToBytes()
|
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()
|
err := c.LowerGoroutines()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -60,6 +82,7 @@ func (c *Compiler) Optimize(optLevel, sizeLevel int, inlinerThreshold uint) erro
|
|||||||
} else {
|
} else {
|
||||||
// Must be run at any optimization level.
|
// Must be run at any optimization level.
|
||||||
c.LowerInterfaces()
|
c.LowerInterfaces()
|
||||||
|
c.LowerFuncValues()
|
||||||
err := c.LowerGoroutines()
|
err := c.LowerGoroutines()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -92,9 +115,35 @@ func (c *Compiler) Optimize(optLevel, sizeLevel int, inlinerThreshold uint) erro
|
|||||||
builder.Populate(modPasses)
|
builder.Populate(modPasses)
|
||||||
modPasses.Run(c.mod)
|
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
|
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.
|
// Eliminate created but not used maps.
|
||||||
//
|
//
|
||||||
// In the future, this should statically allocate created but never modified
|
// 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)
|
sizeInWords := (size + uint64(alignment) - 1) / uint64(alignment)
|
||||||
allocaType := llvm.ArrayType(c.ctx.IntType(alignment*8), int(sizeInWords))
|
allocaType := llvm.ArrayType(c.ctx.IntType(alignment*8), int(sizeInWords))
|
||||||
alloca := c.builder.CreateAlloca(allocaType, "stackalloc.alloca")
|
alloca := c.builder.CreateAlloca(allocaType, "stackalloc.alloca")
|
||||||
zero, _ := c.getZeroValue(alloca.Type().ElementType())
|
zero := c.getZeroValue(alloca.Type().ElementType())
|
||||||
c.builder.CreateStore(zero, alloca)
|
c.builder.CreateStore(zero, alloca)
|
||||||
stackalloc := c.builder.CreateBitCast(alloca, bitcast.Type(), "stackalloc")
|
stackalloc := c.builder.CreateBitCast(alloca, bitcast.Type(), "stackalloc")
|
||||||
bitcast.ReplaceAllUsesWith(stackalloc)
|
bitcast.ReplaceAllUsesWith(stackalloc)
|
||||||
@@ -266,21 +315,12 @@ func (c *Compiler) doesEscape(value llvm.Value) bool {
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
} else if use.IsACallInst() != nilValue {
|
} 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") {
|
if !c.hasFlag(use, value, "nocapture") {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
} else if use.IsAICmpInst() != nilValue {
|
||||||
|
// Comparing pointers don't let the pointer escape.
|
||||||
|
// This is often a compiler-inserted nil check.
|
||||||
} else {
|
} else {
|
||||||
// Unknown instruction, might escape.
|
// Unknown instruction, might escape.
|
||||||
return true
|
return true
|
||||||
|
|||||||
+39
-3
@@ -63,6 +63,12 @@ func (s *StdSizes) Alignof(T types.Type) int64 {
|
|||||||
|
|
||||||
func (s *StdSizes) Offsetsof(fields []*types.Var) []int64 {
|
func (s *StdSizes) Offsetsof(fields []*types.Var) []int64 {
|
||||||
offsets := make([]int64, len(fields))
|
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
|
var o int64
|
||||||
for i, f := range fields {
|
for i, f := range fields {
|
||||||
a := s.Alignof(f.Type())
|
a := s.Alignof(f.Type())
|
||||||
@@ -107,6 +113,9 @@ func (s *StdSizes) Sizeof(T types.Type) int64 {
|
|||||||
if k == types.Uintptr {
|
if k == types.Uintptr {
|
||||||
return s.PtrSize
|
return s.PtrSize
|
||||||
}
|
}
|
||||||
|
if k == types.UnsafePointer {
|
||||||
|
return s.PtrSize
|
||||||
|
}
|
||||||
panic("unknown basic type: " + t.String())
|
panic("unknown basic type: " + t.String())
|
||||||
case *types.Array:
|
case *types.Array:
|
||||||
n := t.Len()
|
n := t.Len()
|
||||||
@@ -125,11 +134,38 @@ func (s *StdSizes) Sizeof(T types.Type) int64 {
|
|||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
fields := make([]*types.Var, t.NumFields())
|
fields := make([]*types.Var, t.NumFields())
|
||||||
|
maxAlign := int64(1)
|
||||||
for i := range fields {
|
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:
|
case *types.Interface:
|
||||||
return s.PtrSize * 2
|
return s.PtrSize * 2
|
||||||
case *types.Pointer:
|
case *types.Pointer:
|
||||||
|
|||||||
+3
-12
@@ -51,10 +51,7 @@ func (c *Compiler) emitSyscall(frame *Frame, call *ssa.CallCommon) (llvm.Value,
|
|||||||
"{r12}",
|
"{r12}",
|
||||||
"{r13}",
|
"{r13}",
|
||||||
}[i]
|
}[i]
|
||||||
llvmValue, err := c.parseExpr(frame, arg)
|
llvmValue := c.getValue(frame, arg)
|
||||||
if err != nil {
|
|
||||||
return llvm.Value{}, err
|
|
||||||
}
|
|
||||||
args = append(args, llvmValue)
|
args = append(args, llvmValue)
|
||||||
argTypes = append(argTypes, llvmValue.Type())
|
argTypes = append(argTypes, llvmValue.Type())
|
||||||
}
|
}
|
||||||
@@ -80,10 +77,7 @@ func (c *Compiler) emitSyscall(frame *Frame, call *ssa.CallCommon) (llvm.Value,
|
|||||||
"{r5}",
|
"{r5}",
|
||||||
"{r6}",
|
"{r6}",
|
||||||
}[i]
|
}[i]
|
||||||
llvmValue, err := c.parseExpr(frame, arg)
|
llvmValue := c.getValue(frame, arg)
|
||||||
if err != nil {
|
|
||||||
return llvm.Value{}, err
|
|
||||||
}
|
|
||||||
args = append(args, llvmValue)
|
args = append(args, llvmValue)
|
||||||
argTypes = append(argTypes, llvmValue.Type())
|
argTypes = append(argTypes, llvmValue.Type())
|
||||||
}
|
}
|
||||||
@@ -113,10 +107,7 @@ func (c *Compiler) emitSyscall(frame *Frame, call *ssa.CallCommon) (llvm.Value,
|
|||||||
"{x4}",
|
"{x4}",
|
||||||
"{x5}",
|
"{x5}",
|
||||||
}[i]
|
}[i]
|
||||||
llvmValue, err := c.parseExpr(frame, arg)
|
llvmValue := c.getValue(frame, arg)
|
||||||
if err != nil {
|
|
||||||
return llvm.Value{}, err
|
|
||||||
}
|
|
||||||
args = append(args, llvmValue)
|
args = append(args, llvmValue)
|
||||||
argTypes = append(argTypes, llvmValue.Type())
|
argTypes = append(argTypes, llvmValue.Type())
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
|
}
|
||||||
+43
-4
@@ -95,7 +95,7 @@ func (fr *frame) evalBasicBlock(bb, incoming llvm.BasicBlock, indent string) (re
|
|||||||
case !inst.IsALoadInst().IsNil():
|
case !inst.IsALoadInst().IsNil():
|
||||||
operand := fr.getLocal(inst.Operand(0)).(*LocalValue)
|
operand := fr.getLocal(inst.Operand(0)).(*LocalValue)
|
||||||
var value llvm.Value
|
var value llvm.Value
|
||||||
if !operand.IsConstant() || inst.IsVolatile() || operand.Underlying.Opcode() == llvm.BitCast {
|
if !operand.IsConstant() || inst.IsVolatile() || (!operand.Underlying.IsAConstantExpr().IsNil() && operand.Underlying.Opcode() == llvm.BitCast) {
|
||||||
value = fr.builder.CreateLoad(operand.Value(), inst.Name())
|
value = fr.builder.CreateLoad(operand.Value(), inst.Name())
|
||||||
} else {
|
} else {
|
||||||
value = operand.Load()
|
value = operand.Load()
|
||||||
@@ -307,9 +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{1}) // len
|
||||||
ret = llvm.ConstInsertValue(ret, retLen, []uint32{2}) // cap
|
ret = llvm.ConstInsertValue(ret, retLen, []uint32{2}) // cap
|
||||||
fr.locals[inst] = &LocalValue{fr.Eval, ret}
|
fr.locals[inst] = &LocalValue{fr.Eval, ret}
|
||||||
case callee.Name() == "runtime.makeInterface":
|
case callee.Name() == "runtime.interfaceImplements":
|
||||||
uintptrType := callee.Type().Context().IntType(fr.TargetData.PointerSize() * 8)
|
typecode := fr.getLocal(inst.Operand(0)).(*LocalValue).Underlying
|
||||||
fr.locals[inst] = &LocalValue{fr.Eval, llvm.ConstPtrToInt(inst.Operand(0), uintptrType)}
|
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":
|
case strings.HasPrefix(callee.Name(), "runtime.print") || callee.Name() == "runtime._panic":
|
||||||
// This are all print instructions, which necessarily have side
|
// This are all print instructions, which necessarily have side
|
||||||
// effects but no results.
|
// effects but no results.
|
||||||
|
|||||||
@@ -18,7 +18,6 @@ type Eval struct {
|
|||||||
TargetData llvm.TargetData
|
TargetData llvm.TargetData
|
||||||
Debug bool
|
Debug bool
|
||||||
builder llvm.Builder
|
builder llvm.Builder
|
||||||
dibuilder *llvm.DIBuilder
|
|
||||||
dirtyGlobals map[llvm.Value]struct{}
|
dirtyGlobals map[llvm.Value]struct{}
|
||||||
sideEffectFuncs map[llvm.Value]*sideEffectResult // cache of side effect scan results
|
sideEffectFuncs map[llvm.Value]*sideEffectResult // cache of side effect scan results
|
||||||
}
|
}
|
||||||
@@ -38,7 +37,6 @@ func Run(mod llvm.Module, targetData llvm.TargetData, debug bool) error {
|
|||||||
dirtyGlobals: map[llvm.Value]struct{}{},
|
dirtyGlobals: map[llvm.Value]struct{}{},
|
||||||
}
|
}
|
||||||
e.builder = mod.Context().NewBuilder()
|
e.builder = mod.Context().NewBuilder()
|
||||||
e.dibuilder = llvm.NewDIBuilder(mod)
|
|
||||||
|
|
||||||
initAll := mod.NamedFunction(name)
|
initAll := mod.NamedFunction(name)
|
||||||
bb := initAll.EntryBasicBlock()
|
bb := initAll.EntryBasicBlock()
|
||||||
@@ -49,7 +47,6 @@ func Run(mod llvm.Module, targetData llvm.TargetData, debug bool) error {
|
|||||||
e.builder.SetInsertPointBefore(bb.FirstInstruction())
|
e.builder.SetInsertPointBefore(bb.FirstInstruction())
|
||||||
dummy := e.builder.CreateAlloca(e.Mod.Context().Int8Type(), "dummy")
|
dummy := e.builder.CreateAlloca(e.Mod.Context().Int8Type(), "dummy")
|
||||||
e.builder.SetInsertPointBefore(dummy)
|
e.builder.SetInsertPointBefore(dummy)
|
||||||
e.builder.SetInstDebugLocation(bb.FirstInstruction())
|
|
||||||
var initCalls []llvm.Value
|
var initCalls []llvm.Value
|
||||||
for inst := bb.FirstInstruction(); !inst.IsNil(); inst = llvm.NextInstruction(inst) {
|
for inst := bb.FirstInstruction(); !inst.IsNil(); inst = llvm.NextInstruction(inst) {
|
||||||
if inst == dummy {
|
if inst == dummy {
|
||||||
|
|||||||
+7
-5
@@ -28,8 +28,15 @@ func (e *Eval) hasSideEffects(fn llvm.Value) *sideEffectResult {
|
|||||||
case "runtime.alloc":
|
case "runtime.alloc":
|
||||||
// Cannot be scanned but can be interpreted.
|
// Cannot be scanned but can be interpreted.
|
||||||
return &sideEffectResult{severity: sideEffectNone}
|
return &sideEffectResult{severity: sideEffectNone}
|
||||||
|
case "runtime.nanotime":
|
||||||
|
// Fixed value at compile time.
|
||||||
|
return &sideEffectResult{severity: sideEffectNone}
|
||||||
case "runtime._panic":
|
case "runtime._panic":
|
||||||
return &sideEffectResult{severity: sideEffectLimited}
|
return &sideEffectResult{severity: sideEffectLimited}
|
||||||
|
case "runtime.interfaceImplements":
|
||||||
|
return &sideEffectResult{severity: sideEffectNone}
|
||||||
|
case "llvm.dbg.value":
|
||||||
|
return &sideEffectResult{severity: sideEffectNone}
|
||||||
}
|
}
|
||||||
if e.sideEffectFuncs == nil {
|
if e.sideEffectFuncs == nil {
|
||||||
e.sideEffectFuncs = make(map[llvm.Value]*sideEffectResult)
|
e.sideEffectFuncs = make(map[llvm.Value]*sideEffectResult)
|
||||||
@@ -81,11 +88,6 @@ func (e *Eval) hasSideEffects(fn llvm.Value) *sideEffectResult {
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if child.IsDeclaration() {
|
if child.IsDeclaration() {
|
||||||
switch child.Name() {
|
|
||||||
case "runtime.makeInterface":
|
|
||||||
// Can be interpreted so does not have side effects.
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
// External function call. Assume only limited side effects
|
// External function call. Assume only limited side effects
|
||||||
// (no affected globals, etc.).
|
// (no affected globals, etc.).
|
||||||
if e.hasLocalSideEffects(dirtyLocals, inst) {
|
if e.hasLocalSideEffects(dirtyLocals, inst) {
|
||||||
|
|||||||
@@ -33,11 +33,12 @@ type Program struct {
|
|||||||
type Function struct {
|
type Function struct {
|
||||||
*ssa.Function
|
*ssa.Function
|
||||||
LLVMFn llvm.Value
|
LLVMFn llvm.Value
|
||||||
linkName string // go:linkname, go:export, go:interrupt
|
linkName string // go:linkname, go:export, go:interrupt
|
||||||
exported bool // go:export
|
exported bool // go:export
|
||||||
nobounds bool // go:nobounds
|
nobounds bool // go:nobounds
|
||||||
flag bool // used by dead code elimination
|
flag bool // used by dead code elimination
|
||||||
interrupt bool // go:interrupt
|
interrupt bool // go:interrupt
|
||||||
|
inline InlineType // go:inline
|
||||||
}
|
}
|
||||||
|
|
||||||
// Global variable, possibly constant.
|
// Global variable, possibly constant.
|
||||||
@@ -69,7 +70,22 @@ type Interface struct {
|
|||||||
Type *types.Interface
|
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 {
|
func NewProgram(lprogram *loader.Program, mainPath string) *Program {
|
||||||
comments := map[string]*ast.CommentGroup{}
|
comments := map[string]*ast.CommentGroup{}
|
||||||
for _, pkgInfo := range lprogram.Sorted() {
|
for _, pkgInfo := range lprogram.Sorted() {
|
||||||
@@ -279,6 +295,8 @@ func (f *Function) parsePragmas() {
|
|||||||
}
|
}
|
||||||
f.linkName = parts[1]
|
f.linkName = parts[1]
|
||||||
f.exported = true
|
f.exported = true
|
||||||
|
case "//go:inline":
|
||||||
|
f.inline = InlineHint
|
||||||
case "//go:interrupt":
|
case "//go:interrupt":
|
||||||
if len(parts) != 2 {
|
if len(parts) != 2 {
|
||||||
continue
|
continue
|
||||||
@@ -332,6 +350,11 @@ func (f *Function) IsInterrupt() bool {
|
|||||||
return f.interrupt
|
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.
|
// Return the link name for this function.
|
||||||
func (f *Function) LinkName() string {
|
func (f *Function) LinkName() string {
|
||||||
if f.linkName != "" {
|
if f.linkName != "" {
|
||||||
|
|||||||
+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.
|
// Link invokes a linker with the given name and flags.
|
||||||
//
|
//
|
||||||
// This version uses the built-in linker when trying to use lld.
|
// 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 {
|
switch linker {
|
||||||
case "ld.lld", commands["ld.lld"]:
|
case "ld.lld":
|
||||||
flags = append([]string{"tinygo:" + linker}, flags...)
|
flags = append([]string{"tinygo:" + linker}, flags...)
|
||||||
var cflag *C.char
|
var cflag *C.char
|
||||||
buf := C.calloc(C.size_t(len(flags)), C.size_t(unsafe.Sizeof(cflag)))
|
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 errors.New("failed to link using built-in ld.lld")
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
case "wasm-ld", commands["wasm-ld"]:
|
case "wasm-ld":
|
||||||
flags = append([]string{"tinygo:" + linker}, flags...)
|
flags = append([]string{"tinygo:" + linker}, flags...)
|
||||||
var cflag *C.char
|
var cflag *C.char
|
||||||
buf := C.calloc(C.size_t(len(flags)), C.size_t(unsafe.Sizeof(cflag)))
|
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
|
return nil
|
||||||
default:
|
default:
|
||||||
// Fall back to external command.
|
// Fall back to external command.
|
||||||
|
if cmdNames, ok := commands[linker]; ok {
|
||||||
|
return execCommand(cmdNames, flags...)
|
||||||
|
}
|
||||||
cmd := exec.Command(linker, flags...)
|
cmd := exec.Command(linker, flags...)
|
||||||
cmd.Stdout = os.Stdout
|
cmd.Stdout = os.Stdout
|
||||||
cmd.Stderr = os.Stderr
|
cmd.Stderr = os.Stderr
|
||||||
cmd.Dir = dir
|
|
||||||
return cmd.Run()
|
return cmd.Run()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+4
-2
@@ -13,10 +13,12 @@ import (
|
|||||||
// Link invokes a linker with the given name and arguments.
|
// Link invokes a linker with the given name and arguments.
|
||||||
//
|
//
|
||||||
// This version always runs the linker as an external command.
|
// 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 := exec.Command(linker, flags...)
|
||||||
cmd.Stdout = os.Stdout
|
cmd.Stdout = os.Stdout
|
||||||
cmd.Stderr = os.Stderr
|
cmd.Stderr = os.Stderr
|
||||||
cmd.Dir = dir
|
|
||||||
return cmd.Run()
|
return cmd.Run()
|
||||||
}
|
}
|
||||||
|
|||||||
-414
@@ -1,414 +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"
|
|
||||||
|
|
||||||
"golang.org/x/tools/go/ast/astutil"
|
|
||||||
)
|
|
||||||
|
|
||||||
// fileInfo holds all Cgo-related information of a given *ast.File.
|
|
||||||
type fileInfo struct {
|
|
||||||
*ast.File
|
|
||||||
filename string
|
|
||||||
functions map[string]*functionInfo
|
|
||||||
globals map[string]*globalInfo
|
|
||||||
typedefs map[string]*typedefInfo
|
|
||||||
importCPos token.Pos
|
|
||||||
}
|
|
||||||
|
|
||||||
// functionInfo stores some information about a Cgo function found by libclang
|
|
||||||
// and declared in the AST.
|
|
||||||
type functionInfo struct {
|
|
||||||
args []paramInfo
|
|
||||||
results *ast.FieldList
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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
|
|
||||||
}
|
|
||||||
|
|
||||||
// globalInfo contains information about a declared global variable in C.
|
|
||||||
type globalInfo struct {
|
|
||||||
typeExpr ast.Expr
|
|
||||||
}
|
|
||||||
|
|
||||||
// cgoAliases list type aliases between Go and C, for types that are equivalent
|
|
||||||
// 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,
|
|
||||||
functions: map[string]*functionInfo{},
|
|
||||||
globals: map[string]*globalInfo{},
|
|
||||||
typedefs: map[string]*typedefInfo{},
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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()
|
|
||||||
|
|
||||||
// Declare stub function pointer values found by libclang.
|
|
||||||
info.addFuncPtrDecls()
|
|
||||||
|
|
||||||
// Declare globals found by libclang.
|
|
||||||
info.addVarDecls()
|
|
||||||
|
|
||||||
// Forward C types to Go types (like C.uint32_t -> uint32).
|
|
||||||
info.addTypeAliases()
|
|
||||||
|
|
||||||
// Add type declarations for C types, declared using typeef in C.
|
|
||||||
info.addTypedefs()
|
|
||||||
|
|
||||||
// Patch the AST to use the declared types and functions.
|
|
||||||
f = astutil.Apply(f, info.walker, nil).(*ast.File)
|
|
||||||
|
|
||||||
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.
|
|
||||||
names := make([]string, 0, len(info.functions))
|
|
||||||
for name := range info.functions {
|
|
||||||
names = append(names, name)
|
|
||||||
}
|
|
||||||
sort.Strings(names)
|
|
||||||
for _, name := range names {
|
|
||||||
fn := info.functions[name]
|
|
||||||
obj := &ast.Object{
|
|
||||||
Kind: ast.Fun,
|
|
||||||
Name: "C." + name,
|
|
||||||
}
|
|
||||||
args := make([]*ast.Field, len(fn.args))
|
|
||||||
decl := &ast.FuncDecl{
|
|
||||||
Name: &ast.Ident{
|
|
||||||
NamePos: info.importCPos,
|
|
||||||
Name: "C." + name,
|
|
||||||
Obj: obj,
|
|
||||||
},
|
|
||||||
Type: &ast.FuncType{
|
|
||||||
Func: info.importCPos,
|
|
||||||
Params: &ast.FieldList{
|
|
||||||
Opening: info.importCPos,
|
|
||||||
List: args,
|
|
||||||
Closing: info.importCPos,
|
|
||||||
},
|
|
||||||
Results: fn.results,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
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: arg.name,
|
|
||||||
Decl: decl,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
Type: arg.typeExpr,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
info.Decls = append(info.Decls, decl)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// addFuncPtrDecls creates stub declarations of function pointer values. These
|
|
||||||
// values will later be replaced with the real values in the compiler.
|
|
||||||
// It adds code like the following to the AST:
|
|
||||||
//
|
|
||||||
// var (
|
|
||||||
// C.add unsafe.Pointer
|
|
||||||
// C.mul unsafe.Pointer
|
|
||||||
// // ...
|
|
||||||
// )
|
|
||||||
func (info *fileInfo) addFuncPtrDecls() {
|
|
||||||
gen := &ast.GenDecl{
|
|
||||||
TokPos: info.importCPos,
|
|
||||||
Tok: token.VAR,
|
|
||||||
Lparen: info.importCPos,
|
|
||||||
Rparen: info.importCPos,
|
|
||||||
}
|
|
||||||
names := make([]string, 0, len(info.functions))
|
|
||||||
for name := range info.functions {
|
|
||||||
names = append(names, name)
|
|
||||||
}
|
|
||||||
sort.Strings(names)
|
|
||||||
for _, name := range names {
|
|
||||||
obj := &ast.Object{
|
|
||||||
Kind: ast.Typ,
|
|
||||||
Name: "C." + name + "$funcaddr",
|
|
||||||
}
|
|
||||||
valueSpec := &ast.ValueSpec{
|
|
||||||
Names: []*ast.Ident{&ast.Ident{
|
|
||||||
NamePos: info.importCPos,
|
|
||||||
Name: "C." + name + "$funcaddr",
|
|
||||||
Obj: obj,
|
|
||||||
}},
|
|
||||||
Type: &ast.SelectorExpr{
|
|
||||||
X: &ast.Ident{
|
|
||||||
NamePos: info.importCPos,
|
|
||||||
Name: "unsafe",
|
|
||||||
},
|
|
||||||
Sel: &ast.Ident{
|
|
||||||
NamePos: info.importCPos,
|
|
||||||
Name: "Pointer",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
obj.Decl = valueSpec
|
|
||||||
gen.Specs = append(gen.Specs, valueSpec)
|
|
||||||
}
|
|
||||||
info.Decls = append(info.Decls, gen)
|
|
||||||
}
|
|
||||||
|
|
||||||
// addVarDecls declares external C globals in the Go source.
|
|
||||||
// It adds code like the following to the AST:
|
|
||||||
//
|
|
||||||
// var (
|
|
||||||
// C.globalInt int
|
|
||||||
// C.globalBool bool
|
|
||||||
// // ...
|
|
||||||
// )
|
|
||||||
func (info *fileInfo) addVarDecls() {
|
|
||||||
gen := &ast.GenDecl{
|
|
||||||
TokPos: info.importCPos,
|
|
||||||
Tok: token.VAR,
|
|
||||||
Lparen: info.importCPos,
|
|
||||||
Rparen: info.importCPos,
|
|
||||||
}
|
|
||||||
names := make([]string, 0, len(info.globals))
|
|
||||||
for name := range info.globals {
|
|
||||||
names = append(names, name)
|
|
||||||
}
|
|
||||||
sort.Strings(names)
|
|
||||||
for _, name := range names {
|
|
||||||
global := info.globals[name]
|
|
||||||
obj := &ast.Object{
|
|
||||||
Kind: ast.Typ,
|
|
||||||
Name: "C." + name,
|
|
||||||
}
|
|
||||||
valueSpec := &ast.ValueSpec{
|
|
||||||
Names: []*ast.Ident{&ast.Ident{
|
|
||||||
NamePos: info.importCPos,
|
|
||||||
Name: "C." + name,
|
|
||||||
Obj: obj,
|
|
||||||
}},
|
|
||||||
Type: global.typeExpr,
|
|
||||||
}
|
|
||||||
obj.Decl = valueSpec
|
|
||||||
gen.Specs = append(gen.Specs, valueSpec)
|
|
||||||
}
|
|
||||||
info.Decls = append(info.Decls, gen)
|
|
||||||
}
|
|
||||||
|
|
||||||
// addTypeAliases aliases some built-in Go types with their equivalent C types.
|
|
||||||
// It adds code like the following to the AST:
|
|
||||||
//
|
|
||||||
// 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,
|
|
||||||
}
|
|
||||||
names := make([]string, 0, len(info.typedefs))
|
|
||||||
for name := range info.typedefs {
|
|
||||||
names = append(names, name)
|
|
||||||
}
|
|
||||||
sort.Strings(names)
|
|
||||||
for _, name := range names {
|
|
||||||
typedef := info.typedefs[name]
|
|
||||||
typeName := "C." + name
|
|
||||||
if strings.HasPrefix(name, "_Cgo_") {
|
|
||||||
typeName = "C." + name[len("_Cgo_"):]
|
|
||||||
}
|
|
||||||
if _, 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: info.importCPos,
|
|
||||||
Name: typeName,
|
|
||||||
Obj: obj,
|
|
||||||
},
|
|
||||||
Type: typedef.typeExpr,
|
|
||||||
}
|
|
||||||
obj.Decl = typeSpec
|
|
||||||
gen.Specs = append(gen.Specs, typeSpec)
|
|
||||||
}
|
|
||||||
info.Decls = append(info.Decls, gen)
|
|
||||||
}
|
|
||||||
|
|
||||||
// walker replaces all "C".<something> expressions to literal "C.<something>"
|
|
||||||
// expressions. Such expressions are impossible to write in Go (a dot cannot be
|
|
||||||
// used in the middle of a name) so in practice all C identifiers live in a
|
|
||||||
// separate namespace (no _Cgo_ hacks like in gc).
|
|
||||||
func (info *fileInfo) walker(cursor *astutil.Cursor) bool {
|
|
||||||
switch node := cursor.Node().(type) {
|
|
||||||
case *ast.CallExpr:
|
|
||||||
fun, ok := node.Fun.(*ast.SelectorExpr)
|
|
||||||
if !ok {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
x, ok := fun.X.(*ast.Ident)
|
|
||||||
if !ok {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
if _, ok := info.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 := info.functions[node.Sel.Name]; ok {
|
|
||||||
name += "$funcaddr"
|
|
||||||
}
|
|
||||||
cursor.Replace(&ast.Ident{
|
|
||||||
NamePos: x.NamePos,
|
|
||||||
Name: name,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
@@ -1,250 +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"
|
|
||||||
"go/ast"
|
|
||||||
"go/token"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
"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 := int(C.clang_Cursor_getNumArguments(c))
|
|
||||||
fn := &functionInfo{}
|
|
||||||
info.functions[name] = fn
|
|
||||||
for i := 0; i < numArgs; i++ {
|
|
||||||
arg := C.clang_Cursor_getArgument(c, C.uint(i))
|
|
||||||
argName := getString(C.clang_getCursorSpelling(arg))
|
|
||||||
argType := C.clang_getArgType(cursorType, C.uint(i))
|
|
||||||
if argName == "" {
|
|
||||||
argName = "$" + strconv.Itoa(i)
|
|
||||||
}
|
|
||||||
fn.args = append(fn.args, paramInfo{
|
|
||||||
name: argName,
|
|
||||||
typeExpr: info.makeASTType(argType),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
resultType := C.clang_getCursorResultType(c)
|
|
||||||
if resultType.kind != C.CXType_Void {
|
|
||||||
fn.results = &ast.FieldList{
|
|
||||||
List: []*ast.Field{
|
|
||||||
&ast.Field{
|
|
||||||
Type: info.makeASTType(resultType),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
case C.CXCursor_TypedefDecl:
|
|
||||||
typedefType := C.clang_getCursorType(c)
|
|
||||||
name := getString(C.clang_getTypedefName(typedefType))
|
|
||||||
underlyingType := C.clang_getTypedefDeclUnderlyingType(c)
|
|
||||||
expr := info.makeASTType(underlyingType)
|
|
||||||
if strings.HasPrefix(name, "_Cgo_") {
|
|
||||||
expr := expr.(*ast.Ident)
|
|
||||||
typeSize := C.clang_Type_getSizeOf(underlyingType)
|
|
||||||
switch expr.Name {
|
|
||||||
// TODO: plain char (may be signed or unsigned)
|
|
||||||
case "C.schar", "C.short", "C.int", "C.long", "C.longlong":
|
|
||||||
switch typeSize {
|
|
||||||
case 1:
|
|
||||||
expr.Name = "int8"
|
|
||||||
case 2:
|
|
||||||
expr.Name = "int16"
|
|
||||||
case 4:
|
|
||||||
expr.Name = "int32"
|
|
||||||
case 8:
|
|
||||||
expr.Name = "int64"
|
|
||||||
}
|
|
||||||
case "C.uchar", "C.ushort", "C.uint", "C.ulong", "C.ulonglong":
|
|
||||||
switch typeSize {
|
|
||||||
case 1:
|
|
||||||
expr.Name = "uint8"
|
|
||||||
case 2:
|
|
||||||
expr.Name = "uint16"
|
|
||||||
case 4:
|
|
||||||
expr.Name = "uint32"
|
|
||||||
case 8:
|
|
||||||
expr.Name = "uint64"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
info.typedefs[name] = &typedefInfo{
|
|
||||||
typeExpr: expr,
|
|
||||||
}
|
|
||||||
case C.CXCursor_VarDecl:
|
|
||||||
name := getString(C.clang_getCursorSpelling(c))
|
|
||||||
cursorType := C.clang_getCursorType(c)
|
|
||||||
info.globals[name] = &globalInfo{
|
|
||||||
typeExpr: info.makeASTType(cursorType),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return C.CXChildVisit_Continue
|
|
||||||
}
|
|
||||||
|
|
||||||
func getString(clangString C.CXString) (s string) {
|
|
||||||
rawString := C.clang_getCString(clangString)
|
|
||||||
s = C.GoString(rawString)
|
|
||||||
C.clang_disposeString(clangString)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// makeASTType return the ast.Expr for the given libclang type. In other words,
|
|
||||||
// it converts a libclang type to a type in the Go AST.
|
|
||||||
func (info *fileInfo) makeASTType(typ C.CXType) ast.Expr {
|
|
||||||
var typeName string
|
|
||||||
switch typ.kind {
|
|
||||||
case C.CXType_SChar:
|
|
||||||
typeName = "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:
|
|
||||||
return &ast.StarExpr{
|
|
||||||
Star: info.importCPos,
|
|
||||||
X: info.makeASTType(C.clang_getPointeeType(typ)),
|
|
||||||
}
|
|
||||||
case C.CXType_FunctionProto:
|
|
||||||
// Be compatible with gc, which uses the *[0]byte type for function
|
|
||||||
// pointer types.
|
|
||||||
// Return type [0]byte because this is a function type, not a pointer to
|
|
||||||
// this function type.
|
|
||||||
return &ast.ArrayType{
|
|
||||||
Lbrack: info.importCPos,
|
|
||||||
Len: &ast.BasicLit{
|
|
||||||
ValuePos: info.importCPos,
|
|
||||||
Kind: token.INT,
|
|
||||||
Value: "0",
|
|
||||||
},
|
|
||||||
Elt: &ast.Ident{
|
|
||||||
NamePos: info.importCPos,
|
|
||||||
Name: "byte",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
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: info.importCPos,
|
|
||||||
Name: typeName,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
+33
-14
@@ -10,17 +10,22 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"sort"
|
"sort"
|
||||||
|
|
||||||
|
"github.com/tinygo-org/tinygo/cgo"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Program holds all packages and some metadata about the program as a whole.
|
// Program holds all packages and some metadata about the program as a whole.
|
||||||
type Program struct {
|
type Program struct {
|
||||||
Build *build.Context
|
Build *build.Context
|
||||||
Packages map[string]*Package
|
OverlayBuild *build.Context
|
||||||
sorted []*Package
|
ShouldOverlay func(path string) bool
|
||||||
fset *token.FileSet
|
Packages map[string]*Package
|
||||||
TypeChecker types.Config
|
sorted []*Package
|
||||||
Dir string // current working directory (for error reporting)
|
fset *token.FileSet
|
||||||
CFlags []string
|
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.
|
// 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.
|
// 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 {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -293,13 +302,23 @@ func (p *Package) parseFiles() ([]*ast.File, error) {
|
|||||||
fileErrs = append(fileErrs, err)
|
fileErrs = append(fileErrs, err)
|
||||||
continue
|
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)
|
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 {
|
if len(fileErrs) != 0 {
|
||||||
return nil, Errors{p, fileErrs}
|
return nil, Errors{p, fileErrs}
|
||||||
}
|
}
|
||||||
@@ -327,7 +346,7 @@ func (p *Package) importRecursively() error {
|
|||||||
p.Importing = true
|
p.Importing = true
|
||||||
for _, to := range p.Package.Imports {
|
for _, to := range p.Package.Imports {
|
||||||
if to == "C" {
|
if to == "C" {
|
||||||
// Do Cgo processing in a later stage.
|
// Do CGo processing in a later stage.
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if _, ok := p.Imports[to]; ok {
|
if _, ok := p.Imports[to]; ok {
|
||||||
|
|||||||
@@ -33,16 +33,27 @@ func (e *commandError) Error() string {
|
|||||||
return e.Msg + " " + e.File + ": " + e.Err.Error()
|
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 {
|
type BuildConfig struct {
|
||||||
opt string
|
opt string
|
||||||
gc string
|
gc string
|
||||||
printIR bool
|
panicStrategy string
|
||||||
dumpSSA bool
|
printIR bool
|
||||||
debug bool
|
dumpSSA bool
|
||||||
printSizes string
|
debug bool
|
||||||
cFlags []string
|
printSizes string
|
||||||
ldFlags []string
|
cFlags []string
|
||||||
wasmAbi string
|
ldFlags []string
|
||||||
|
wasmAbi string
|
||||||
}
|
}
|
||||||
|
|
||||||
// Helper function for Compiler object.
|
// Helper function for Compiler object.
|
||||||
@@ -51,23 +62,39 @@ func Compile(pkgName, outpath string, spec *TargetSpec, config *BuildConfig, act
|
|||||||
config.gc = spec.GC
|
config.gc = spec.GC
|
||||||
}
|
}
|
||||||
|
|
||||||
// Append command line passed CFlags and LDFlags
|
root := sourceDir()
|
||||||
spec.CFlags = append(spec.CFlags, config.cFlags...)
|
|
||||||
spec.LDFlags = append(spec.LDFlags, config.ldFlags...)
|
|
||||||
|
|
||||||
|
// 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{
|
compilerConfig := compiler.Config{
|
||||||
Triple: spec.Triple,
|
Triple: spec.Triple,
|
||||||
CPU: spec.CPU,
|
CPU: spec.CPU,
|
||||||
GOOS: spec.GOOS,
|
GOOS: spec.GOOS,
|
||||||
GOARCH: spec.GOARCH,
|
GOARCH: spec.GOARCH,
|
||||||
GC: config.gc,
|
GC: config.gc,
|
||||||
CFlags: spec.CFlags,
|
PanicStrategy: config.panicStrategy,
|
||||||
LDFlags: spec.LDFlags,
|
CFlags: cflags,
|
||||||
Debug: config.debug,
|
LDFlags: ldflags,
|
||||||
DumpSSA: config.dumpSSA,
|
Debug: config.debug,
|
||||||
RootDir: sourceDir(),
|
DumpSSA: config.dumpSSA,
|
||||||
GOPATH: getGopath(),
|
TINYGOROOT: root,
|
||||||
BuildTags: spec.BuildTags,
|
GOROOT: goroot,
|
||||||
|
GOPATH: getGopath(),
|
||||||
|
BuildTags: spec.BuildTags,
|
||||||
}
|
}
|
||||||
c, err := compiler.NewCompiler(pkgName, compilerConfig)
|
c, err := compiler.NewCompiler(pkgName, compilerConfig)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -75,12 +102,15 @@ func Compile(pkgName, outpath string, spec *TargetSpec, config *BuildConfig, act
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Compile Go code to IR.
|
// Compile Go code to IR.
|
||||||
err = c.Compile(pkgName)
|
errs := c.Compile(pkgName)
|
||||||
if err != nil {
|
if len(errs) != 0 {
|
||||||
return err
|
if len(errs) == 1 {
|
||||||
|
return errs[0]
|
||||||
|
}
|
||||||
|
return &multiError{errs}
|
||||||
}
|
}
|
||||||
if config.printIR {
|
if config.printIR {
|
||||||
fmt.Println("Generated LLVM IR:")
|
fmt.Println("; Generated LLVM IR:")
|
||||||
fmt.Println(c.IR())
|
fmt.Println(c.IR())
|
||||||
}
|
}
|
||||||
if err := c.Verify(); err != nil {
|
if err := c.Verify(); err != nil {
|
||||||
@@ -191,19 +221,20 @@ func Compile(pkgName, outpath string, spec *TargetSpec, config *BuildConfig, act
|
|||||||
// Prepare link command.
|
// Prepare link command.
|
||||||
executable := filepath.Join(dir, "main")
|
executable := filepath.Join(dir, "main")
|
||||||
tmppath := executable // final file
|
tmppath := executable // final file
|
||||||
ldflags := append(spec.LDFlags, "-o", executable, objfile)
|
ldflags := append(ldflags, "-o", executable, objfile, "-L", root)
|
||||||
if spec.RTLib == "compiler-rt" {
|
if spec.RTLib == "compiler-rt" {
|
||||||
ldflags = append(ldflags, librt)
|
ldflags = append(ldflags, librt)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Compile extra files.
|
// Compile extra files.
|
||||||
for i, path := range spec.ExtraFiles {
|
for i, path := range spec.ExtraFiles {
|
||||||
|
abspath := filepath.Join(root, path)
|
||||||
outpath := filepath.Join(dir, "extra-"+strconv.Itoa(i)+"-"+filepath.Base(path)+".o")
|
outpath := filepath.Join(dir, "extra-"+strconv.Itoa(i)+"-"+filepath.Base(path)+".o")
|
||||||
cmd := exec.Command(spec.Compiler, append(spec.CFlags, "-c", "-o", outpath, path)...)
|
cmdNames := []string{spec.Compiler}
|
||||||
cmd.Stdout = os.Stdout
|
if names, ok := commands[spec.Compiler]; ok {
|
||||||
cmd.Stderr = os.Stderr
|
cmdNames = names
|
||||||
cmd.Dir = sourceDir()
|
}
|
||||||
err := cmd.Run()
|
err := execCommand(cmdNames, append(cflags, "-c", "-o", outpath, abspath)...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return &commandError{"failed to build", path, err}
|
return &commandError{"failed to build", path, err}
|
||||||
}
|
}
|
||||||
@@ -215,11 +246,11 @@ func Compile(pkgName, outpath string, spec *TargetSpec, config *BuildConfig, act
|
|||||||
for _, file := range pkg.CFiles {
|
for _, file := range pkg.CFiles {
|
||||||
path := filepath.Join(pkg.Package.Dir, file)
|
path := filepath.Join(pkg.Package.Dir, file)
|
||||||
outpath := filepath.Join(dir, "pkg"+strconv.Itoa(i)+"-"+file+".o")
|
outpath := filepath.Join(dir, "pkg"+strconv.Itoa(i)+"-"+file+".o")
|
||||||
cmd := exec.Command(spec.Compiler, append(spec.CFlags, "-c", "-o", outpath, path)...)
|
cmdNames := []string{spec.Compiler}
|
||||||
cmd.Stdout = os.Stdout
|
if names, ok := commands[spec.Compiler]; ok {
|
||||||
cmd.Stderr = os.Stderr
|
cmdNames = names
|
||||||
cmd.Dir = sourceDir()
|
}
|
||||||
err := cmd.Run()
|
err := execCommand(cmdNames, append(cflags, "-c", "-o", outpath, path)...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return &commandError{"failed to build", path, err}
|
return &commandError{"failed to build", path, err}
|
||||||
}
|
}
|
||||||
@@ -228,7 +259,7 @@ func Compile(pkgName, outpath string, spec *TargetSpec, config *BuildConfig, act
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Link the object files together.
|
// Link the object files together.
|
||||||
err = Link(sourceDir(), spec.Linker, ldflags...)
|
err = Link(spec.Linker, ldflags...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return &commandError{"failed to link", executable, err}
|
return &commandError{"failed to link", executable, err}
|
||||||
}
|
}
|
||||||
@@ -492,6 +523,10 @@ func handleCompilerError(err error) {
|
|||||||
for _, err := range errLoader.Errs {
|
for _, err := range errLoader.Errs {
|
||||||
fmt.Fprintln(os.Stderr, err)
|
fmt.Fprintln(os.Stderr, err)
|
||||||
}
|
}
|
||||||
|
} else if errMulti, ok := err.(*multiError); ok {
|
||||||
|
for _, err := range errMulti.Errs {
|
||||||
|
fmt.Fprintln(os.Stderr, err)
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
fmt.Fprintln(os.Stderr, "error:", err)
|
fmt.Fprintln(os.Stderr, "error:", err)
|
||||||
}
|
}
|
||||||
@@ -502,7 +537,8 @@ func handleCompilerError(err error) {
|
|||||||
func main() {
|
func main() {
|
||||||
outpath := flag.String("o", "", "output filename")
|
outpath := flag.String("o", "", "output filename")
|
||||||
opt := flag.String("opt", "z", "optimization level: 0, 1, 2, s, z")
|
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")
|
printIR := flag.Bool("printir", false, "print LLVM IR")
|
||||||
dumpSSA := flag.Bool("dumpssa", false, "dump internal Go SSA")
|
dumpSSA := flag.Bool("dumpssa", false, "dump internal Go SSA")
|
||||||
target := flag.String("target", "", "LLVM target")
|
target := flag.String("target", "", "LLVM target")
|
||||||
@@ -523,13 +559,14 @@ func main() {
|
|||||||
|
|
||||||
flag.CommandLine.Parse(os.Args[2:])
|
flag.CommandLine.Parse(os.Args[2:])
|
||||||
config := &BuildConfig{
|
config := &BuildConfig{
|
||||||
opt: *opt,
|
opt: *opt,
|
||||||
gc: *gc,
|
gc: *gc,
|
||||||
printIR: *printIR,
|
panicStrategy: *panicStrategy,
|
||||||
dumpSSA: *dumpSSA,
|
printIR: *printIR,
|
||||||
debug: !*nodebug,
|
dumpSSA: *dumpSSA,
|
||||||
printSizes: *printSize,
|
debug: !*nodebug,
|
||||||
wasmAbi: *wasmAbi,
|
printSizes: *printSize,
|
||||||
|
wasmAbi: *wasmAbi,
|
||||||
}
|
}
|
||||||
|
|
||||||
if *cFlags != "" {
|
if *cFlags != "" {
|
||||||
@@ -540,6 +577,12 @@ func main() {
|
|||||||
config.ldFlags = strings.Split(*ldFlags, " ")
|
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)
|
os.Setenv("CC", "clang -target="+*target)
|
||||||
|
|
||||||
switch command {
|
switch command {
|
||||||
|
|||||||
+18
-7
@@ -13,17 +13,19 @@ import (
|
|||||||
"runtime"
|
"runtime"
|
||||||
"sort"
|
"sort"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/tinygo-org/tinygo/loader"
|
||||||
)
|
)
|
||||||
|
|
||||||
const TESTDATA = "testdata"
|
const TESTDATA = "testdata"
|
||||||
|
|
||||||
func TestCompiler(t *testing.T) {
|
func TestCompiler(t *testing.T) {
|
||||||
matches, err := filepath.Glob(TESTDATA + "/*.go")
|
matches, err := filepath.Glob(filepath.Join(TESTDATA, "*.go"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal("could not read test files:", err)
|
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 {
|
if err != nil {
|
||||||
t.Fatal("could not read test packages:", err)
|
t.Fatal("could not read test packages:", err)
|
||||||
}
|
}
|
||||||
@@ -56,6 +58,9 @@ func TestCompiler(t *testing.T) {
|
|||||||
|
|
||||||
t.Log("running tests for emulated cortex-m3...")
|
t.Log("running tests for emulated cortex-m3...")
|
||||||
for _, path := range matches {
|
for _, path := range matches {
|
||||||
|
if path == "testdata/reflect.go" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
t.Run(path, func(t *testing.T) {
|
t.Run(path, func(t *testing.T) {
|
||||||
runTest(path, tmpdir, "qemu", t)
|
runTest(path, tmpdir, "qemu", t)
|
||||||
})
|
})
|
||||||
@@ -64,7 +69,7 @@ func TestCompiler(t *testing.T) {
|
|||||||
if runtime.GOOS == "linux" {
|
if runtime.GOOS == "linux" {
|
||||||
t.Log("running tests for linux/arm...")
|
t.Log("running tests for linux/arm...")
|
||||||
for _, path := range matches {
|
for _, path := range matches {
|
||||||
if path == "testdata/cgo/" {
|
if path == filepath.Join("testdata", "cgo")+string(filepath.Separator) {
|
||||||
continue // TODO: improve CGo
|
continue // TODO: improve CGo
|
||||||
}
|
}
|
||||||
t.Run(path, func(t *testing.T) {
|
t.Run(path, func(t *testing.T) {
|
||||||
@@ -74,7 +79,7 @@ func TestCompiler(t *testing.T) {
|
|||||||
|
|
||||||
t.Log("running tests for linux/arm64...")
|
t.Log("running tests for linux/arm64...")
|
||||||
for _, path := range matches {
|
for _, path := range matches {
|
||||||
if path == "testdata/cgo/" {
|
if path == filepath.Join("testdata", "cgo")+string(filepath.Separator) {
|
||||||
continue // TODO: improve CGo
|
continue // TODO: improve CGo
|
||||||
}
|
}
|
||||||
t.Run(path, func(t *testing.T) {
|
t.Run(path, func(t *testing.T) {
|
||||||
@@ -84,7 +89,7 @@ func TestCompiler(t *testing.T) {
|
|||||||
|
|
||||||
t.Log("running tests for WebAssembly...")
|
t.Log("running tests for WebAssembly...")
|
||||||
for _, path := range matches {
|
for _, path := range matches {
|
||||||
if path == "testdata/gc.go" {
|
if path == filepath.Join("testdata", "gc.go") {
|
||||||
continue // known to fail
|
continue // known to fail
|
||||||
}
|
}
|
||||||
t.Run(path, func(t *testing.T) {
|
t.Run(path, func(t *testing.T) {
|
||||||
@@ -97,7 +102,7 @@ func TestCompiler(t *testing.T) {
|
|||||||
func runTest(path, tmpdir string, target string, t *testing.T) {
|
func runTest(path, tmpdir string, target string, t *testing.T) {
|
||||||
// Get the expected output for this test.
|
// Get the expected output for this test.
|
||||||
txtpath := path[:len(path)-3] + ".txt"
|
txtpath := path[:len(path)-3] + ".txt"
|
||||||
if path[len(path)-1] == '/' {
|
if path[len(path)-1] == os.PathSeparator {
|
||||||
txtpath = path + "out.txt"
|
txtpath = path + "out.txt"
|
||||||
}
|
}
|
||||||
f, err := os.Open(txtpath)
|
f, err := os.Open(txtpath)
|
||||||
@@ -121,7 +126,13 @@ func runTest(path, tmpdir string, target string, t *testing.T) {
|
|||||||
binary := filepath.Join(tmpdir, "test")
|
binary := filepath.Join(tmpdir, "test")
|
||||||
err = Build("./"+path, binary, target, config)
|
err = Build("./"+path, binary, target, config)
|
||||||
if err != nil {
|
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()
|
t.Fail()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
//go:export update
|
||||||
func update() {
|
func update() {
|
||||||
document := js.Global().Get("document")
|
document := js.Global().Get("document")
|
||||||
a_str := document.Call("getElementById", "a").Get("value").String()
|
aStr := document.Call("getElementById", "a").Get("value").String()
|
||||||
b_str := document.Call("getElementById", "b").Get("value").String()
|
bStr := document.Call("getElementById", "b").Get("value").String()
|
||||||
a, _ := strconv.Atoi(a_str)
|
a, _ := strconv.Atoi(aStr)
|
||||||
b, _ := strconv.Atoi(b_str)
|
b, _ := strconv.Atoi(bStr)
|
||||||
result := a + b
|
result := add(a, b)
|
||||||
document.Call("getElementById", "result").Set("value", result)
|
document.Call("getElementById", "result").Set("value", result)
|
||||||
}
|
}
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
const WASM_URL = '../../../wasm.wasm';
|
const WASM_URL = 'wasm.wasm';
|
||||||
|
|
||||||
var wasm;
|
var wasm;
|
||||||
|
|
||||||
@@ -14,7 +14,7 @@ function init() {
|
|||||||
|
|
||||||
const go = new Go();
|
const go = new Go();
|
||||||
if ('instantiateStreaming' in WebAssembly) {
|
if ('instantiateStreaming' in WebAssembly) {
|
||||||
WebAssembly.instantiateStreaming(fetch(WASM_URL), go.importObject).then(function(obj) {
|
WebAssembly.instantiateStreaming(fetch(WASM_URL), go.importObject).then(function (obj) {
|
||||||
wasm = obj.instance;
|
wasm = obj.instance;
|
||||||
go.run(wasm);
|
go.run(wasm);
|
||||||
updateResult();
|
updateResult();
|
||||||
@@ -23,7 +23,7 @@ function init() {
|
|||||||
fetch(WASM_URL).then(resp =>
|
fetch(WASM_URL).then(resp =>
|
||||||
resp.arrayBuffer()
|
resp.arrayBuffer()
|
||||||
).then(bytes =>
|
).then(bytes =>
|
||||||
WebAssembly.instantiate(bytes, go.importObject).then(function(obj) {
|
WebAssembly.instantiate(bytes, go.importObject).then(function (obj) {
|
||||||
wasm = obj.instance;
|
wasm = obj.instance;
|
||||||
go.run(wasm);
|
go.run(wasm);
|
||||||
updateResult();
|
updateResult();
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
# WebAssembly main execution example
|
||||||
|
|
||||||
|
A simple hello world that prints to the browser console.
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
Note that `index.html` is copied almost verbatim from the Go 1.12 source at
|
||||||
|
`$GOROOT/misc/wasm/wasm_exec.html`. Its license applies to this file.
|
||||||
@@ -0,0 +1,49 @@
|
|||||||
|
<!doctype html>
|
||||||
|
<!--
|
||||||
|
Copyright 2018 The Go Authors. All rights reserved.
|
||||||
|
Use of this source code is governed by a BSD-style
|
||||||
|
license that can be found in the LICENSE file.
|
||||||
|
-->
|
||||||
|
<html>
|
||||||
|
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<title>Go wasm</title>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<!--
|
||||||
|
Add the following polyfill for Microsoft Edge 17/18 support:
|
||||||
|
<script src="https://cdn.jsdelivr.net/npm/text-encoding@0.7.0/lib/encoding.min.js"></script>
|
||||||
|
(see https://caniuse.com/#feat=textencoder)
|
||||||
|
-->
|
||||||
|
<script src="wasm_exec.js"></script>
|
||||||
|
<script>
|
||||||
|
if (!WebAssembly.instantiateStreaming) { // polyfill
|
||||||
|
WebAssembly.instantiateStreaming = async (resp, importObject) => {
|
||||||
|
const source = await (await resp).arrayBuffer();
|
||||||
|
return await WebAssembly.instantiate(source, importObject);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const go = new Go();
|
||||||
|
let mod, inst;
|
||||||
|
WebAssembly.instantiateStreaming(fetch("wasm.wasm"), go.importObject).then((result) => {
|
||||||
|
mod = result.module;
|
||||||
|
inst = result.instance;
|
||||||
|
document.getElementById("runButton").disabled = false;
|
||||||
|
}).catch((err) => {
|
||||||
|
console.error(err);
|
||||||
|
});
|
||||||
|
|
||||||
|
async function run() {
|
||||||
|
console.clear();
|
||||||
|
await go.run(inst);
|
||||||
|
inst = await WebAssembly.instantiate(mod, go.importObject); // reset instance
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<button onClick="run();" id="runButton" disabled>Run</button>
|
||||||
|
</body>
|
||||||
|
|
||||||
|
</html>
|
||||||
@@ -0,0 +1,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>
|
|
||||||
@@ -76,8 +76,16 @@ const (
|
|||||||
|
|
||||||
// I2C on the Circuit Playground Express.
|
// I2C on the Circuit Playground Express.
|
||||||
var (
|
var (
|
||||||
I2C0 = I2C{Bus: sam.SERCOM5_I2CM} // external device
|
// external device
|
||||||
I2C1 = I2C{Bus: sam.SERCOM1_I2CM} // internal 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)
|
// SPI pins (internal flash)
|
||||||
@@ -91,3 +99,15 @@ const (
|
|||||||
var (
|
var (
|
||||||
SPI0 = SPI{Bus: sam.SERCOM3_SPI}
|
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}
|
||||||
|
)
|
||||||
|
|||||||
@@ -44,8 +44,8 @@ const (
|
|||||||
|
|
||||||
// UART1 pins
|
// UART1 pins
|
||||||
const (
|
const (
|
||||||
UART_TX_PIN = D1
|
UART_TX_PIN = D10
|
||||||
UART_RX_PIN = D0
|
UART_RX_PIN = D11
|
||||||
)
|
)
|
||||||
|
|
||||||
// I2C pins
|
// I2C pins
|
||||||
@@ -56,7 +56,10 @@ const (
|
|||||||
|
|
||||||
// I2C on the ItsyBitsy M0.
|
// I2C on the ItsyBitsy M0.
|
||||||
var (
|
var (
|
||||||
I2C0 = I2C{Bus: sam.SERCOM3_I2CM}
|
I2C0 = I2C{Bus: sam.SERCOM3_I2CM,
|
||||||
|
SDA: SDA_PIN,
|
||||||
|
SCL: SCL_PIN,
|
||||||
|
PinMode: GPIO_SERCOM}
|
||||||
)
|
)
|
||||||
|
|
||||||
// SPI pins
|
// SPI pins
|
||||||
@@ -70,3 +73,15 @@ const (
|
|||||||
var (
|
var (
|
||||||
SPI0 = SPI{Bus: sam.SERCOM4_SPI}
|
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
|
package machine
|
||||||
|
|
||||||
import (
|
|
||||||
"device/nrf"
|
|
||||||
"errors"
|
|
||||||
)
|
|
||||||
|
|
||||||
// The micro:bit does not have a 32kHz crystal on board.
|
// The micro:bit does not have a 32kHz crystal on board.
|
||||||
const HasLowFrequencyCrystal = false
|
const HasLowFrequencyCrystal = false
|
||||||
|
|
||||||
@@ -79,65 +74,3 @@ const (
|
|||||||
LED_ROW_2 = 14
|
LED_ROW_2 = 14
|
||||||
LED_ROW_3 = 15
|
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) {
|
func (p GPIO) Configure(config GPIOConfig) {
|
||||||
if config.Mode == GPIO_OUTPUT { // set output bit
|
if config.Mode == GPIO_OUTPUT { // set output bit
|
||||||
if p.Pin < 8 {
|
if p.Pin < 8 {
|
||||||
*avr.DDRD |= 1 << p.Pin
|
avr.DDRD.SetBits(1 << p.Pin)
|
||||||
} else {
|
} else {
|
||||||
*avr.DDRB |= 1 << (p.Pin - 8)
|
avr.DDRB.SetBits(1 << (p.Pin - 8))
|
||||||
}
|
}
|
||||||
} else { // configure input: clear output bit
|
} else { // configure input: clear output bit
|
||||||
if p.Pin < 8 {
|
if p.Pin < 8 {
|
||||||
*avr.DDRD &^= 1 << p.Pin
|
avr.DDRD.ClearBits(1 << p.Pin)
|
||||||
} else {
|
} 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.
|
// Get returns the current value of a GPIO pin.
|
||||||
func (p GPIO) Get() bool {
|
func (p GPIO) Get() bool {
|
||||||
if p.Pin < 8 {
|
if p.Pin < 8 {
|
||||||
val := *avr.PIND & (1 << p.Pin)
|
val := avr.PIND.Get() & (1 << p.Pin)
|
||||||
return (val > 0)
|
return (val > 0)
|
||||||
} else {
|
} else {
|
||||||
val := *avr.PINB & (1 << (p.Pin - 8))
|
val := avr.PINB.Get() & (1 << (p.Pin - 8))
|
||||||
return (val > 0)
|
return (val > 0)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p GPIO) getPortMask() (*avr.RegValue, uint8) {
|
func (p GPIO) getPortMask() (*avr.Register8, uint8) {
|
||||||
if p.Pin < 8 {
|
if p.Pin < 8 {
|
||||||
return avr.PORTD, 1 << p.Pin
|
return avr.PORTD, 1 << p.Pin
|
||||||
} else {
|
} else {
|
||||||
@@ -45,64 +45,64 @@ func (p GPIO) getPortMask() (*avr.RegValue, uint8) {
|
|||||||
// InitPWM initializes the registers needed for PWM.
|
// InitPWM initializes the registers needed for PWM.
|
||||||
func InitPWM() {
|
func InitPWM() {
|
||||||
// use waveform generation
|
// use waveform generation
|
||||||
*avr.TCCR0A |= avr.TCCR0A_WGM00
|
avr.TCCR0A.SetBits(avr.TCCR0A_WGM00)
|
||||||
|
|
||||||
// set timer 0 prescale factor to 64
|
// 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
|
// 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
|
// 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
|
// 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)
|
// 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.
|
// Configure configures a PWM pin for output.
|
||||||
func (pwm PWM) Configure() {
|
func (pwm PWM) Configure() {
|
||||||
if pwm.Pin < 8 {
|
if pwm.Pin < 8 {
|
||||||
*avr.DDRD |= 1 << pwm.Pin
|
avr.DDRD.SetBits(1 << pwm.Pin)
|
||||||
} else {
|
} 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
|
// 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.
|
// 8-bit value ranging from 0 to 255.
|
||||||
func (pwm PWM) Set(value uint16) {
|
func (pwm PWM) Set(value uint16) {
|
||||||
value8 := value >> 8
|
value8 := uint8(value >> 8)
|
||||||
switch pwm.Pin {
|
switch pwm.Pin {
|
||||||
case 3:
|
case 3:
|
||||||
// connect pwm to pin on timer 2, channel B
|
// connect pwm to pin on timer 2, channel B
|
||||||
*avr.TCCR2A |= avr.TCCR2A_COM2B1
|
avr.TCCR2A.SetBits(avr.TCCR2A_COM2B1)
|
||||||
*avr.OCR2B = avr.RegValue(value8) // set pwm duty
|
avr.OCR2B.Set(value8) // set pwm duty
|
||||||
case 5:
|
case 5:
|
||||||
// connect pwm to pin on timer 0, channel B
|
// connect pwm to pin on timer 0, channel B
|
||||||
*avr.TCCR0A |= avr.TCCR0A_COM0B1
|
avr.TCCR0A.SetBits(avr.TCCR0A_COM0B1)
|
||||||
*avr.OCR0B = avr.RegValue(value8) // set pwm duty
|
avr.OCR0B.Set(value8) // set pwm duty
|
||||||
case 6:
|
case 6:
|
||||||
// connect pwm to pin on timer 0, channel A
|
// connect pwm to pin on timer 0, channel A
|
||||||
*avr.TCCR0A |= avr.TCCR0A_COM0A1
|
avr.TCCR0A.SetBits(avr.TCCR0A_COM0A1)
|
||||||
*avr.OCR0A = avr.RegValue(value8) // set pwm duty
|
avr.OCR0A.Set(value8) // set pwm duty
|
||||||
case 9:
|
case 9:
|
||||||
// connect pwm to pin on timer 1, channel A
|
// 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
|
// 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:
|
case 10:
|
||||||
// connect pwm to pin on timer 1, channel B
|
// 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
|
// 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:
|
case 11:
|
||||||
// connect pwm to pin on timer 2, channel A
|
// connect pwm to pin on timer 2, channel A
|
||||||
*avr.TCCR2A |= avr.TCCR2A_COM2A1
|
avr.TCCR2A.SetBits(avr.TCCR2A_COM2A1)
|
||||||
*avr.OCR2A = avr.RegValue(value8) // set pwm duty
|
avr.OCR2A.Set(value8) // set pwm duty
|
||||||
default:
|
default:
|
||||||
panic("Invalid PWM pin")
|
panic("Invalid PWM pin")
|
||||||
}
|
}
|
||||||
@@ -121,19 +121,19 @@ func (i2c I2C) Configure(config I2CConfig) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Activate internal pullups for twi.
|
// 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.
|
// 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:
|
// twi bit rate formula from atmega128 manual pg. 204:
|
||||||
// SCL Frequency = CPU Clock Frequency / (16 + (2 * TWBR))
|
// SCL Frequency = CPU Clock Frequency / (16 + (2 * TWBR))
|
||||||
// NOTE: TWBR should be 10 or higher for master mode.
|
// NOTE: TWBR should be 10 or higher for master mode.
|
||||||
// It is 72 for a 16mhz board with 100kHz TWI
|
// 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.
|
// Enable twi module.
|
||||||
*avr.TWCR = avr.TWCR_TWEN
|
avr.TWCR.Set(avr.TWCR_TWEN)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Tx does a single I2C transaction at the specified address.
|
// 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.
|
// start starts an I2C communication session.
|
||||||
func (i2c I2C) start(address uint8, write bool) {
|
func (i2c I2C) start(address uint8, write bool) {
|
||||||
// Clear TWI interrupt flag, put start condition on SDA, and enable TWI.
|
// 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.
|
// 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.
|
// Write 7-bit shifted peripheral address.
|
||||||
@@ -179,36 +179,36 @@ func (i2c I2C) start(address uint8, write bool) {
|
|||||||
// stop ends an I2C communication session.
|
// stop ends an I2C communication session.
|
||||||
func (i2c I2C) stop() {
|
func (i2c I2C) stop() {
|
||||||
// Send stop condition.
|
// 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.
|
// 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.
|
// writeByte writes a single byte to the I2C bus.
|
||||||
func (i2c I2C) writeByte(data byte) {
|
func (i2c I2C) writeByte(data byte) {
|
||||||
// Write data to register.
|
// Write data to register.
|
||||||
*avr.TWDR = avr.RegValue(data)
|
avr.TWDR.Set(data)
|
||||||
|
|
||||||
// Clear TWI interrupt flag and enable TWI.
|
// 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.
|
// 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.
|
// readByte reads a single byte from the I2C bus.
|
||||||
func (i2c I2C) readByte() byte {
|
func (i2c I2C) readByte() byte {
|
||||||
// Clear TWI interrupt flag and enable TWI.
|
// 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.
|
// 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.
|
// 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
|
// https://www.microchip.com/webdoc/AVRLibcReferenceManual/FAQ_1faq_wrong_baud_rate.html
|
||||||
// ((F_CPU + UART_BAUD_RATE * 8L) / (UART_BAUD_RATE * 16L) - 1)
|
// ((F_CPU + UART_BAUD_RATE * 8L) / (UART_BAUD_RATE * 16L) - 1)
|
||||||
ps := ((CPU_FREQUENCY+config.BaudRate*8)/(config.BaudRate*16) - 1)
|
ps := ((CPU_FREQUENCY+config.BaudRate*8)/(config.BaudRate*16) - 1)
|
||||||
*avr.UBRR0H = avr.RegValue(ps >> 8)
|
avr.UBRR0H.Set(uint8(ps >> 8))
|
||||||
*avr.UBRR0L = avr.RegValue(ps & 0xff)
|
avr.UBRR0L.Set(uint8(ps & 0xff))
|
||||||
|
|
||||||
// enable RX, TX and RX interrupt
|
// 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
|
// 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.
|
// WriteByte writes a byte of data to the UART.
|
||||||
func (uart UART) WriteByte(c byte) error {
|
func (uart UART) WriteByte(c byte) error {
|
||||||
// Wait until UART buffer is not busy.
|
// 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
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
//go:interrupt USART_RX_vect
|
//go:interrupt USART_RX_vect
|
||||||
func handleUSART_RX() {
|
func handleUSART_RX() {
|
||||||
// Read register to clear it.
|
// Read register to clear it.
|
||||||
data := *avr.UDR0
|
data := avr.UDR0.Get()
|
||||||
|
|
||||||
// Ensure no error.
|
// 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.
|
// Put data from UDR register into buffer.
|
||||||
UART0.Receive(byte(data))
|
UART0.Receive(byte(data))
|
||||||
}
|
}
|
||||||
|
|||||||
+255
-17
@@ -343,6 +343,12 @@ func (a ADC) Get() uint16 {
|
|||||||
sam.ADC.INPUTCTRL |= sam.RegValue(ch << sam.ADC_INPUTCTRL_MUXPOS_Pos)
|
sam.ADC.INPUTCTRL |= sam.RegValue(ch << sam.ADC_INPUTCTRL_MUXPOS_Pos)
|
||||||
waitADCSync()
|
waitADCSync()
|
||||||
|
|
||||||
|
// Select internal ground for ADC input
|
||||||
|
sam.ADC.INPUTCTRL &^= sam.ADC_INPUTCTRL_MUXNEG_Msk
|
||||||
|
waitADCSync()
|
||||||
|
sam.ADC.INPUTCTRL |= sam.RegValue(sam.ADC_INPUTCTRL_MUXNEG_GND << sam.ADC_INPUTCTRL_MUXNEG_Pos)
|
||||||
|
waitADCSync()
|
||||||
|
|
||||||
// Enable ADC
|
// Enable ADC
|
||||||
sam.ADC.CTRLA |= sam.ADC_CTRLA_ENABLE
|
sam.ADC.CTRLA |= sam.ADC_CTRLA_ENABLE
|
||||||
waitADCSync()
|
waitADCSync()
|
||||||
@@ -368,7 +374,7 @@ func (a ADC) Get() uint16 {
|
|||||||
sam.ADC.CTRLA &^= sam.ADC_CTRLA_ENABLE
|
sam.ADC.CTRLA &^= sam.ADC_CTRLA_ENABLE
|
||||||
waitADCSync()
|
waitADCSync()
|
||||||
|
|
||||||
return uint16(val)
|
return uint16(val) << 4 // scales from 12 to 16-bit result
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a ADC) getADCChannel() uint8 {
|
func (a ADC) getADCChannel() uint8 {
|
||||||
@@ -416,7 +422,7 @@ var (
|
|||||||
UART0 = USBCDC{Buffer: NewRingBuffer()}
|
UART0 = USBCDC{Buffer: NewRingBuffer()}
|
||||||
|
|
||||||
// The first hardware serial port on the SAMD21. Uses the SERCOM0 interface.
|
// The first hardware serial port on the SAMD21. Uses the SERCOM0 interface.
|
||||||
UART1 = UART{Bus: sam.SERCOM0_USART, Buffer: NewRingBuffer()}
|
UART1 = UART{Bus: sam.SERCOM1_USART, Buffer: NewRingBuffer()}
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@@ -453,26 +459,26 @@ func (uart UART) Configure(config UARTConfig) {
|
|||||||
// determine pads
|
// determine pads
|
||||||
var txpad, rxpad int
|
var txpad, rxpad int
|
||||||
switch config.TX {
|
switch config.TX {
|
||||||
case UART_TX_PIN:
|
case PA10:
|
||||||
txpad = sercomTXPad2
|
txpad = sercomTXPad2
|
||||||
case D10:
|
case PA18:
|
||||||
txpad = sercomTXPad2
|
txpad = sercomTXPad2
|
||||||
case D11:
|
case PA16:
|
||||||
txpad = sercomTXPad0
|
txpad = sercomTXPad0
|
||||||
default:
|
default:
|
||||||
panic("Invalid TX pin for UART")
|
panic("Invalid TX pin for UART")
|
||||||
}
|
}
|
||||||
|
|
||||||
switch config.RX {
|
switch config.RX {
|
||||||
case UART_RX_PIN:
|
case PA11:
|
||||||
rxpad = sercomRXPad3
|
rxpad = sercomRXPad3
|
||||||
case D10:
|
case PA18:
|
||||||
rxpad = sercomRXPad2
|
rxpad = sercomRXPad2
|
||||||
case D11:
|
case PA16:
|
||||||
rxpad = sercomRXPad0
|
rxpad = sercomRXPad0
|
||||||
case D12:
|
case PA19:
|
||||||
rxpad = sercomRXPad3
|
rxpad = sercomRXPad3
|
||||||
case D13:
|
case PA17:
|
||||||
rxpad = sercomRXPad1
|
rxpad = sercomRXPad1
|
||||||
default:
|
default:
|
||||||
panic("Invalid RX pin for UART")
|
panic("Invalid RX pin for UART")
|
||||||
@@ -531,11 +537,11 @@ func (uart UART) Configure(config UARTConfig) {
|
|||||||
uart.Bus.INTENSET = sam.SERCOM_USART_INTENSET_RXC
|
uart.Bus.INTENSET = sam.SERCOM_USART_INTENSET_RXC
|
||||||
|
|
||||||
// Enable RX IRQ.
|
// Enable RX IRQ.
|
||||||
if config.TX == UART_TX_PIN {
|
if config.TX == PA10 {
|
||||||
// UART0
|
// UART0
|
||||||
arm.EnableIRQ(sam.IRQ_SERCOM0)
|
arm.EnableIRQ(sam.IRQ_SERCOM0)
|
||||||
} else {
|
} else {
|
||||||
// UART1
|
// UART1 which is the normal default, since UART0 is used for USBCDC.
|
||||||
arm.EnableIRQ(sam.IRQ_SERCOM1)
|
arm.EnableIRQ(sam.IRQ_SERCOM1)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -563,7 +569,7 @@ func (uart UART) WriteByte(c byte) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
//go:export SERCOM0_IRQHandler
|
//go:export SERCOM1_IRQHandler
|
||||||
func handleUART1() {
|
func handleUART1() {
|
||||||
// should reset IRQ
|
// should reset IRQ
|
||||||
UART1.Receive(byte((UART1.Bus.DATA & 0xFF)))
|
UART1.Receive(byte((UART1.Bus.DATA & 0xFF)))
|
||||||
@@ -572,7 +578,10 @@ func handleUART1() {
|
|||||||
|
|
||||||
// I2C on the SAMD21.
|
// I2C on the SAMD21.
|
||||||
type I2C struct {
|
type I2C struct {
|
||||||
Bus *sam.SERCOM_I2CM_Type
|
Bus *sam.SERCOM_I2CM_Type
|
||||||
|
SCL uint8
|
||||||
|
SDA uint8
|
||||||
|
PinMode GPIOMode
|
||||||
}
|
}
|
||||||
|
|
||||||
// I2CConfig is used to store config info for I2C.
|
// I2CConfig is used to store config info for I2C.
|
||||||
@@ -608,7 +617,7 @@ func (i2c I2C) Configure(config I2CConfig) {
|
|||||||
config.Frequency = TWI_FREQ_100KHZ
|
config.Frequency = TWI_FREQ_100KHZ
|
||||||
}
|
}
|
||||||
|
|
||||||
// reset SERCOM3
|
// reset SERCOM
|
||||||
i2c.Bus.CTRLA |= sam.SERCOM_I2CM_CTRLA_SWRST
|
i2c.Bus.CTRLA |= sam.SERCOM_I2CM_CTRLA_SWRST
|
||||||
for (i2c.Bus.CTRLA&sam.SERCOM_I2CM_CTRLA_SWRST) > 0 ||
|
for (i2c.Bus.CTRLA&sam.SERCOM_I2CM_CTRLA_SWRST) > 0 ||
|
||||||
(i2c.Bus.SYNCBUSY&sam.SERCOM_I2CM_SYNCBUSY_SWRST) > 0 {
|
(i2c.Bus.SYNCBUSY&sam.SERCOM_I2CM_SYNCBUSY_SWRST) > 0 {
|
||||||
@@ -632,8 +641,8 @@ func (i2c I2C) Configure(config I2CConfig) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// enable pins
|
// enable pins
|
||||||
GPIO{SDA_PIN}.Configure(GPIOConfig{Mode: GPIO_SERCOM})
|
GPIO{i2c.SDA}.Configure(GPIOConfig{Mode: i2c.PinMode})
|
||||||
GPIO{SCL_PIN}.Configure(GPIOConfig{Mode: GPIO_SERCOM})
|
GPIO{i2c.SCL}.Configure(GPIOConfig{Mode: i2c.PinMode})
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetBaudRate sets the communication speed for the I2C.
|
// SetBaudRate sets the communication speed for the I2C.
|
||||||
@@ -799,6 +808,235 @@ func (i2c I2C) readByte() byte {
|
|||||||
return byte(i2c.Bus.DATA)
|
return byte(i2c.Bus.DATA)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// I2S on the SAMD21.
|
||||||
|
|
||||||
|
// I2S
|
||||||
|
type I2S struct {
|
||||||
|
Bus *sam.I2S_Type
|
||||||
|
}
|
||||||
|
|
||||||
|
// Configure is used to configure the I2S interface. You must call this
|
||||||
|
// before you can use the I2S bus.
|
||||||
|
func (i2s I2S) Configure(config I2SConfig) {
|
||||||
|
// handle defaults
|
||||||
|
if config.SCK == 0 {
|
||||||
|
config.SCK = I2S_SCK_PIN
|
||||||
|
config.WS = I2S_WS_PIN
|
||||||
|
config.SD = I2S_SD_PIN
|
||||||
|
}
|
||||||
|
|
||||||
|
if config.AudioFrequency == 0 {
|
||||||
|
config.AudioFrequency = 48000
|
||||||
|
}
|
||||||
|
|
||||||
|
if config.DataFormat == I2SDataFormatDefault {
|
||||||
|
if config.Stereo {
|
||||||
|
config.DataFormat = I2SDataFormat16bit
|
||||||
|
} else {
|
||||||
|
config.DataFormat = I2SDataFormat32bit
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Turn on clock for I2S
|
||||||
|
sam.PM.APBCMASK |= sam.PM_APBCMASK_I2S_
|
||||||
|
|
||||||
|
// setting clock rate for sample.
|
||||||
|
division_factor := CPU_FREQUENCY / (config.AudioFrequency * uint32(config.DataFormat))
|
||||||
|
|
||||||
|
// Switch Generic Clock Generator 3 to DFLL48M.
|
||||||
|
sam.GCLK.GENDIV = sam.RegValue((sam.GCLK_CLKCTRL_GEN_GCLK3 << sam.GCLK_GENDIV_ID_Pos) |
|
||||||
|
(division_factor << sam.GCLK_GENDIV_DIV_Pos))
|
||||||
|
waitForSync()
|
||||||
|
|
||||||
|
sam.GCLK.GENCTRL = sam.RegValue((sam.GCLK_CLKCTRL_GEN_GCLK3 << sam.GCLK_GENCTRL_ID_Pos) |
|
||||||
|
(sam.GCLK_GENCTRL_SRC_DFLL48M << sam.GCLK_GENCTRL_SRC_Pos) |
|
||||||
|
sam.GCLK_GENCTRL_IDC |
|
||||||
|
sam.GCLK_GENCTRL_GENEN)
|
||||||
|
waitForSync()
|
||||||
|
|
||||||
|
// Use Generic Clock Generator 3 as source for I2S.
|
||||||
|
sam.GCLK.CLKCTRL = sam.RegValue16((sam.GCLK_CLKCTRL_ID_I2S_0 << sam.GCLK_CLKCTRL_ID_Pos) |
|
||||||
|
(sam.GCLK_CLKCTRL_GEN_GCLK3 << sam.GCLK_CLKCTRL_GEN_Pos) |
|
||||||
|
sam.GCLK_CLKCTRL_CLKEN)
|
||||||
|
waitForSync()
|
||||||
|
|
||||||
|
// reset the device
|
||||||
|
i2s.Bus.CTRLA |= sam.I2S_CTRLA_SWRST
|
||||||
|
for (i2s.Bus.SYNCBUSY & sam.I2S_SYNCBUSY_SWRST) > 0 {
|
||||||
|
}
|
||||||
|
|
||||||
|
// disable device before continuing
|
||||||
|
for (i2s.Bus.SYNCBUSY & sam.I2S_SYNCBUSY_ENABLE) > 0 {
|
||||||
|
}
|
||||||
|
i2s.Bus.CTRLA &^= sam.I2S_CTRLA_ENABLE
|
||||||
|
|
||||||
|
// setup clock
|
||||||
|
if config.ClockSource == I2SClockSourceInternal {
|
||||||
|
// TODO: make sure correct for I2S output
|
||||||
|
|
||||||
|
// set serial clock select pin
|
||||||
|
i2s.Bus.CLKCTRL0 |= sam.I2S_CLKCTRL_SCKSEL
|
||||||
|
|
||||||
|
// set frame select pin
|
||||||
|
i2s.Bus.CLKCTRL0 |= sam.I2S_CLKCTRL_FSSEL
|
||||||
|
} else {
|
||||||
|
// Configure FS generation from SCK clock.
|
||||||
|
i2s.Bus.CLKCTRL0 &^= sam.I2S_CLKCTRL_FSSEL
|
||||||
|
}
|
||||||
|
|
||||||
|
if config.Standard == I2StandardPhilips {
|
||||||
|
// set 1-bit delay
|
||||||
|
i2s.Bus.CLKCTRL0 |= sam.I2S_CLKCTRL_BITDELAY
|
||||||
|
} else {
|
||||||
|
// set 0-bit delay
|
||||||
|
i2s.Bus.CLKCTRL0 &^= sam.I2S_CLKCTRL_BITDELAY
|
||||||
|
}
|
||||||
|
|
||||||
|
// set number of slots.
|
||||||
|
if config.Stereo {
|
||||||
|
i2s.Bus.CLKCTRL0 |= (1 << sam.I2S_CLKCTRL_NBSLOTS_Pos)
|
||||||
|
} else {
|
||||||
|
i2s.Bus.CLKCTRL0 &^= (1 << sam.I2S_CLKCTRL_NBSLOTS_Pos)
|
||||||
|
}
|
||||||
|
|
||||||
|
// set slot size
|
||||||
|
switch config.DataFormat {
|
||||||
|
case I2SDataFormat8bit:
|
||||||
|
i2s.Bus.CLKCTRL0 |= sam.I2S_CLKCTRL_SLOTSIZE_8
|
||||||
|
|
||||||
|
case I2SDataFormat16bit:
|
||||||
|
i2s.Bus.CLKCTRL0 |= sam.I2S_CLKCTRL_SLOTSIZE_16
|
||||||
|
|
||||||
|
case I2SDataFormat24bit:
|
||||||
|
i2s.Bus.CLKCTRL0 |= sam.I2S_CLKCTRL_SLOTSIZE_24
|
||||||
|
|
||||||
|
case I2SDataFormat32bit:
|
||||||
|
i2s.Bus.CLKCTRL0 |= sam.I2S_CLKCTRL_SLOTSIZE_32
|
||||||
|
}
|
||||||
|
|
||||||
|
// configure pin for clock
|
||||||
|
GPIO{config.SCK}.Configure(GPIOConfig{Mode: GPIO_COM})
|
||||||
|
|
||||||
|
// configure pin for WS, if needed
|
||||||
|
if config.WS != 0xff {
|
||||||
|
GPIO{config.WS}.Configure(GPIOConfig{Mode: GPIO_COM})
|
||||||
|
}
|
||||||
|
|
||||||
|
// now set serializer data size.
|
||||||
|
switch config.DataFormat {
|
||||||
|
case I2SDataFormat8bit:
|
||||||
|
i2s.Bus.SERCTRL1 |= sam.I2S_SERCTRL_DATASIZE_8
|
||||||
|
|
||||||
|
case I2SDataFormat16bit:
|
||||||
|
i2s.Bus.SERCTRL1 |= sam.I2S_SERCTRL_DATASIZE_16
|
||||||
|
|
||||||
|
case I2SDataFormat24bit:
|
||||||
|
i2s.Bus.SERCTRL1 |= sam.I2S_SERCTRL_DATASIZE_24
|
||||||
|
|
||||||
|
case I2SDataFormat32bit:
|
||||||
|
case I2SDataFormatDefault:
|
||||||
|
i2s.Bus.SERCTRL1 |= sam.I2S_SERCTRL_DATASIZE_32
|
||||||
|
}
|
||||||
|
|
||||||
|
// set serializer slot adjustment
|
||||||
|
if config.Standard == I2SStandardLSB {
|
||||||
|
// adjust right
|
||||||
|
i2s.Bus.SERCTRL1 &^= sam.I2S_SERCTRL_SLOTADJ
|
||||||
|
} else {
|
||||||
|
// adjust left
|
||||||
|
i2s.Bus.SERCTRL1 |= sam.I2S_SERCTRL_SLOTADJ
|
||||||
|
|
||||||
|
// reverse bit order?
|
||||||
|
i2s.Bus.SERCTRL1 |= sam.I2S_SERCTRL_BITREV
|
||||||
|
}
|
||||||
|
|
||||||
|
// set serializer mode.
|
||||||
|
if config.Mode == I2SModePDM {
|
||||||
|
i2s.Bus.SERCTRL1 |= sam.I2S_SERCTRL_SERMODE_PDM2
|
||||||
|
} else {
|
||||||
|
i2s.Bus.SERCTRL1 |= sam.I2S_SERCTRL_SERMODE_RX
|
||||||
|
}
|
||||||
|
|
||||||
|
// configure data pin
|
||||||
|
GPIO{config.SD}.Configure(GPIOConfig{Mode: GPIO_COM})
|
||||||
|
|
||||||
|
// re-enable
|
||||||
|
i2s.Bus.CTRLA |= sam.I2S_CTRLA_ENABLE
|
||||||
|
for (i2s.Bus.SYNCBUSY & sam.I2S_SYNCBUSY_ENABLE) > 0 {
|
||||||
|
}
|
||||||
|
|
||||||
|
// enable i2s clock
|
||||||
|
i2s.Bus.CTRLA |= sam.I2S_CTRLA_CKEN0
|
||||||
|
for (i2s.Bus.SYNCBUSY & sam.I2S_SYNCBUSY_CKEN0) > 0 {
|
||||||
|
}
|
||||||
|
|
||||||
|
// enable i2s serializer
|
||||||
|
i2s.Bus.CTRLA |= sam.I2S_CTRLA_SEREN1
|
||||||
|
for (i2s.Bus.SYNCBUSY & sam.I2S_SYNCBUSY_SEREN1) > 0 {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read data from the I2S bus into the provided slice.
|
||||||
|
// The I2S bus must already have been configured correctly.
|
||||||
|
func (i2s I2S) Read(p []uint32) (n int, err error) {
|
||||||
|
i := 0
|
||||||
|
for i = 0; i < len(p); i++ {
|
||||||
|
// Wait until ready
|
||||||
|
for (i2s.Bus.INTFLAG & sam.I2S_INTFLAG_RXRDY1) == 0 {
|
||||||
|
}
|
||||||
|
|
||||||
|
for (i2s.Bus.SYNCBUSY & sam.I2S_SYNCBUSY_DATA1) > 0 {
|
||||||
|
}
|
||||||
|
|
||||||
|
// read data
|
||||||
|
p[i] = uint32(i2s.Bus.DATA1)
|
||||||
|
|
||||||
|
// indicate read complete
|
||||||
|
i2s.Bus.INTFLAG = sam.I2S_INTFLAG_RXRDY1
|
||||||
|
}
|
||||||
|
|
||||||
|
return i, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write data to the I2S bus from the provided slice.
|
||||||
|
// The I2S bus must already have been configured correctly.
|
||||||
|
func (i2s I2S) Write(p []uint32) (n int, err error) {
|
||||||
|
i := 0
|
||||||
|
for i = 0; i < len(p); i++ {
|
||||||
|
// Wait until ready
|
||||||
|
for (i2s.Bus.INTFLAG & sam.I2S_INTFLAG_TXRDY1) == 0 {
|
||||||
|
}
|
||||||
|
|
||||||
|
for (i2s.Bus.SYNCBUSY & sam.I2S_SYNCBUSY_DATA1) > 0 {
|
||||||
|
}
|
||||||
|
|
||||||
|
// write data
|
||||||
|
i2s.Bus.DATA1 = sam.RegValue(p[i])
|
||||||
|
|
||||||
|
// indicate write complete
|
||||||
|
i2s.Bus.INTFLAG = sam.I2S_INTFLAG_TXRDY1
|
||||||
|
}
|
||||||
|
|
||||||
|
return i, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close the I2S bus.
|
||||||
|
func (i2s I2S) Close() error {
|
||||||
|
// Sync wait
|
||||||
|
for (i2s.Bus.SYNCBUSY & sam.I2S_SYNCBUSY_ENABLE) > 0 {
|
||||||
|
}
|
||||||
|
|
||||||
|
// disable I2S
|
||||||
|
i2s.Bus.CTRLA &^= sam.I2S_CTRLA_ENABLE
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func waitForSync() {
|
||||||
|
for (sam.GCLK.STATUS & sam.GCLK_STATUS_SYNCBUSY) > 0 {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// SPI
|
// SPI
|
||||||
type SPI struct {
|
type SPI struct {
|
||||||
Bus *sam.SERCOM_SPI_Type
|
Bus *sam.SERCOM_SPI_Type
|
||||||
|
|||||||
@@ -9,19 +9,19 @@ import (
|
|||||||
// Configure sets the pin to input or output.
|
// Configure sets the pin to input or output.
|
||||||
func (p GPIO) Configure(config GPIOConfig) {
|
func (p GPIO) Configure(config GPIOConfig) {
|
||||||
if config.Mode == GPIO_OUTPUT { // set output bit
|
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
|
} 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
|
return avr.PORTB, 1 << p.Pin
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get returns the current value of a GPIO pin.
|
// Get returns the current value of a GPIO pin.
|
||||||
func (p GPIO) Get() bool {
|
func (p GPIO) Get() bool {
|
||||||
val := *avr.PINB & (1 << p.Pin)
|
val := avr.PINB.Get() & (1 << p.Pin)
|
||||||
return (val > 0)
|
return (val > 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+12
-14
@@ -17,10 +17,10 @@ const (
|
|||||||
func (p GPIO) Set(value bool) {
|
func (p GPIO) Set(value bool) {
|
||||||
if value { // set bits
|
if value { // set bits
|
||||||
port, mask := p.PortMaskSet()
|
port, mask := p.PortMaskSet()
|
||||||
*port = mask
|
port.Set(mask)
|
||||||
} else { // clear bits
|
} else { // clear bits
|
||||||
port, mask := p.PortMaskClear()
|
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
|
// 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
|
// returned mask is only valid as long as no other pin in the same port has been
|
||||||
// changed.
|
// changed.
|
||||||
func (p GPIO) PortMaskSet() (*avr.RegValue, avr.RegValue) {
|
func (p GPIO) PortMaskSet() (*avr.Register8, uint8) {
|
||||||
port, mask := p.getPortMask()
|
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
|
// 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
|
// 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
|
// returned mask is only valid as long as no other pin in the same port has been
|
||||||
// changed.
|
// changed.
|
||||||
func (p GPIO) PortMaskClear() (*avr.RegValue, avr.RegValue) {
|
func (p GPIO) PortMaskClear() (*avr.Register8, uint8) {
|
||||||
port, mask := p.getPortMask()
|
port, mask := p.getPortMask()
|
||||||
return port, *port &^ avr.RegValue(mask)
|
return port, port.Get() &^ mask
|
||||||
}
|
}
|
||||||
|
|
||||||
// InitADC initializes the registers needed for ADC.
|
// InitADC initializes the registers needed for ADC.
|
||||||
func InitADC() {
|
func InitADC() {
|
||||||
// set a2d prescaler so we are inside the desired 50-200 KHz range at 16MHz.
|
// 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
|
// 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.
|
// 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
|
// 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. This has the same effect as shifting the return value left by 6
|
||||||
// bits.
|
// 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
|
// start the conversion
|
||||||
*avr.ADCSRA |= avr.ADCSRA_ADSC
|
avr.ADCSRA.SetBits(avr.ADCSRA_ADSC)
|
||||||
|
|
||||||
// ADSC is cleared when the conversion finishes
|
// 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)
|
return uint16(avr.ADCL.Get()) | uint16(avr.ADCH.Get())<<8
|
||||||
high := uint16(*avr.ADCH)
|
|
||||||
return uint16(low) | uint16(high<<8)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// I2C on AVR.
|
// I2C on AVR.
|
||||||
|
|||||||
@@ -185,21 +185,20 @@ func (i2c I2C) Tx(addr uint16, w, r []byte) error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if len(r) != 0 {
|
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
|
i2c.Bus.TASKS_STARTRX = 1 // re-start transmission for reading
|
||||||
for i := range r { // read each char
|
for i := range r { // read each char
|
||||||
if i+1 == len(r) {
|
if i+1 == len(r) {
|
||||||
// The 'stop' signal must be sent before reading back the last
|
// To trigger stop task when last byte is received, set before resume task.
|
||||||
// byte, so that it will be sent by the I2C peripheral right
|
i2c.Bus.SHORTS = nrf.TWI_SHORTS_BB_STOP
|
||||||
// after the last byte has been read.
|
|
||||||
r[i] = i2c.readLastByte()
|
|
||||||
} else {
|
|
||||||
r[i] = i2c.readByte()
|
|
||||||
}
|
}
|
||||||
|
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
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -229,16 +228,6 @@ func (i2c I2C) readByte() byte {
|
|||||||
return byte(i2c.Bus.RXD)
|
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.
|
// SPI on the NRF.
|
||||||
type SPI struct {
|
type SPI struct {
|
||||||
Bus *nrf.SPI_Type
|
Bus *nrf.SPI_Type
|
||||||
|
|||||||
@@ -14,4 +14,5 @@ const (
|
|||||||
portE
|
portE
|
||||||
portF
|
portF
|
||||||
portG
|
portG
|
||||||
|
portH
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -0,0 +1,220 @@
|
|||||||
|
// +build stm32,stm32f407
|
||||||
|
|
||||||
|
package machine
|
||||||
|
|
||||||
|
// Peripheral abstraction layer for the stm32.
|
||||||
|
|
||||||
|
import (
|
||||||
|
"device/arm"
|
||||||
|
"device/stm32"
|
||||||
|
)
|
||||||
|
|
||||||
|
const CPU_FREQUENCY = 168000000
|
||||||
|
|
||||||
|
const (
|
||||||
|
// Mode Flag
|
||||||
|
GPIO_OUTPUT = 0
|
||||||
|
GPIO_INPUT = GPIO_INPUT_PULLDOWN
|
||||||
|
GPIO_INPUT_FLOATING = 1
|
||||||
|
GPIO_INPUT_PULLDOWN = 2
|
||||||
|
GPIO_INPUT_PULLUP = 3
|
||||||
|
|
||||||
|
// for UART
|
||||||
|
GPIO_UART_TX = 4
|
||||||
|
GPIO_UART_RX = 5
|
||||||
|
|
||||||
|
//GPIOx_MODER
|
||||||
|
GPIO_MODE_INPUT = 0
|
||||||
|
GPIO_MODE_GENERAL_OUTPUT = 1
|
||||||
|
GPIO_MODE_ALTERNABTIVE = 2
|
||||||
|
GPIO_MODE_ANALOG = 3
|
||||||
|
|
||||||
|
//GPIOx_OTYPER
|
||||||
|
GPIO_OUTPUT_MODE_PUSH_PULL = 0
|
||||||
|
GPIO_OUTPUT_MODE_OPEN_DRAIN = 1
|
||||||
|
|
||||||
|
// GPIOx_OSPEEDR
|
||||||
|
GPIO_SPEED_LOW = 0
|
||||||
|
GPIO_SPEED_MID = 1
|
||||||
|
GPIO_SPEED_HI = 2
|
||||||
|
GPIO_SPEED_VERY_HI = 3
|
||||||
|
|
||||||
|
// GPIOx_PUPDR
|
||||||
|
GPIO_FLOATING = 0
|
||||||
|
GPIO_PULL_UP = 1
|
||||||
|
GPIO_PULL_DOWN = 2
|
||||||
|
)
|
||||||
|
|
||||||
|
func (p GPIO) getPort() *stm32.GPIO_Type {
|
||||||
|
switch p.Pin / 16 {
|
||||||
|
case 0:
|
||||||
|
return stm32.GPIOA
|
||||||
|
case 1:
|
||||||
|
return stm32.GPIOB
|
||||||
|
case 2:
|
||||||
|
return stm32.GPIOC
|
||||||
|
case 3:
|
||||||
|
return stm32.GPIOD
|
||||||
|
case 4:
|
||||||
|
return stm32.GPIOE
|
||||||
|
case 5:
|
||||||
|
return stm32.GPIOF
|
||||||
|
case 6:
|
||||||
|
return stm32.GPIOG
|
||||||
|
case 7:
|
||||||
|
return stm32.GPIOH
|
||||||
|
case 8:
|
||||||
|
return stm32.GPIOI
|
||||||
|
default:
|
||||||
|
panic("machine: unknown port")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// enableClock enables the clock for this desired GPIO port.
|
||||||
|
func (p GPIO) enableClock() {
|
||||||
|
switch p.Pin / 16 {
|
||||||
|
case 0:
|
||||||
|
stm32.RCC.AHB1ENR |= stm32.RCC_AHB1ENR_GPIOAEN
|
||||||
|
case 1:
|
||||||
|
stm32.RCC.AHB1ENR |= stm32.RCC_AHB1ENR_GPIOBEN
|
||||||
|
case 2:
|
||||||
|
stm32.RCC.AHB1ENR |= stm32.RCC_AHB1ENR_GPIOCEN
|
||||||
|
case 3:
|
||||||
|
stm32.RCC.AHB1ENR |= stm32.RCC_AHB1ENR_GPIODEN
|
||||||
|
case 4:
|
||||||
|
stm32.RCC.AHB1ENR |= stm32.RCC_AHB1ENR_GPIOEEN
|
||||||
|
case 5:
|
||||||
|
stm32.RCC.AHB1ENR |= stm32.RCC_AHB1ENR_GPIOFEN
|
||||||
|
case 6:
|
||||||
|
stm32.RCC.AHB1ENR |= stm32.RCC_AHB1ENR_GPIOGEN
|
||||||
|
case 7:
|
||||||
|
stm32.RCC.AHB1ENR |= stm32.RCC_AHB1ENR_GPIOHEN
|
||||||
|
case 8:
|
||||||
|
stm32.RCC.AHB1ENR |= stm32.RCC_AHB1ENR_GPIOIEN
|
||||||
|
default:
|
||||||
|
panic("machine: unknown port")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Configure this pin with the given configuration.
|
||||||
|
func (p GPIO) Configure(config GPIOConfig) {
|
||||||
|
// Configure the GPIO pin.
|
||||||
|
p.enableClock()
|
||||||
|
port := p.getPort()
|
||||||
|
pin := p.Pin % 16
|
||||||
|
pos := pin * 2
|
||||||
|
|
||||||
|
if config.Mode == GPIO_INPUT_FLOATING {
|
||||||
|
port.MODER = stm32.RegValue((uint32(port.MODER)&^(0x3<<pos) | (uint32(GPIO_MODE_INPUT) << pos)))
|
||||||
|
port.PUPDR = stm32.RegValue((uint32(port.PUPDR)&^(0x3<<pos) | (uint32(GPIO_FLOATING) << pos)))
|
||||||
|
} else if config.Mode == GPIO_INPUT_PULLDOWN {
|
||||||
|
port.MODER = stm32.RegValue((uint32(port.MODER)&^(0x3<<pos) | (uint32(GPIO_MODE_INPUT) << pos)))
|
||||||
|
port.PUPDR = stm32.RegValue((uint32(port.PUPDR)&^(0x3<<pos) | (uint32(GPIO_PULL_DOWN) << pos)))
|
||||||
|
} else if config.Mode == GPIO_INPUT_PULLUP {
|
||||||
|
port.MODER = stm32.RegValue((uint32(port.MODER)&^(0x3<<pos) | (uint32(GPIO_MODE_INPUT) << pos)))
|
||||||
|
port.PUPDR = stm32.RegValue((uint32(port.PUPDR)&^(0x3<<pos) | (uint32(GPIO_PULL_UP) << pos)))
|
||||||
|
} else if config.Mode == GPIO_OUTPUT {
|
||||||
|
port.MODER = stm32.RegValue((uint32(port.MODER)&^(0x3<<pos) | (uint32(GPIO_MODE_GENERAL_OUTPUT) << pos)))
|
||||||
|
port.OSPEEDR = stm32.RegValue((uint32(port.OSPEEDR)&^(0x3<<pos) | (uint32(GPIO_SPEED_HI) << pos)))
|
||||||
|
} else if config.Mode == GPIO_UART_TX {
|
||||||
|
port.MODER = stm32.RegValue((uint32(port.MODER)&^(0x3<<pos) | (uint32(GPIO_MODE_ALTERNABTIVE) << pos)))
|
||||||
|
port.OSPEEDR = stm32.RegValue((uint32(port.OSPEEDR)&^(0x3<<pos) | (uint32(GPIO_SPEED_HI) << pos)))
|
||||||
|
port.PUPDR = stm32.RegValue((uint32(port.PUPDR)&^(0x3<<pos) | (uint32(GPIO_PULL_UP) << pos)))
|
||||||
|
p.setAltFunc(0x7)
|
||||||
|
} else if config.Mode == GPIO_UART_RX {
|
||||||
|
port.MODER = stm32.RegValue((uint32(port.MODER)&^(0x3<<pos) | (uint32(GPIO_MODE_ALTERNABTIVE) << pos)))
|
||||||
|
port.PUPDR = stm32.RegValue((uint32(port.PUPDR)&^(0x3<<pos) | (uint32(GPIO_FLOATING) << pos)))
|
||||||
|
p.setAltFunc(0x7)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p GPIO) setAltFunc(af uint32) {
|
||||||
|
port := p.getPort()
|
||||||
|
pin := p.Pin % 16
|
||||||
|
pos := pin * 4
|
||||||
|
if pin >= 8 {
|
||||||
|
port.AFRH = stm32.RegValue(uint32(port.AFRH)&^(0xF<<pos) | ((af & 0xF) << pos))
|
||||||
|
} else {
|
||||||
|
port.AFRL = stm32.RegValue(uint32(port.AFRL)&^(0xF<<pos) | ((af & 0xF) << pos))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set the pin to high or low.
|
||||||
|
// Warning: only use this on an output pin!
|
||||||
|
func (p GPIO) Set(high bool) {
|
||||||
|
port := p.getPort()
|
||||||
|
pin := p.Pin % 16
|
||||||
|
if high {
|
||||||
|
port.BSRR = 1 << pin
|
||||||
|
} else {
|
||||||
|
port.BSRR = 1 << (pin + 16)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// UART
|
||||||
|
type UART struct {
|
||||||
|
Buffer *RingBuffer
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
// Both UART0 and UART1 refer to USART2.
|
||||||
|
UART0 = UART{Buffer: NewRingBuffer()}
|
||||||
|
UART1 = &UART0
|
||||||
|
)
|
||||||
|
|
||||||
|
// Configure the UART.
|
||||||
|
func (uart UART) Configure(config UARTConfig) {
|
||||||
|
// Default baud rate to 115200.
|
||||||
|
if config.BaudRate == 0 {
|
||||||
|
config.BaudRate = 115200
|
||||||
|
}
|
||||||
|
|
||||||
|
// pins
|
||||||
|
switch config.TX {
|
||||||
|
default:
|
||||||
|
// use standard TX/RX pins PA2 and PA3
|
||||||
|
GPIO{UART_TX_PIN}.Configure(GPIOConfig{Mode: GPIO_UART_TX})
|
||||||
|
GPIO{UART_RX_PIN}.Configure(GPIOConfig{Mode: GPIO_UART_RX})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Enable USART2 clock
|
||||||
|
stm32.RCC.APB1ENR |= stm32.RCC_APB1ENR_USART2EN
|
||||||
|
|
||||||
|
/*
|
||||||
|
Set baud rate(115200)
|
||||||
|
OVER8 = 0, APB2 = 42mhz
|
||||||
|
+----------+--------+
|
||||||
|
| baudrate | BRR |
|
||||||
|
+----------+--------+
|
||||||
|
| 1200 | 0x88B8 |
|
||||||
|
| 2400 | 0x445C |
|
||||||
|
| 9600 | 0x1117 |
|
||||||
|
| 19200 | 0x88C |
|
||||||
|
| 38400 | 0x446 |
|
||||||
|
| 57600 | 0x2D9 |
|
||||||
|
| 115200 | 0x16D |
|
||||||
|
+----------+--------+
|
||||||
|
*/
|
||||||
|
stm32.USART2.BRR = 0x16c
|
||||||
|
|
||||||
|
// Enable USART2 port.
|
||||||
|
stm32.USART2.CR1 = stm32.USART_CR1_TE | stm32.USART_CR1_RE | stm32.USART_CR1_RXNEIE | stm32.USART_CR1_UE
|
||||||
|
|
||||||
|
// Enable RX IRQ.
|
||||||
|
arm.SetPriority(stm32.IRQ_USART2, 0xc0)
|
||||||
|
arm.EnableIRQ(stm32.IRQ_USART2)
|
||||||
|
}
|
||||||
|
|
||||||
|
// WriteByte writes a byte of data to the UART.
|
||||||
|
func (uart UART) WriteByte(c byte) error {
|
||||||
|
stm32.USART2.DR = stm32.RegValue(c)
|
||||||
|
|
||||||
|
for (stm32.USART2.SR & stm32.USART_SR_TXE) == 0 {
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
//go:export USART2_IRQHandler
|
||||||
|
func handleUSART2() {
|
||||||
|
UART1.Receive(byte((stm32.USART2.DR & 0xFF)))
|
||||||
|
}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
// +build wasm
|
// +build avr cortexm wasm
|
||||||
|
|
||||||
package os
|
package os
|
||||||
|
|
||||||
+1
-1
@@ -1,4 +1,4 @@
|
|||||||
// +build darwin linux
|
// +build darwin linux,!avr,!cortexm
|
||||||
|
|
||||||
package os
|
package os
|
||||||
|
|
||||||
|
|||||||
+3
-3
@@ -134,18 +134,18 @@ func (t Type) String() string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (t Type) Kind() Kind {
|
func (t Type) Kind() Kind {
|
||||||
if t % 2 == 0 {
|
if t%2 == 0 {
|
||||||
// basic type
|
// basic type
|
||||||
return Kind((t >> 1) % 32)
|
return Kind((t >> 1) % 32)
|
||||||
} else {
|
} else {
|
||||||
return Kind(t >> 1) % 8 + 19
|
return Kind(t>>1)%8 + 19
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t Type) Elem() Type {
|
func (t Type) Elem() Type {
|
||||||
switch t.Kind() {
|
switch t.Kind() {
|
||||||
case Chan, Ptr, Slice:
|
case Chan, Ptr, Slice:
|
||||||
if (t >> 4) % 2 != 0 {
|
if (t>>4)%2 != 0 {
|
||||||
panic("unimplemented: (reflect.Type).Elem() for named types")
|
panic("unimplemented: (reflect.Type).Elem() for named types")
|
||||||
}
|
}
|
||||||
return t >> 5
|
return t >> 5
|
||||||
|
|||||||
+21
-2
@@ -335,7 +335,7 @@ func (v Value) Index(i int) Value {
|
|||||||
typecode: v.Type().Elem(),
|
typecode: v.Type().Elem(),
|
||||||
indirect: true,
|
indirect: true,
|
||||||
}
|
}
|
||||||
addr := uintptr(slice.Data) + elem.Type().Size() * uintptr(i) // pointer to new value
|
addr := uintptr(slice.Data) + elem.Type().Size()*uintptr(i) // pointer to new value
|
||||||
elem.value = unsafe.Pointer(addr)
|
elem.value = unsafe.Pointer(addr)
|
||||||
return elem
|
return elem
|
||||||
case String:
|
case String:
|
||||||
@@ -348,7 +348,7 @@ func (v Value) Index(i int) Value {
|
|||||||
}
|
}
|
||||||
return Value{
|
return Value{
|
||||||
typecode: Uint8.basicType(),
|
typecode: Uint8.basicType(),
|
||||||
value: unsafe.Pointer(uintptr(*(*uint8)(unsafe.Pointer(s.Data + uintptr(i))))),
|
value: unsafe.Pointer(uintptr(*(*uint8)(unsafe.Pointer(s.Data + uintptr(i))))),
|
||||||
}
|
}
|
||||||
case Array:
|
case Array:
|
||||||
panic("unimplemented: (reflect.Value).Index()")
|
panic("unimplemented: (reflect.Value).Index()")
|
||||||
@@ -365,6 +365,25 @@ func (v Value) MapIndex(key Value) Value {
|
|||||||
panic("unimplemented: (reflect.Value).MapIndex()")
|
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) {
|
func (v Value) Set(x Value) {
|
||||||
if !v.indirect {
|
if !v.indirect {
|
||||||
panic("reflect: value is not addressable")
|
panic("reflect: value is not addressable")
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
// +build arm,!avr,!cortexm
|
||||||
|
|
||||||
package runtime
|
package runtime
|
||||||
|
|
||||||
const GOARCH = "arm"
|
const GOARCH = "arm"
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
// +build tinygo.arm
|
// +build cortexm
|
||||||
|
|
||||||
package runtime
|
package runtime
|
||||||
|
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
// +build wasm,!tinygo.arm,!avr
|
// +build wasm
|
||||||
|
|
||||||
package runtime
|
package runtime
|
||||||
|
|
||||||
|
|||||||
+15
-23
@@ -39,32 +39,27 @@ const (
|
|||||||
chanStateClosed
|
chanStateClosed
|
||||||
)
|
)
|
||||||
|
|
||||||
func chanSendStub(caller *coroutine, ch *channel, _ unsafe.Pointer, size uintptr)
|
func deadlockStub()
|
||||||
func chanRecvStub(caller *coroutine, ch *channel, _ unsafe.Pointer, _ *bool, size uintptr)
|
|
||||||
|
|
||||||
// chanSend sends a single value over the channel. If this operation can
|
// chanSend sends a single value over the channel. If this operation can
|
||||||
// complete immediately (there is a goroutine waiting for a value), it sends the
|
// complete immediately (there is a goroutine waiting for a value), it sends the
|
||||||
// value and re-activates both goroutines. If not, it sets itself as waiting on
|
// value and re-activates both goroutines. If not, it sets itself as waiting on
|
||||||
// a value.
|
// a value.
|
||||||
//
|
func chanSend(sender *coroutine, ch *channel, value unsafe.Pointer, size uintptr) {
|
||||||
// The unsafe.Pointer value is used during lowering. During IR generation, it
|
|
||||||
// points to the to-be-received value. During coroutine lowering, this value is
|
|
||||||
// replaced with a read from the coroutine promise.
|
|
||||||
func chanSend(sender *coroutine, ch *channel, _ unsafe.Pointer, size uintptr) {
|
|
||||||
if ch == nil {
|
if ch == nil {
|
||||||
// A nil channel blocks forever. Do not scheduler this goroutine again.
|
// A nil channel blocks forever. Do not scheduler this goroutine again.
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
switch ch.state {
|
switch ch.state {
|
||||||
case chanStateEmpty:
|
case chanStateEmpty:
|
||||||
|
sender.promise().ptr = value
|
||||||
ch.state = chanStateSend
|
ch.state = chanStateSend
|
||||||
ch.blocked = sender
|
ch.blocked = sender
|
||||||
case chanStateRecv:
|
case chanStateRecv:
|
||||||
receiver := ch.blocked
|
receiver := ch.blocked
|
||||||
receiverPromise := receiver.promise()
|
receiverPromise := receiver.promise()
|
||||||
senderPromise := sender.promise()
|
memcpy(receiverPromise.ptr, value, size)
|
||||||
memcpy(unsafe.Pointer(&receiverPromise.data), unsafe.Pointer(&senderPromise.data), size)
|
receiverPromise.data = 1 // commaOk = true
|
||||||
receiverPromise.commaOk = true
|
|
||||||
ch.blocked = receiverPromise.next
|
ch.blocked = receiverPromise.next
|
||||||
receiverPromise.next = nil
|
receiverPromise.next = nil
|
||||||
activateTask(receiver)
|
activateTask(receiver)
|
||||||
@@ -75,6 +70,7 @@ func chanSend(sender *coroutine, ch *channel, _ unsafe.Pointer, size uintptr) {
|
|||||||
case chanStateClosed:
|
case chanStateClosed:
|
||||||
runtimePanic("send on closed channel")
|
runtimePanic("send on closed channel")
|
||||||
case chanStateSend:
|
case chanStateSend:
|
||||||
|
sender.promise().ptr = value
|
||||||
sender.promise().next = ch.blocked
|
sender.promise().next = ch.blocked
|
||||||
ch.blocked = sender
|
ch.blocked = sender
|
||||||
}
|
}
|
||||||
@@ -84,11 +80,7 @@ func chanSend(sender *coroutine, ch *channel, _ unsafe.Pointer, size uintptr) {
|
|||||||
// sender, it receives the value immediately and re-activates both coroutines.
|
// sender, it receives the value immediately and re-activates both coroutines.
|
||||||
// If not, it sets itself as available for receiving. If the channel is closed,
|
// If not, it sets itself as available for receiving. If the channel is closed,
|
||||||
// it immediately activates itself with a zero value as the result.
|
// it immediately activates itself with a zero value as the result.
|
||||||
//
|
func chanRecv(receiver *coroutine, ch *channel, value unsafe.Pointer, size uintptr) {
|
||||||
// The two unnamed values exist to help during lowering. The unsafe.Pointer
|
|
||||||
// points to the value, and the *bool points to the comma-ok value. Both are
|
|
||||||
// replaced by reads from the coroutine promise.
|
|
||||||
func chanRecv(receiver *coroutine, ch *channel, _ unsafe.Pointer, _ *bool, size uintptr) {
|
|
||||||
if ch == nil {
|
if ch == nil {
|
||||||
// A nil channel blocks forever. Do not scheduler this goroutine again.
|
// A nil channel blocks forever. Do not scheduler this goroutine again.
|
||||||
return
|
return
|
||||||
@@ -96,10 +88,9 @@ func chanRecv(receiver *coroutine, ch *channel, _ unsafe.Pointer, _ *bool, size
|
|||||||
switch ch.state {
|
switch ch.state {
|
||||||
case chanStateSend:
|
case chanStateSend:
|
||||||
sender := ch.blocked
|
sender := ch.blocked
|
||||||
receiverPromise := receiver.promise()
|
|
||||||
senderPromise := sender.promise()
|
senderPromise := sender.promise()
|
||||||
memcpy(unsafe.Pointer(&receiverPromise.data), unsafe.Pointer(&senderPromise.data), size)
|
memcpy(value, senderPromise.ptr, size)
|
||||||
receiverPromise.commaOk = true
|
receiver.promise().data = 1 // commaOk = true
|
||||||
ch.blocked = senderPromise.next
|
ch.blocked = senderPromise.next
|
||||||
senderPromise.next = nil
|
senderPromise.next = nil
|
||||||
activateTask(receiver)
|
activateTask(receiver)
|
||||||
@@ -108,14 +99,15 @@ func chanRecv(receiver *coroutine, ch *channel, _ unsafe.Pointer, _ *bool, size
|
|||||||
ch.state = chanStateEmpty
|
ch.state = chanStateEmpty
|
||||||
}
|
}
|
||||||
case chanStateEmpty:
|
case chanStateEmpty:
|
||||||
|
receiver.promise().ptr = value
|
||||||
ch.state = chanStateRecv
|
ch.state = chanStateRecv
|
||||||
ch.blocked = receiver
|
ch.blocked = receiver
|
||||||
case chanStateClosed:
|
case chanStateClosed:
|
||||||
receiverPromise := receiver.promise()
|
memzero(value, size)
|
||||||
memzero(unsafe.Pointer(&receiverPromise.data), size)
|
receiver.promise().data = 0 // commaOk = false
|
||||||
receiverPromise.commaOk = false
|
|
||||||
activateTask(receiver)
|
activateTask(receiver)
|
||||||
case chanStateRecv:
|
case chanStateRecv:
|
||||||
|
receiver.promise().ptr = value
|
||||||
receiver.promise().next = ch.blocked
|
receiver.promise().next = ch.blocked
|
||||||
ch.blocked = receiver
|
ch.blocked = receiver
|
||||||
}
|
}
|
||||||
@@ -141,8 +133,8 @@ func chanClose(ch *channel, size uintptr) {
|
|||||||
case chanStateRecv:
|
case chanStateRecv:
|
||||||
// The receiver must be re-activated with a zero value.
|
// The receiver must be re-activated with a zero value.
|
||||||
receiverPromise := ch.blocked.promise()
|
receiverPromise := ch.blocked.promise()
|
||||||
memzero(unsafe.Pointer(&receiverPromise.data), size)
|
memzero(receiverPromise.ptr, size)
|
||||||
receiverPromise.commaOk = false
|
receiverPromise.data = 0 // commaOk = false
|
||||||
activateTask(ch.blocked)
|
activateTask(ch.blocked)
|
||||||
ch.state = chanStateClosed
|
ch.state = chanStateClosed
|
||||||
ch.blocked = nil
|
ch.blocked = nil
|
||||||
|
|||||||
@@ -0,0 +1,65 @@
|
|||||||
|
// Copyright 2010 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package runtime
|
||||||
|
|
||||||
|
// inf2one returns a signed 1 if f is an infinity and a signed 0 otherwise.
|
||||||
|
// The sign of the result is the sign of f.
|
||||||
|
func inf2one(f float64) float64 {
|
||||||
|
g := 0.0
|
||||||
|
if isInf(f) {
|
||||||
|
g = 1.0
|
||||||
|
}
|
||||||
|
return copysign(g, f)
|
||||||
|
}
|
||||||
|
|
||||||
|
func complex64div(n complex64, m complex64) complex64 {
|
||||||
|
return complex64(complex128div(complex128(n), complex128(m)))
|
||||||
|
}
|
||||||
|
|
||||||
|
func complex128div(n complex128, m complex128) complex128 {
|
||||||
|
var e, f float64 // complex(e, f) = n/m
|
||||||
|
|
||||||
|
// Algorithm for robust complex division as described in
|
||||||
|
// Robert L. Smith: Algorithm 116: Complex division. Commun. ACM 5(8): 435 (1962).
|
||||||
|
if abs(real(m)) >= abs(imag(m)) {
|
||||||
|
ratio := imag(m) / real(m)
|
||||||
|
denom := real(m) + ratio*imag(m)
|
||||||
|
e = (real(n) + imag(n)*ratio) / denom
|
||||||
|
f = (imag(n) - real(n)*ratio) / denom
|
||||||
|
} else {
|
||||||
|
ratio := real(m) / imag(m)
|
||||||
|
denom := imag(m) + ratio*real(m)
|
||||||
|
e = (real(n)*ratio + imag(n)) / denom
|
||||||
|
f = (imag(n)*ratio - real(n)) / denom
|
||||||
|
}
|
||||||
|
|
||||||
|
if isNaN(e) && isNaN(f) {
|
||||||
|
// Correct final result to infinities and zeros if applicable.
|
||||||
|
// Matches C99: ISO/IEC 9899:1999 - G.5.1 Multiplicative operators.
|
||||||
|
|
||||||
|
a, b := real(n), imag(n)
|
||||||
|
c, d := real(m), imag(m)
|
||||||
|
|
||||||
|
switch {
|
||||||
|
case m == 0 && (!isNaN(a) || !isNaN(b)):
|
||||||
|
e = copysign(inf, c) * a
|
||||||
|
f = copysign(inf, c) * b
|
||||||
|
|
||||||
|
case (isInf(a) || isInf(b)) && isFinite(c) && isFinite(d):
|
||||||
|
a = inf2one(a)
|
||||||
|
b = inf2one(b)
|
||||||
|
e = inf * (a*c + b*d)
|
||||||
|
f = inf * (b*c - a*d)
|
||||||
|
|
||||||
|
case (isInf(c) || isInf(d)) && isFinite(a) && isFinite(b):
|
||||||
|
c = inf2one(c)
|
||||||
|
d = inf2one(d)
|
||||||
|
e = 0 * (a*c + b*d)
|
||||||
|
f = 0 * (b*c - a*d)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return complex(e, f)
|
||||||
|
}
|
||||||
@@ -0,0 +1,53 @@
|
|||||||
|
// Copyright 2017 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package runtime
|
||||||
|
|
||||||
|
import "unsafe"
|
||||||
|
|
||||||
|
var inf = float64frombits(0x7FF0000000000000)
|
||||||
|
|
||||||
|
// isNaN reports whether f is an IEEE 754 ``not-a-number'' value.
|
||||||
|
func isNaN(f float64) (is bool) {
|
||||||
|
// IEEE 754 says that only NaNs satisfy f != f.
|
||||||
|
return f != f
|
||||||
|
}
|
||||||
|
|
||||||
|
// isFinite reports whether f is neither NaN nor an infinity.
|
||||||
|
func isFinite(f float64) bool {
|
||||||
|
return !isNaN(f - f)
|
||||||
|
}
|
||||||
|
|
||||||
|
// isInf reports whether f is an infinity.
|
||||||
|
func isInf(f float64) bool {
|
||||||
|
return !isNaN(f) && !isFinite(f)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Abs returns the absolute value of x.
|
||||||
|
//
|
||||||
|
// Special cases are:
|
||||||
|
// Abs(±Inf) = +Inf
|
||||||
|
// Abs(NaN) = NaN
|
||||||
|
func abs(x float64) float64 {
|
||||||
|
const sign = 1 << 63
|
||||||
|
return float64frombits(float64bits(x) &^ sign)
|
||||||
|
}
|
||||||
|
|
||||||
|
// copysign returns a value with the magnitude
|
||||||
|
// of x and the sign of y.
|
||||||
|
func copysign(x, y float64) float64 {
|
||||||
|
const sign = 1 << 63
|
||||||
|
return float64frombits(float64bits(x)&^sign | float64bits(y)&sign)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Float64bits returns the IEEE 754 binary representation of f.
|
||||||
|
func float64bits(f float64) uint64 {
|
||||||
|
return *(*uint64)(unsafe.Pointer(&f))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Float64frombits returns the floating point number corresponding
|
||||||
|
// the IEEE 754 binary representation b.
|
||||||
|
func float64frombits(b uint64) float64 {
|
||||||
|
return *(*float64)(unsafe.Pointer(&b))
|
||||||
|
}
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
package runtime
|
||||||
|
|
||||||
|
// This file implements some data types that may be useful for some
|
||||||
|
// implementations of func values.
|
||||||
|
|
||||||
|
import (
|
||||||
|
"unsafe"
|
||||||
|
)
|
||||||
|
|
||||||
|
// funcValue is the underlying type of func values, depending on which func
|
||||||
|
// value representation was used.
|
||||||
|
type funcValue struct {
|
||||||
|
context unsafe.Pointer // function context, for closures and bound methods
|
||||||
|
id uintptr // ptrtoint of *funcValueWithSignature before lowering, opaque index (non-0) after lowering
|
||||||
|
}
|
||||||
|
|
||||||
|
// funcValueWithSignature is used before the func lowering pass.
|
||||||
|
type funcValueWithSignature struct {
|
||||||
|
funcPtr uintptr // ptrtoint of the actual function pointer
|
||||||
|
signature *uint8 // pointer to identify this signature (the value is undef)
|
||||||
|
}
|
||||||
|
|
||||||
|
// getFuncPtr is a dummy function that may be used if the func lowering pass is
|
||||||
|
// not used. It is generally too slow but may be a useful fallback to debug the
|
||||||
|
// func lowering pass.
|
||||||
|
func getFuncPtr(val funcValue, signature *uint8) uintptr {
|
||||||
|
return (*funcValueWithSignature)(unsafe.Pointer(val.id)).funcPtr
|
||||||
|
}
|
||||||
@@ -1,5 +1,3 @@
|
|||||||
// +build gc.marksweep
|
|
||||||
|
|
||||||
package runtime
|
package runtime
|
||||||
|
|
||||||
// This memory manager is a textbook mark/sweep implementation, heavily inspired
|
// This memory manager is a textbook mark/sweep implementation, heavily inspired
|
||||||
@@ -168,42 +166,9 @@ func (b gcBlock) unmark() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Initialize the memory allocator.
|
|
||||||
// No memory may be allocated before this is called. That means the runtime and
|
|
||||||
// any packages the runtime depends upon may not allocate memory during package
|
|
||||||
// initialization.
|
|
||||||
func init() {
|
|
||||||
totalSize := heapEnd - heapStart
|
|
||||||
|
|
||||||
// Allocate some memory to keep 2 bits of information about every block.
|
|
||||||
metadataSize := totalSize / (blocksPerStateByte * bytesPerBlock)
|
|
||||||
|
|
||||||
// Align the pool.
|
|
||||||
poolStart = (heapStart + metadataSize + (bytesPerBlock - 1)) &^ (bytesPerBlock - 1)
|
|
||||||
poolEnd := heapEnd &^ (bytesPerBlock - 1)
|
|
||||||
numBlocks := (poolEnd - poolStart) / bytesPerBlock
|
|
||||||
endBlock = gcBlock(numBlocks)
|
|
||||||
if gcDebug {
|
|
||||||
println("heapStart: ", heapStart)
|
|
||||||
println("heapEnd: ", heapEnd)
|
|
||||||
println("total size: ", totalSize)
|
|
||||||
println("metadata size: ", metadataSize)
|
|
||||||
println("poolStart: ", poolStart)
|
|
||||||
println("# of blocks: ", numBlocks)
|
|
||||||
println("# of block states:", metadataSize*blocksPerStateByte)
|
|
||||||
}
|
|
||||||
if gcAsserts && metadataSize*blocksPerStateByte < numBlocks {
|
|
||||||
// sanity check
|
|
||||||
runtimePanic("gc: metadata array is too small")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set all block states to 'free'.
|
|
||||||
memzero(unsafe.Pointer(heapStart), metadataSize)
|
|
||||||
}
|
|
||||||
|
|
||||||
// alloc tries to find some free space on the heap, possibly doing a garbage
|
// alloc tries to find some free space on the heap, possibly doing a garbage
|
||||||
// collection cycle if needed. If no space is free, it panics.
|
// collection cycle if needed. If no space is free, it panics.
|
||||||
func alloc(size uintptr) unsafe.Pointer {
|
func heapAlloc(size uintptr) unsafe.Pointer {
|
||||||
if size == 0 {
|
if size == 0 {
|
||||||
return unsafe.Pointer(&zeroSizedAlloc)
|
return unsafe.Pointer(&zeroSizedAlloc)
|
||||||
}
|
}
|
||||||
@@ -275,53 +240,6 @@ func free(ptr unsafe.Pointer) {
|
|||||||
// TODO: free blocks on request, when the compiler knows they're unused.
|
// TODO: free blocks on request, when the compiler knows they're unused.
|
||||||
}
|
}
|
||||||
|
|
||||||
// GC performs a garbage collection cycle.
|
|
||||||
func GC() {
|
|
||||||
if gcDebug {
|
|
||||||
println("running collection cycle...")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Mark phase: mark all reachable objects, recursively.
|
|
||||||
markRoots(globalsStart, globalsEnd)
|
|
||||||
markRoots(getCurrentStackPointer(), stackTop) // assume a descending stack
|
|
||||||
|
|
||||||
// Sweep phase: free all non-marked objects and unmark marked objects for
|
|
||||||
// the next collection cycle.
|
|
||||||
sweep()
|
|
||||||
|
|
||||||
// Show how much has been sweeped, for debugging.
|
|
||||||
if gcDebug {
|
|
||||||
dumpHeap()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// markRoots reads all pointers from start to end (exclusive) and if they look
|
|
||||||
// like a heap pointer and are unmarked, marks them and scans that object as
|
|
||||||
// well (recursively). The start and end parameters must be valid pointers and
|
|
||||||
// must be aligned.
|
|
||||||
func markRoots(start, end uintptr) {
|
|
||||||
if gcDebug {
|
|
||||||
println("mark from", start, "to", end, int(end-start))
|
|
||||||
}
|
|
||||||
|
|
||||||
for addr := start; addr != end; addr += unsafe.Sizeof(addr) {
|
|
||||||
root := *(*uintptr)(unsafe.Pointer(addr))
|
|
||||||
if looksLikePointer(root) {
|
|
||||||
block := blockFromAddr(root)
|
|
||||||
head := block.findHead()
|
|
||||||
if head.state() != blockStateMark {
|
|
||||||
if gcDebug {
|
|
||||||
println("found unmarked pointer", root, "at address", addr)
|
|
||||||
}
|
|
||||||
head.setState(blockStateMark)
|
|
||||||
next := block.findNext()
|
|
||||||
// TODO: avoid recursion as much as possible
|
|
||||||
markRoots(head.address(), next.address())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Sweep goes through all memory and frees unmarked memory.
|
// Sweep goes through all memory and frees unmarked memory.
|
||||||
func sweep() {
|
func sweep() {
|
||||||
freeCurrentObject := false
|
freeCurrentObject := false
|
||||||
@@ -347,10 +265,8 @@ func sweep() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// looksLikePointer returns whether this could be a pointer. Currently, it
|
// addressOnHeap returns whether this address points into the heap.
|
||||||
// simply returns whether it lies anywhere in the heap. Go allows interior
|
func addressOnHeap(ptr uintptr) bool {
|
||||||
// pointers so we can't check alignment or anything like that.
|
|
||||||
func looksLikePointer(ptr uintptr) bool {
|
|
||||||
return ptr >= poolStart && ptr < heapEnd
|
return ptr >= poolStart && ptr < heapEnd
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,91 @@
|
|||||||
|
// +build gc.conservative
|
||||||
|
|
||||||
|
package runtime
|
||||||
|
|
||||||
|
import (
|
||||||
|
"unsafe"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Initialize the memory allocator.
|
||||||
|
// No memory may be allocated before this is called. That means the runtime and
|
||||||
|
// any packages the runtime depends upon may not allocate memory during package
|
||||||
|
// initialization.
|
||||||
|
func init() {
|
||||||
|
totalSize := heapEnd - heapStart
|
||||||
|
|
||||||
|
// Allocate some memory to keep 2 bits of information about every block.
|
||||||
|
metadataSize := totalSize / (blocksPerStateByte * bytesPerBlock)
|
||||||
|
|
||||||
|
// Align the pool.
|
||||||
|
poolStart = (heapStart + metadataSize + (bytesPerBlock - 1)) &^ (bytesPerBlock - 1)
|
||||||
|
poolEnd := heapEnd &^ (bytesPerBlock - 1)
|
||||||
|
numBlocks := (poolEnd - poolStart) / bytesPerBlock
|
||||||
|
endBlock = gcBlock(numBlocks)
|
||||||
|
if gcDebug {
|
||||||
|
println("heapStart: ", heapStart)
|
||||||
|
println("heapEnd: ", heapEnd)
|
||||||
|
println("total size: ", totalSize)
|
||||||
|
println("metadata size: ", metadataSize)
|
||||||
|
println("poolStart: ", poolStart)
|
||||||
|
println("# of blocks: ", numBlocks)
|
||||||
|
println("# of block states:", metadataSize*blocksPerStateByte)
|
||||||
|
}
|
||||||
|
if gcAsserts && metadataSize*blocksPerStateByte < numBlocks {
|
||||||
|
// sanity check
|
||||||
|
runtimePanic("gc: metadata array is too small")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set all block states to 'free'.
|
||||||
|
memzero(unsafe.Pointer(heapStart), metadataSize)
|
||||||
|
}
|
||||||
|
|
||||||
|
func alloc(size uintptr) unsafe.Pointer {
|
||||||
|
return heapAlloc(size)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GC performs a garbage collection cycle.
|
||||||
|
func GC() {
|
||||||
|
if gcDebug {
|
||||||
|
println("running collection cycle...")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mark phase: mark all reachable objects, recursively.
|
||||||
|
markRoots(globalsStart, globalsEnd)
|
||||||
|
markRoots(getCurrentStackPointer(), stackTop) // assume a descending stack
|
||||||
|
|
||||||
|
// Sweep phase: free all non-marked objects and unmark marked objects for
|
||||||
|
// the next collection cycle.
|
||||||
|
sweep()
|
||||||
|
|
||||||
|
// Show how much has been sweeped, for debugging.
|
||||||
|
if gcDebug {
|
||||||
|
dumpHeap()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// markRoots reads all pointers from start to end (exclusive) and if they look
|
||||||
|
// like a heap pointer and are unmarked, marks them and scans that object as
|
||||||
|
// well (recursively). The start and end parameters must be valid pointers and
|
||||||
|
// must be aligned.
|
||||||
|
func markRoots(start, end uintptr) {
|
||||||
|
if gcDebug {
|
||||||
|
println("mark from", start, "to", end, int(end-start))
|
||||||
|
}
|
||||||
|
|
||||||
|
for addr := start; addr != end; addr += unsafe.Sizeof(addr) {
|
||||||
|
root := *(*uintptr)(unsafe.Pointer(addr))
|
||||||
|
if addressOnHeap(root) {
|
||||||
|
block := blockFromAddr(root)
|
||||||
|
head := block.findHead()
|
||||||
|
if head.state() != blockStateMark {
|
||||||
|
if gcDebug {
|
||||||
|
println("found unmarked pointer", root, "at address", addr)
|
||||||
|
}
|
||||||
|
head.setState(blockStateMark)
|
||||||
|
next := block.findNext()
|
||||||
|
// TODO: avoid recursion as much as possible
|
||||||
|
markRoots(head.address(), next.address())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
// +build gc.dumb
|
// +build gc.leaking
|
||||||
|
|
||||||
package runtime
|
package runtime
|
||||||
|
|
||||||
@@ -30,18 +30,6 @@ func alloc(size uintptr) unsafe.Pointer {
|
|||||||
return unsafe.Pointer(addr)
|
return unsafe.Pointer(addr)
|
||||||
}
|
}
|
||||||
|
|
||||||
func free(ptr unsafe.Pointer) {
|
|
||||||
// Memory is never freed.
|
|
||||||
}
|
|
||||||
|
|
||||||
func GC() {
|
func GC() {
|
||||||
// No-op.
|
// No-op.
|
||||||
}
|
}
|
||||||
|
|
||||||
func KeepAlive(x interface{}) {
|
|
||||||
// Unimplemented. Only required with SetFinalizer().
|
|
||||||
}
|
|
||||||
|
|
||||||
func SetFinalizer(obj interface{}, finalizer interface{}) {
|
|
||||||
// Unimplemented.
|
|
||||||
}
|
|
||||||
@@ -12,18 +12,6 @@ import (
|
|||||||
|
|
||||||
func alloc(size uintptr) unsafe.Pointer
|
func alloc(size uintptr) unsafe.Pointer
|
||||||
|
|
||||||
func free(ptr unsafe.Pointer) {
|
|
||||||
// Nothing to free when nothing gets allocated.
|
|
||||||
}
|
|
||||||
|
|
||||||
func GC() {
|
func GC() {
|
||||||
// Unimplemented.
|
// Unimplemented.
|
||||||
}
|
}
|
||||||
|
|
||||||
func KeepAlive(x interface{}) {
|
|
||||||
// Unimplemented. Only required with SetFinalizer().
|
|
||||||
}
|
|
||||||
|
|
||||||
func SetFinalizer(obj interface{}, finalizer interface{}) {
|
|
||||||
// Unimplemented.
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -0,0 +1,116 @@
|
|||||||
|
// +build gc.precise
|
||||||
|
|
||||||
|
package runtime
|
||||||
|
|
||||||
|
import (
|
||||||
|
"unsafe"
|
||||||
|
)
|
||||||
|
|
||||||
|
//go:extern runtime.trackedGlobalsStart
|
||||||
|
var trackedGlobalsStart uintptr
|
||||||
|
|
||||||
|
//go:extern runtime.trackedGlobalsLength
|
||||||
|
var trackedGlobalsLength uintptr
|
||||||
|
|
||||||
|
//go:extern runtime.trackedGlobalsBitmap
|
||||||
|
var trackedGlobalsBitmap [0]uint8
|
||||||
|
|
||||||
|
// Initialize the memory allocator.
|
||||||
|
// No memory may be allocated before this is called. That means the runtime and
|
||||||
|
// any packages the runtime depends upon may not allocate memory during package
|
||||||
|
// initialization.
|
||||||
|
func init() {
|
||||||
|
totalSize := heapEnd - heapStart
|
||||||
|
|
||||||
|
// Allocate some memory to keep 2 bits of information about every block.
|
||||||
|
metadataSize := totalSize / (blocksPerStateByte * bytesPerBlock)
|
||||||
|
|
||||||
|
// Align the pool.
|
||||||
|
poolStart = (heapStart + metadataSize + (bytesPerBlock - 1)) &^ (bytesPerBlock - 1)
|
||||||
|
poolEnd := heapEnd &^ (bytesPerBlock - 1)
|
||||||
|
numBlocks := (poolEnd - poolStart) / bytesPerBlock
|
||||||
|
endBlock = gcBlock(numBlocks)
|
||||||
|
if gcDebug {
|
||||||
|
println("heapStart: ", heapStart)
|
||||||
|
println("heapEnd: ", heapEnd)
|
||||||
|
println("total size: ", totalSize)
|
||||||
|
println("metadata size: ", metadataSize)
|
||||||
|
println("poolStart: ", poolStart)
|
||||||
|
println("# of blocks: ", numBlocks)
|
||||||
|
println("# of block states:", metadataSize*blocksPerStateByte)
|
||||||
|
}
|
||||||
|
if gcAsserts && metadataSize*blocksPerStateByte < numBlocks {
|
||||||
|
// sanity check
|
||||||
|
runtimePanic("gc: metadata array is too small")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set all block states to 'free'.
|
||||||
|
memzero(unsafe.Pointer(heapStart), metadataSize)
|
||||||
|
}
|
||||||
|
|
||||||
|
func alloc(size uintptr) unsafe.Pointer {
|
||||||
|
GC()
|
||||||
|
return heapAlloc(size)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GC performs a garbage collection cycle.
|
||||||
|
func GC() {
|
||||||
|
if gcDebug {
|
||||||
|
println("\nrunning collection cycle...")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mark phase: mark all reachable objects, recursively.
|
||||||
|
markGlobals()
|
||||||
|
markRoots(getCurrentStackPointer(), stackTop) // assume a descending stack
|
||||||
|
|
||||||
|
// Sweep phase: free all non-marked objects and unmark marked objects for
|
||||||
|
// the next collection cycle.
|
||||||
|
sweep()
|
||||||
|
|
||||||
|
// Show how much has been sweeped, for debugging.
|
||||||
|
if gcDebug {
|
||||||
|
dumpHeap()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//go:nobounds
|
||||||
|
func markGlobals() {
|
||||||
|
for i := uintptr(0); i < trackedGlobalsLength; i++ {
|
||||||
|
if trackedGlobalsBitmap[i/8]&(1<<(i%8)) != 0 {
|
||||||
|
addr := trackedGlobalsStart + i*unsafe.Alignof(uintptr(0))
|
||||||
|
root := *(*uintptr)(unsafe.Pointer(addr))
|
||||||
|
markRoot(addr, root)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// markRoots reads all pointers from start to end (exclusive) and if they look
|
||||||
|
// like a heap pointer and are unmarked, marks them and scans that object as
|
||||||
|
// well (recursively). The start and end parameters must be valid pointers and
|
||||||
|
// must be aligned.
|
||||||
|
func markRoots(start, end uintptr) {
|
||||||
|
if gcDebug {
|
||||||
|
println("mark from", start, "to", end, int(end-start))
|
||||||
|
}
|
||||||
|
|
||||||
|
for addr := start; addr != end; addr += unsafe.Sizeof(addr) {
|
||||||
|
root := *(*uintptr)(unsafe.Pointer(addr))
|
||||||
|
markRoot(addr, root)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func markRoot(addr, root uintptr) {
|
||||||
|
if addressOnHeap(root) {
|
||||||
|
block := blockFromAddr(root)
|
||||||
|
head := block.findHead()
|
||||||
|
if head.state() != blockStateMark {
|
||||||
|
if gcDebug {
|
||||||
|
println("found unmarked pointer", root, "at address", addr)
|
||||||
|
}
|
||||||
|
head.setState(blockStateMark)
|
||||||
|
next := block.findNext()
|
||||||
|
// TODO: avoid recursion as much as possible
|
||||||
|
markRoots(head.address(), next.address())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+49
-15
@@ -60,14 +60,20 @@ func hashmapTopHash(hash uint32) uint8 {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Create a new hashmap with the given keySize and valueSize.
|
// Create a new hashmap with the given keySize and valueSize.
|
||||||
func hashmapMake(keySize, valueSize uint8) *hashmap {
|
func hashmapMake(keySize, valueSize uint8, sizeHint uintptr) *hashmap {
|
||||||
|
numBuckets := sizeHint / 8
|
||||||
|
bucketBits := uint8(0)
|
||||||
|
for numBuckets != 0 {
|
||||||
|
numBuckets /= 2
|
||||||
|
bucketBits++
|
||||||
|
}
|
||||||
bucketBufSize := unsafe.Sizeof(hashmapBucket{}) + uintptr(keySize)*8 + uintptr(valueSize)*8
|
bucketBufSize := unsafe.Sizeof(hashmapBucket{}) + uintptr(keySize)*8 + uintptr(valueSize)*8
|
||||||
bucket := alloc(bucketBufSize)
|
buckets := alloc(bucketBufSize * (1 << bucketBits))
|
||||||
return &hashmap{
|
return &hashmap{
|
||||||
buckets: bucket,
|
buckets: buckets,
|
||||||
keySize: keySize,
|
keySize: keySize,
|
||||||
valueSize: valueSize,
|
valueSize: valueSize,
|
||||||
bucketBits: 0,
|
bucketBits: bucketBits,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -83,13 +89,20 @@ func hashmapLen(m *hashmap) int {
|
|||||||
// Set a specified key to a given value. Grow the map if necessary.
|
// Set a specified key to a given value. Grow the map if necessary.
|
||||||
//go:nobounds
|
//go:nobounds
|
||||||
func hashmapSet(m *hashmap, key unsafe.Pointer, value unsafe.Pointer, hash uint32, keyEqual func(x, y unsafe.Pointer, n uintptr) bool) {
|
func hashmapSet(m *hashmap, key unsafe.Pointer, value unsafe.Pointer, hash uint32, keyEqual func(x, y unsafe.Pointer, n uintptr) bool) {
|
||||||
|
tophash := hashmapTopHash(hash)
|
||||||
|
|
||||||
|
if m.buckets == nil {
|
||||||
|
// No bucket was allocated yet, do so now.
|
||||||
|
m.buckets = unsafe.Pointer(hashmapInsertIntoNewBucket(m, key, value, tophash))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
numBuckets := uintptr(1) << m.bucketBits
|
numBuckets := uintptr(1) << m.bucketBits
|
||||||
bucketNumber := (uintptr(hash) & (numBuckets - 1))
|
bucketNumber := (uintptr(hash) & (numBuckets - 1))
|
||||||
bucketSize := unsafe.Sizeof(hashmapBucket{}) + uintptr(m.keySize)*8 + uintptr(m.valueSize)*8
|
bucketSize := unsafe.Sizeof(hashmapBucket{}) + uintptr(m.keySize)*8 + uintptr(m.valueSize)*8
|
||||||
bucketAddr := uintptr(m.buckets) + bucketSize*bucketNumber
|
bucketAddr := uintptr(m.buckets) + bucketSize*bucketNumber
|
||||||
bucket := (*hashmapBucket)(unsafe.Pointer(bucketAddr))
|
bucket := (*hashmapBucket)(unsafe.Pointer(bucketAddr))
|
||||||
|
var lastBucket *hashmapBucket
|
||||||
tophash := hashmapTopHash(hash)
|
|
||||||
|
|
||||||
// See whether the key already exists somewhere.
|
// See whether the key already exists somewhere.
|
||||||
var emptySlotKey unsafe.Pointer
|
var emptySlotKey unsafe.Pointer
|
||||||
@@ -98,9 +111,9 @@ func hashmapSet(m *hashmap, key unsafe.Pointer, value unsafe.Pointer, hash uint3
|
|||||||
for bucket != nil {
|
for bucket != nil {
|
||||||
for i := uintptr(0); i < 8; i++ {
|
for i := uintptr(0); i < 8; i++ {
|
||||||
slotKeyOffset := unsafe.Sizeof(hashmapBucket{}) + uintptr(m.keySize)*uintptr(i)
|
slotKeyOffset := unsafe.Sizeof(hashmapBucket{}) + uintptr(m.keySize)*uintptr(i)
|
||||||
slotKey := unsafe.Pointer(bucketAddr + slotKeyOffset)
|
slotKey := unsafe.Pointer(uintptr(unsafe.Pointer(bucket)) + slotKeyOffset)
|
||||||
slotValueOffset := unsafe.Sizeof(hashmapBucket{}) + uintptr(m.keySize)*8 + uintptr(m.valueSize)*uintptr(i)
|
slotValueOffset := unsafe.Sizeof(hashmapBucket{}) + uintptr(m.keySize)*8 + uintptr(m.valueSize)*uintptr(i)
|
||||||
slotValue := unsafe.Pointer(bucketAddr + slotValueOffset)
|
slotValue := unsafe.Pointer(uintptr(unsafe.Pointer(bucket)) + slotValueOffset)
|
||||||
if bucket.tophash[i] == 0 && emptySlotKey == nil {
|
if bucket.tophash[i] == 0 && emptySlotKey == nil {
|
||||||
// Found an empty slot, store it for if we couldn't find an
|
// Found an empty slot, store it for if we couldn't find an
|
||||||
// existing slot.
|
// existing slot.
|
||||||
@@ -109,7 +122,7 @@ func hashmapSet(m *hashmap, key unsafe.Pointer, value unsafe.Pointer, hash uint3
|
|||||||
emptySlotTophash = &bucket.tophash[i]
|
emptySlotTophash = &bucket.tophash[i]
|
||||||
}
|
}
|
||||||
if bucket.tophash[i] == tophash {
|
if bucket.tophash[i] == tophash {
|
||||||
// Could be an existing value that's the same.
|
// Could be an existing key that's the same.
|
||||||
if keyEqual(key, slotKey, uintptr(m.keySize)) {
|
if keyEqual(key, slotKey, uintptr(m.keySize)) {
|
||||||
// found same key, replace it
|
// found same key, replace it
|
||||||
memcpy(slotValue, value, uintptr(m.valueSize))
|
memcpy(slotValue, value, uintptr(m.valueSize))
|
||||||
@@ -117,16 +130,37 @@ func hashmapSet(m *hashmap, key unsafe.Pointer, value unsafe.Pointer, hash uint3
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
lastBucket = bucket
|
||||||
bucket = bucket.next
|
bucket = bucket.next
|
||||||
}
|
}
|
||||||
if emptySlotKey != nil {
|
if emptySlotKey == nil {
|
||||||
m.count++
|
// Add a new bucket to the bucket chain.
|
||||||
memcpy(emptySlotKey, key, uintptr(m.keySize))
|
// TODO: rebalance if necessary to avoid O(n) insert and lookup time.
|
||||||
memcpy(emptySlotValue, value, uintptr(m.valueSize))
|
lastBucket.next = (*hashmapBucket)(hashmapInsertIntoNewBucket(m, key, value, tophash))
|
||||||
*emptySlotTophash = tophash
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
panic("todo: hashmap: grow bucket")
|
m.count++
|
||||||
|
memcpy(emptySlotKey, key, uintptr(m.keySize))
|
||||||
|
memcpy(emptySlotValue, value, uintptr(m.valueSize))
|
||||||
|
*emptySlotTophash = tophash
|
||||||
|
}
|
||||||
|
|
||||||
|
// hashmapInsertIntoNewBucket creates a new bucket, inserts the given key and
|
||||||
|
// value into the bucket, and returns a pointer to this bucket.
|
||||||
|
func hashmapInsertIntoNewBucket(m *hashmap, key, value unsafe.Pointer, tophash uint8) *hashmapBucket {
|
||||||
|
bucketBufSize := unsafe.Sizeof(hashmapBucket{}) + uintptr(m.keySize)*8 + uintptr(m.valueSize)*8
|
||||||
|
bucketBuf := alloc(bucketBufSize)
|
||||||
|
// Insert into the first slot, which is empty as it has just been allocated.
|
||||||
|
slotKeyOffset := unsafe.Sizeof(hashmapBucket{})
|
||||||
|
slotKey := unsafe.Pointer(uintptr(bucketBuf) + slotKeyOffset)
|
||||||
|
slotValueOffset := unsafe.Sizeof(hashmapBucket{}) + uintptr(m.keySize)*8
|
||||||
|
slotValue := unsafe.Pointer(uintptr(bucketBuf) + slotValueOffset)
|
||||||
|
m.count++
|
||||||
|
memcpy(slotKey, key, uintptr(m.keySize))
|
||||||
|
memcpy(slotValue, value, uintptr(m.valueSize))
|
||||||
|
bucket := (*hashmapBucket)(bucketBuf)
|
||||||
|
bucket.tophash[0] = tophash
|
||||||
|
return bucket
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get the value of a specified key, or zero the value if not found.
|
// Get the value of a specified key, or zero the value if not found.
|
||||||
|
|||||||
@@ -43,15 +43,22 @@ type interfaceMethodInfo struct {
|
|||||||
funcptr uintptr // bitcast from the actual function pointer
|
funcptr uintptr // bitcast from the actual function pointer
|
||||||
}
|
}
|
||||||
|
|
||||||
// Pseudo function call used while putting a concrete value in an interface,
|
type typecodeID struct{}
|
||||||
// that must be lowered to a constant uintptr.
|
|
||||||
func makeInterface(typecode *uint8, methodSet *interfaceMethodInfo) uintptr
|
// Pseudo type used before interface lowering. By using a struct instead of a
|
||||||
|
// function call, this is simpler to reason about during init interpretation
|
||||||
|
// than a function call. Also, by keeping the method set around it is easier to
|
||||||
|
// implement interfaceImplements in the interp package.
|
||||||
|
type typeInInterface struct {
|
||||||
|
typecode *typecodeID
|
||||||
|
methodSet *interfaceMethodInfo // nil or a GEP of an array
|
||||||
|
}
|
||||||
|
|
||||||
// Pseudo function call used during a type assert. It is used during interface
|
// Pseudo function call used during a type assert. It is used during interface
|
||||||
// lowering, to assign the lowest type numbers to the types with the most type
|
// lowering, to assign the lowest type numbers to the types with the most type
|
||||||
// asserts. Also, it is replaced with const false if this type assert can never
|
// asserts. Also, it is replaced with const false if this type assert can never
|
||||||
// happen.
|
// happen.
|
||||||
func typeAssert(actualType uintptr, assertedType *uint8) bool
|
func typeAssert(actualType uintptr, assertedType *typecodeID) bool
|
||||||
|
|
||||||
// Pseudo function call that returns whether a given type implements all methods
|
// Pseudo function call that returns whether a given type implements all methods
|
||||||
// of the given interface.
|
// of the given interface.
|
||||||
|
|||||||
@@ -1 +1,3 @@
|
|||||||
TheVersion = `go0.1.0`
|
package sys
|
||||||
|
|
||||||
|
const TheVersion = `go0.1.0`
|
||||||
|
|||||||
@@ -1,28 +0,0 @@
|
|||||||
// +build js,tinygo.arm avr
|
|
||||||
|
|
||||||
package runtime
|
|
||||||
|
|
||||||
// This file stubs out some external functions declared by the syscall/js
|
|
||||||
// package. They cannot be used on microcontrollers.
|
|
||||||
|
|
||||||
type js_ref uint64
|
|
||||||
|
|
||||||
//go:linkname js_valueGet syscall/js.valueGet
|
|
||||||
func js_valueGet(v js_ref, p string) js_ref {
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
//go:linkname js_valueNew syscall/js.valueNew
|
|
||||||
func js_valueNew(v js_ref, args []js_ref) (js_ref, bool) {
|
|
||||||
return 0, true
|
|
||||||
}
|
|
||||||
|
|
||||||
//go:linkname js_valueCall syscall/js.valueCall
|
|
||||||
func js_valueCall(v js_ref, m string, args []js_ref) (js_ref, bool) {
|
|
||||||
return 0, true
|
|
||||||
}
|
|
||||||
|
|
||||||
//go:linkname js_stringVal syscall/js.stringVal
|
|
||||||
func js_stringVal(x string) js_ref {
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
+8
-14
@@ -27,6 +27,14 @@ func _recover() interface{} {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// See emitNilCheck in compiler/asserts.go.
|
||||||
|
// This function is a dummy function that has its first and only parameter
|
||||||
|
// marked 'nocapture' to work around a limitation in LLVM: a regular pointer
|
||||||
|
// comparison captures the pointer.
|
||||||
|
func isnil(ptr *uint8) bool {
|
||||||
|
return ptr == nil
|
||||||
|
}
|
||||||
|
|
||||||
// Panic when trying to dereference a nil pointer.
|
// Panic when trying to dereference a nil pointer.
|
||||||
func nilpanic() {
|
func nilpanic() {
|
||||||
runtimePanic("nil pointer dereference")
|
runtimePanic("nil pointer dereference")
|
||||||
@@ -41,17 +49,3 @@ func lookuppanic() {
|
|||||||
func slicepanic() {
|
func slicepanic() {
|
||||||
runtimePanic("slice out of range")
|
runtimePanic("slice out of range")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check for bounds in *ssa.MakeSlice.
|
|
||||||
func sliceBoundsCheckMake(length, capacity uintptr, max uintptr) {
|
|
||||||
if length > capacity || capacity > max {
|
|
||||||
runtimePanic("slice size out of range")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check for bounds in *ssa.MakeSlice. Supports 64-bit indexes.
|
|
||||||
func sliceBoundsCheckMake64(length, capacity uint64, max uintptr) {
|
|
||||||
if length > capacity || capacity > uint64(max) {
|
|
||||||
runtimePanic("slice size out of range")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user