mirror of
https://github.com/tinygo-org/tinygo.git
synced 2026-07-29 16:18:41 +00:00
Compare commits
131 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 8a771e3f89 | |||
| 5ad251b2bd | |||
| 5324fb7a40 | |||
| 7369a0e7f2 | |||
| 8906192690 | |||
| 20a55e7944 | |||
| a2c7112b1f | |||
| 3eec878a11 | |||
| e6cab6c327 | |||
| 2463153a8c | |||
| 2a71aa90bc | |||
| 52bac4d75b | |||
| be9e4f439c | |||
| ba49148644 | |||
| 02facb8568 | |||
| 4164c39a4a | |||
| 832f301a74 | |||
| 2168b1b516 | |||
| 92e07ec8af | |||
| be40c383c8 | |||
| b730590059 | |||
| fb1a476033 | |||
| 4397152108 | |||
| d266e44bc5 | |||
| 8fe126b1f6 | |||
| ab50f823dd | |||
| 3fa926c512 | |||
| 01e58691a1 | |||
| 81c0f9af4e | |||
| b5ecd9cab0 | |||
| 9aace79298 | |||
| c16e07469b | |||
| 582457b81e | |||
| 4ea1559d46 | |||
| da7f7eef00 | |||
| 17ef7a5c32 | |||
| 6c9e55bd06 | |||
| 368abeeb9a | |||
| 076699add8 | |||
| d843ebfe40 | |||
| d17f500c8b | |||
| 65beddafe8 | |||
| cea0d9f864 | |||
| 1913e3e1cd | |||
| 9d47897368 | |||
| a7d00f1b6d | |||
| 7732c6f449 | |||
| cf2a7b5089 | |||
| d40fb5bc46 | |||
| cc313798f3 | |||
| 86acfc7ef4 | |||
| a5fb785334 | |||
| 86b888fdcb | |||
| ef1bfe359c | |||
| 0a5601c3fc | |||
| f5fd49bb74 | |||
| d2856bd6bd | |||
| 869d2c4524 | |||
| 9d35c1197f | |||
| 688dd81400 | |||
| abca3132a9 | |||
| e0ebc75df2 | |||
| b4859240e1 | |||
| 4eb34b36f8 | |||
| 61750be9aa | |||
| efd9cf72ba | |||
| 500ee8067d | |||
| 8cd2c7502e | |||
| d905476231 | |||
| 10ed3decb0 | |||
| 8d959b7c63 | |||
| 02c4020228 | |||
| bc41cc688f | |||
| 7d481c179f | |||
| 69aaea44a0 | |||
| 6c1abfe047 | |||
| 656fb4e372 | |||
| f4485f414f | |||
| 55144ad608 | |||
| 1dbfc976e8 | |||
| e26f0b35e3 | |||
| 4ee26fd54b | |||
| a718b10502 | |||
| 8a5fa51f60 | |||
| 7c5f943564 | |||
| 7938740bf3 | |||
| 319d21e662 | |||
| 3bf2487dc5 | |||
| 6917faabf5 | |||
| db4de46d88 | |||
| 9b1dd8e03d | |||
| e356bad4d1 | |||
| c19c738f52 | |||
| bbc3046687 | |||
| e4fc3bb66a | |||
| 542135c357 | |||
| 61f711ef26 | |||
| fd3309afa8 | |||
| fea56d4164 | |||
| ea8e4079bc | |||
| 0818a125c0 | |||
| 7c758b0dfc | |||
| 4ea59742d7 | |||
| b7cb10519c | |||
| 562ad740da | |||
| 92206558fb | |||
| fd6b671494 | |||
| e2c8654237 | |||
| 5012be337f | |||
| 614433cb75 | |||
| b8cd8b6f25 | |||
| f43d01bdc7 | |||
| 9979cf2bbd | |||
| 79b470acee | |||
| 0b7047cc0e | |||
| 99add1571b | |||
| b7f4373681 | |||
| 95721a8d8c | |||
| 33dc4b5121 | |||
| a04db67ea9 | |||
| 9846c062b3 | |||
| 9878f7ebc4 | |||
| 4688664b41 | |||
| 54169c714f | |||
| f76385850d | |||
| cb09531873 | |||
| 51f2a152d5 | |||
| c3880b3ec5 | |||
| 7d5542dda7 | |||
| 8eb6039052 | |||
| 64597de344 |
+101
-12
@@ -61,19 +61,85 @@ commands:
|
||||
- install-node
|
||||
- restore_cache:
|
||||
keys:
|
||||
- go-cache-{{ checksum "Gopkg.lock" }}-{{ .Environment.CIRCLE_PREVIOUS_BUILD_NUM }}
|
||||
- go-cache-{{ checksum "Gopkg.lock" }}
|
||||
- go-cache-v2-{{ checksum "go.mod" }}-{{ .Environment.CIRCLE_PREVIOUS_BUILD_NUM }}
|
||||
- go-cache-v2-{{ checksum "go.mod" }}
|
||||
- llvm-source-linux
|
||||
- run: go install .
|
||||
- run: go test -v
|
||||
- run: go test -v ./interp ./transform .
|
||||
- run: make gen-device -j4
|
||||
- run: make smoketest RISCV=0
|
||||
- save_cache:
|
||||
key: go-cache-{{ checksum "Gopkg.lock" }}-{{ .Environment.CIRCLE_BUILD_NUM }}
|
||||
key: go-cache-v2-{{ checksum "go.mod" }}-{{ .Environment.CIRCLE_BUILD_NUM }}
|
||||
paths:
|
||||
- ~/.cache/go-build
|
||||
- ~/.cache/tinygo
|
||||
- /go/pkg/mod
|
||||
- run: make fmt-check
|
||||
assert-test-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-v2-{{ checksum "go.mod" }}-{{ .Environment.CIRCLE_PREVIOUS_BUILD_NUM }}
|
||||
- go-cache-v2-{{ checksum "go.mod" }}
|
||||
- llvm-source-linux
|
||||
- restore_cache:
|
||||
keys:
|
||||
- llvm-build-8-linux-v7-assert
|
||||
- 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 ASSERT=1 llvm-build
|
||||
fi
|
||||
- save_cache:
|
||||
key: llvm-build-8-linux-v7-assert
|
||||
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
|
||||
- run: make ASSERT=1
|
||||
- run:
|
||||
name: "Test TinyGo"
|
||||
command: make ASSERT=1 test
|
||||
- save_cache:
|
||||
key: go-cache-v2-{{ checksum "go.mod" }}-{{ .Environment.CIRCLE_BUILD_NUM }}
|
||||
paths:
|
||||
- ~/.cache/go-build
|
||||
- ~/.cache/tinygo
|
||||
- /go/pkg/mod
|
||||
- run: make gen-device -j4
|
||||
- run: make smoketest TINYGO=build/tinygo RISCV=0
|
||||
build-linux:
|
||||
steps:
|
||||
- checkout
|
||||
@@ -95,8 +161,8 @@ commands:
|
||||
- install-node
|
||||
- restore_cache:
|
||||
keys:
|
||||
- go-cache-{{ checksum "Gopkg.lock" }}-{{ .Environment.CIRCLE_PREVIOUS_BUILD_NUM }}
|
||||
- go-cache-{{ checksum "Gopkg.lock" }}
|
||||
- go-cache-v2-{{ checksum "go.mod" }}-{{ .Environment.CIRCLE_PREVIOUS_BUILD_NUM }}
|
||||
- go-cache-v2-{{ checksum "go.mod" }}
|
||||
- llvm-source-linux
|
||||
- restore_cache:
|
||||
keys:
|
||||
@@ -138,10 +204,11 @@ commands:
|
||||
- store_artifacts:
|
||||
path: /tmp/tinygo.linux-amd64.tar.gz
|
||||
- save_cache:
|
||||
key: go-cache-{{ checksum "Gopkg.lock" }}-{{ .Environment.CIRCLE_BUILD_NUM }}
|
||||
key: go-cache-v2-{{ checksum "go.mod" }}-{{ .Environment.CIRCLE_BUILD_NUM }}
|
||||
paths:
|
||||
- ~/.cache/go-build
|
||||
- ~/.cache/tinygo
|
||||
- /go/pkg/mod
|
||||
- run:
|
||||
name: "Extract release tarball"
|
||||
command: |
|
||||
@@ -162,10 +229,14 @@ commands:
|
||||
- run:
|
||||
name: "Install dependencies"
|
||||
command: |
|
||||
curl https://dl.google.com/go/go1.12.5.darwin-amd64.tar.gz -o go1.12.5.darwin-amd64.tar.gz
|
||||
sudo tar -C /usr/local -xzf go1.12.5.darwin-amd64.tar.gz
|
||||
curl https://dl.google.com/go/go1.13.darwin-amd64.tar.gz -o go1.13.darwin-amd64.tar.gz
|
||||
sudo tar -C /usr/local -xzf go1.13.darwin-amd64.tar.gz
|
||||
ln -s /usr/local/go/bin/go /usr/local/bin/go
|
||||
HOMEBREW_NO_AUTO_UPDATE=1 brew install qemu
|
||||
- restore_cache:
|
||||
keys:
|
||||
- go-cache-macos-v2-{{ checksum "go.mod" }}-{{ .Environment.CIRCLE_PREVIOUS_BUILD_NUM }}
|
||||
- go-cache-macos-v2-{{ checksum "go.mod" }}
|
||||
- restore_cache:
|
||||
keys:
|
||||
- llvm-source-8-macos-v5
|
||||
@@ -220,22 +291,38 @@ commands:
|
||||
curl -O https://static.dev.sifive.com/dev-tools/riscv64-unknown-elf-gcc-8.2.0-2019.05.3-x86_64-apple-darwin.tar.gz
|
||||
sudo tar -C /usr/local --strip-components=1 -xf riscv64-unknown-elf-gcc-8.2.0-2019.05.3-x86_64-apple-darwin.tar.gz
|
||||
- run: make smoketest AVR=0
|
||||
- save_cache:
|
||||
key: go-cache-macos-v2-{{ checksum "go.mod" }}-{{ .Environment.CIRCLE_BUILD_NUM }}
|
||||
paths:
|
||||
- ~/.cache/go-build
|
||||
- ~/.cache/tinygo
|
||||
- /go/pkg/mod
|
||||
|
||||
|
||||
jobs:
|
||||
test-llvm8-go111:
|
||||
docker:
|
||||
- image: circleci/golang:1.11
|
||||
- image: circleci/golang:1.11-stretch
|
||||
steps:
|
||||
- test-linux
|
||||
test-llvm8-go112:
|
||||
docker:
|
||||
- image: circleci/golang:1.12
|
||||
- image: circleci/golang:1.12-stretch
|
||||
steps:
|
||||
- test-linux
|
||||
test-llvm8-go113:
|
||||
docker:
|
||||
- image: circleci/golang:1.13-stretch
|
||||
steps:
|
||||
- test-linux
|
||||
assert-test-linux:
|
||||
docker:
|
||||
- image: circleci/golang:1.13-stretch
|
||||
steps:
|
||||
- assert-test-linux
|
||||
build-linux:
|
||||
docker:
|
||||
- image: circleci/golang:1.12
|
||||
- image: circleci/golang:1.13-stretch
|
||||
steps:
|
||||
- build-linux
|
||||
build-macos:
|
||||
@@ -252,5 +339,7 @@ workflows:
|
||||
jobs:
|
||||
- test-llvm8-go111
|
||||
- test-llvm8-go112
|
||||
- test-llvm8-go113
|
||||
- build-linux
|
||||
- build-macos
|
||||
- assert-test-linux
|
||||
|
||||
+1
-1
@@ -12,5 +12,5 @@ src/device/sifive/*.s
|
||||
src/device/stm32/*.go
|
||||
src/device/stm32/*.s
|
||||
vendor
|
||||
llvm
|
||||
llvm-build
|
||||
llvm-project
|
||||
|
||||
+7
-4
@@ -16,7 +16,6 @@ LLVM, Clang and LLD are quite light on dependencies, requiring only standard
|
||||
build tools to be built. Go is of course necessary to build TinyGo itself.
|
||||
|
||||
* Go (1.11+)
|
||||
* [dep](https://golang.github.io/dep/)
|
||||
* Standard build tools (gcc/clang)
|
||||
* git
|
||||
* CMake
|
||||
@@ -28,15 +27,19 @@ on a different system like Mac.
|
||||
## Download the source
|
||||
|
||||
The first step is to download the TinyGo sources (use `--recursive` if you clone
|
||||
the git repository). Then, inside the directory, perform these steps:
|
||||
the git repository). Then, inside the directory, download the LLVM source:
|
||||
|
||||
dep ensure -vendor-only # download Go dependencies
|
||||
make llvm-source # download LLVM
|
||||
make llvm-source
|
||||
|
||||
You can also store LLVM outside of the TinyGo root directory by setting the
|
||||
`LLVM_BUILDDIR`, `CLANG_SRC` and `LLD_SRC` make variables, but that is not
|
||||
covered by this guide.
|
||||
|
||||
TinyGo uses Go modules, so if you clone TinyGo inside your GOPATH (and are using
|
||||
Go below 1.13), make sure that Go modules are enabled:
|
||||
|
||||
export GO111MODULE=on
|
||||
|
||||
## Build LLVM, Clang, LLD
|
||||
|
||||
Before starting the build, you may want to set the following environment
|
||||
|
||||
@@ -1,3 +1,90 @@
|
||||
0.9.0
|
||||
---
|
||||
* **command line**
|
||||
- implement 1200-baud UART bootloader reset when flashing boards that support
|
||||
it
|
||||
- flash using mass-storage device for boards that support it
|
||||
- implement `tinygo env`
|
||||
- add support for Windows (but not yet producing Windows binaries)
|
||||
- add Go version to `tinygo env`
|
||||
- update SVD files for up-to-date peripheral interfaces
|
||||
* **compiler**
|
||||
- add `//go:align` pragma
|
||||
- fix bug related to type aliases
|
||||
- add support for buffered channels
|
||||
- remove incorrect reflect optimization
|
||||
- implement copying slices in init interpretation
|
||||
- add support for constant indices with a named type
|
||||
- add support for recursive types like linked lists
|
||||
- fix miscompile of function nil panics
|
||||
- fix bug related to goroutines
|
||||
* **standard library**
|
||||
- `machine`: do not check for nil slices in `SPI.Tx`
|
||||
- `reflectlite`: add support for Go 1.13
|
||||
- `runtime`: implement `internal/bytealg.CountString`
|
||||
- `sync`: properly handle nil `New` func in `sync.Pool`
|
||||
* **targets**
|
||||
- `arduino`: fix .bss section initialization
|
||||
- `fe310`: implement `Pin.Get`
|
||||
- `gameboy-advance`: support directly outputting .gba files
|
||||
- `samd`: reduce code size by avoiding reflection
|
||||
- `samd21`: do not hardcode pin numbers for peripherals
|
||||
- `stm32f103`: avoid issue with `time.Sleep` less than 200µs
|
||||
|
||||
0.8.0
|
||||
---
|
||||
* **command line**
|
||||
- fix parsing of beta Go versions
|
||||
- check the major/minor installed version of Go before compiling
|
||||
- validate `-target` flag better to not panic on an invalid target
|
||||
* **compiler**
|
||||
- implement full slice expression: `s[:2:4]`
|
||||
- fix a crash when storing a linked list in an interface
|
||||
- fix comparing struct types by making type IDs more unique
|
||||
- fix some bugs in IR generation
|
||||
- add support for linked lists in reflect data
|
||||
- implement `[]rune` to string conversion
|
||||
- implement support for `go` on func values
|
||||
* **standard library**
|
||||
- `reflect`: add support for named types
|
||||
- `reflect`: add support for `t.Bits()`
|
||||
- `reflect`: add basic support for `t.AssignableTo()`
|
||||
- `reflect`: implement `t.Align()`
|
||||
- `reflect`: add support for struct types
|
||||
- `reflect`: fix bug in `v.IsNil` and `v.Pointer` for addressable values
|
||||
- `reflect`: implement support for array types
|
||||
- `reflect`: implement `t.Comparable()`
|
||||
- `runtime`: implement stack-based scheduler
|
||||
- `runtime`: fix bug in the sleep queue of the scheduler
|
||||
- `runtime`: implement `memcpy` for Cortex-M
|
||||
- `testing`: implement stub `testing.B` struct
|
||||
- `testing`: add common test logging methods such as Errorf/Fatalf/Printf
|
||||
* **targets**
|
||||
- `386`: add support for linux/386 syscalls
|
||||
- `atsamd21`: make SPI pins configurable so that multiple SPI ports can be
|
||||
used
|
||||
- `atsamd21`: correct issue with invalid first reading coming from ADC
|
||||
- `atsamd21`: add support for reset-to-bootloader using 1200baud over USB-CDC
|
||||
- `atsamd21`: make pin selection more flexible for peripherals
|
||||
- `atsamd21`: fix minimum delay in `time.Sleep`
|
||||
- `atsamd51`: fix minimum delay in `time.Sleep`
|
||||
- `nrf`: improve SPI write-only speed, by making use of double buffering
|
||||
- `stm32f103`: fix SPI frequency selection
|
||||
- `stm32f103`: add machine.Pin.Get method for reading GPIO values
|
||||
- `stm32f103`: allow board specific UART usage
|
||||
- `nucleo-f103rb`: add support for NUCLEO-F103RB board
|
||||
- `itsybitsy-m4`: add support for this board with a SAMD51 family chip
|
||||
- `cortex-m`: add support for `arm.SystemReset()`
|
||||
- `gameboy-advance`: add initial support for the GameBoy Advance
|
||||
- `wasm`: add `//go:wasm-module` magic comment to set the wasm module name
|
||||
- `wasm`: add syscall/js.valueSetIndex support
|
||||
- `wasm`: add syscall/js.valueInvoke support
|
||||
|
||||
0.7.1
|
||||
---
|
||||
* **targets**
|
||||
- `atsamd21`: add support for the `-port` flag in the flash subcommand
|
||||
|
||||
0.7.0
|
||||
---
|
||||
* **command line**
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
# This is the official list of TinyGo authors for copyright purposes.
|
||||
#
|
||||
# This file is not actively maintained.
|
||||
# To be included, send a change adding the individual or
|
||||
# company who owns a contribution's copyright.
|
||||
#
|
||||
# Names should be added to this file as one of
|
||||
# Organization's name
|
||||
# Individual's name <submission email address>
|
||||
# Individual's name <submission email address> <email2> <emailN>
|
||||
#
|
||||
# Please keep the list sorted.
|
||||
|
||||
Ayke van Laethem <aykevanlaethem@gmail.com>
|
||||
Daniel Esteban <conejo@conejo.me>
|
||||
Loon, LLC.
|
||||
Ron Evans <ron@hybridgroup.com>
|
||||
+26
-29
@@ -1,34 +1,31 @@
|
||||
# TinyGo base stage just installs LLVM 8 and the TinyGo compiler itself.
|
||||
FROM golang:latest AS tinygo-base
|
||||
# TinyGo base stage installs Go 1.13, LLVM 8 and the TinyGo compiler itself.
|
||||
FROM golang:1.13 AS tinygo-base
|
||||
|
||||
RUN wget -O- https://apt.llvm.org/llvm-snapshot.gpg.key| apt-key add - && \
|
||||
echo "deb http://apt.llvm.org/stretch/ llvm-toolchain-stretch-8 main" >> /etc/apt/sources.list && \
|
||||
echo "deb http://apt.llvm.org/buster/ llvm-toolchain-buster-8 main" >> /etc/apt/sources.list && \
|
||||
apt-get update && \
|
||||
apt-get install -y llvm-8-dev libclang-8-dev git
|
||||
|
||||
RUN wget -O- https://raw.githubusercontent.com/golang/dep/master/install.sh | sh
|
||||
|
||||
COPY . /go/src/github.com/tinygo-org/tinygo
|
||||
COPY . /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 /tinygo/ && \
|
||||
rm -rf ./lib/* && \
|
||||
git submodule update --init --recursive --force
|
||||
|
||||
RUN cd /go/src/github.com/tinygo-org/tinygo/ && \
|
||||
dep ensure --vendor-only && \
|
||||
go install /go/src/github.com/tinygo-org/tinygo/
|
||||
RUN cd /tinygo/ && \
|
||||
go install /tinygo/
|
||||
|
||||
# tinygo-wasm stage installs the needed dependencies to compile TinyGo programs for WASM.
|
||||
FROM tinygo-base AS tinygo-wasm
|
||||
|
||||
COPY --from=tinygo-base /go/bin/tinygo /go/bin/tinygo
|
||||
COPY --from=tinygo-base /go/src/github.com/tinygo-org/tinygo/src /go/src/github.com/tinygo-org/tinygo/src
|
||||
COPY --from=tinygo-base /go/src/github.com/tinygo-org/tinygo/targets /go/src/github.com/tinygo-org/tinygo/targets
|
||||
COPY --from=tinygo-base /tinygo/src /tinygo/src
|
||||
COPY --from=tinygo-base /tinygo/targets /tinygo/targets
|
||||
|
||||
RUN wget -O- https://apt.llvm.org/llvm-snapshot.gpg.key| apt-key add - && \
|
||||
echo "deb http://apt.llvm.org/stretch/ llvm-toolchain-stretch-8 main" >> /etc/apt/sources.list && \
|
||||
echo "deb http://apt.llvm.org/buster/ llvm-toolchain-buster-8 main" >> /etc/apt/sources.list && \
|
||||
apt-get update && \
|
||||
apt-get install -y libllvm8 lld-8
|
||||
|
||||
@@ -36,13 +33,13 @@ RUN wget -O- https://apt.llvm.org/llvm-snapshot.gpg.key| apt-key add - && \
|
||||
FROM tinygo-base AS tinygo-avr
|
||||
|
||||
COPY --from=tinygo-base /go/bin/tinygo /go/bin/tinygo
|
||||
COPY --from=tinygo-base /go/src/github.com/tinygo-org/tinygo/src /go/src/github.com/tinygo-org/tinygo/src
|
||||
COPY --from=tinygo-base /go/src/github.com/tinygo-org/tinygo/targets /go/src/github.com/tinygo-org/tinygo/targets
|
||||
COPY --from=tinygo-base /go/src/github.com/tinygo-org/tinygo/Makefile /go/src/github.com/tinygo-org/tinygo/
|
||||
COPY --from=tinygo-base /go/src/github.com/tinygo-org/tinygo/tools /go/src/github.com/tinygo-org/tinygo/tools
|
||||
COPY --from=tinygo-base /go/src/github.com/tinygo-org/tinygo/lib /go/src/github.com/tinygo-org/tinygo/lib
|
||||
COPY --from=tinygo-base /tinygo/src /tinygo/src
|
||||
COPY --from=tinygo-base /tinygo/targets /tinygo/targets
|
||||
COPY --from=tinygo-base /tinygo/Makefile /tinygo/
|
||||
COPY --from=tinygo-base /tinygo/tools /tinygo/tools
|
||||
COPY --from=tinygo-base /tinygo/lib /tinygo/lib
|
||||
|
||||
RUN cd /go/src/github.com/tinygo-org/tinygo/ && \
|
||||
RUN cd /tinygo/ && \
|
||||
apt-get update && \
|
||||
apt-get install -y apt-utils python3 make binutils-avr gcc-avr avr-libc && \
|
||||
make gen-device-avr && \
|
||||
@@ -54,13 +51,13 @@ RUN cd /go/src/github.com/tinygo-org/tinygo/ && \
|
||||
FROM tinygo-base AS tinygo-arm
|
||||
|
||||
COPY --from=tinygo-base /go/bin/tinygo /go/bin/tinygo
|
||||
COPY --from=tinygo-base /go/src/github.com/tinygo-org/tinygo/src /go/src/github.com/tinygo-org/tinygo/src
|
||||
COPY --from=tinygo-base /go/src/github.com/tinygo-org/tinygo/targets /go/src/github.com/tinygo-org/tinygo/targets
|
||||
COPY --from=tinygo-base /go/src/github.com/tinygo-org/tinygo/Makefile /go/src/github.com/tinygo-org/tinygo/
|
||||
COPY --from=tinygo-base /go/src/github.com/tinygo-org/tinygo/tools /go/src/github.com/tinygo-org/tinygo/tools
|
||||
COPY --from=tinygo-base /go/src/github.com/tinygo-org/tinygo/lib /go/src/github.com/tinygo-org/tinygo/lib
|
||||
COPY --from=tinygo-base /tinygo/src /tinygo/src
|
||||
COPY --from=tinygo-base /tinygo/targets /tinygo/targets
|
||||
COPY --from=tinygo-base /tinygo/Makefile /tinygo/
|
||||
COPY --from=tinygo-base /tinygo/tools /tinygo/tools
|
||||
COPY --from=tinygo-base /tinygo/lib /tinygo/lib
|
||||
|
||||
RUN cd /go/src/github.com/tinygo-org/tinygo/ && \
|
||||
RUN cd /tinygo/ && \
|
||||
apt-get update && \
|
||||
apt-get install -y apt-utils python3 make clang-8 && \
|
||||
make gen-device-nrf && make gen-device-stm32 && \
|
||||
@@ -71,11 +68,11 @@ RUN cd /go/src/github.com/tinygo-org/tinygo/ && \
|
||||
# tinygo-all stage installs the needed dependencies to compile TinyGo programs for all platforms.
|
||||
FROM tinygo-wasm AS tinygo-all
|
||||
|
||||
COPY --from=tinygo-base /go/src/github.com/tinygo-org/tinygo/Makefile /go/src/github.com/tinygo-org/tinygo/
|
||||
COPY --from=tinygo-base /go/src/github.com/tinygo-org/tinygo/tools /go/src/github.com/tinygo-org/tinygo/tools
|
||||
COPY --from=tinygo-base /go/src/github.com/tinygo-org/tinygo/lib /go/src/github.com/tinygo-org/tinygo/lib
|
||||
COPY --from=tinygo-base /tinygo/Makefile /tinygo/
|
||||
COPY --from=tinygo-base /tinygo/tools /tinygo/tools
|
||||
COPY --from=tinygo-base /tinygo/lib /tinygo/lib
|
||||
|
||||
RUN cd /go/src/github.com/tinygo-org/tinygo/ && \
|
||||
RUN cd /tinygo/ && \
|
||||
apt-get update && \
|
||||
apt-get install -y apt-utils python3 make clang-8 binutils-avr gcc-avr avr-libc && \
|
||||
make gen-device && \
|
||||
|
||||
Generated
-51
@@ -1,51 +0,0 @@
|
||||
# This file is autogenerated, do not edit; changes may be undone by the next 'dep ensure'.
|
||||
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
digest = "1:06519a2ec1d59040eaccec40206f9d0b59dc662db2a032f974d6d6b9a2bcb839"
|
||||
name = "github.com/blakesmith/ar"
|
||||
packages = ["."]
|
||||
pruneopts = "UT"
|
||||
revision = "8bd4349a67f2533b078dbc524689d15dba0f4659"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
digest = "1:00b45e06c7843541372fc17d982242bd6adfc2fc382b6f2e9ef9ce53d87a50b9"
|
||||
name = "github.com/marcinbor85/gohex"
|
||||
packages = ["."]
|
||||
pruneopts = "UT"
|
||||
revision = "7a43cd876e46e0f6ddc553f10f91731a78e6e949"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
digest = "1:ba70784a3deee74c0ca3c87bcac3c2f93d3b2d27d8f237b768c358b45ba47da8"
|
||||
name = "golang.org/x/tools"
|
||||
packages = [
|
||||
"go/ast/astutil",
|
||||
"go/ssa",
|
||||
"go/types/typeutil",
|
||||
]
|
||||
pruneopts = "UT"
|
||||
revision = "8dcc6e70cdefe9a82236b6e195e4f4e2108fcb9f"
|
||||
|
||||
[[projects]]
|
||||
branch = "llvm8"
|
||||
digest = "1:bf5539bdf6b3cc3ec1e45926db05d81180da11ce722fa1edcce3f0b4e1967da5"
|
||||
name = "tinygo.org/x/go-llvm"
|
||||
packages = ["."]
|
||||
pruneopts = "UT"
|
||||
revision = "7707ae5d1261a8929edea7336c8087ca8b520d8d"
|
||||
|
||||
[solve-meta]
|
||||
analyzer-name = "dep"
|
||||
analyzer-version = 1
|
||||
input-imports = [
|
||||
"github.com/blakesmith/ar",
|
||||
"github.com/marcinbor85/gohex",
|
||||
"golang.org/x/tools/go/ast/astutil",
|
||||
"golang.org/x/tools/go/ssa",
|
||||
"tinygo.org/x/go-llvm",
|
||||
]
|
||||
solver-name = "gps-cdcl"
|
||||
solver-version = 1
|
||||
-11
@@ -1,11 +0,0 @@
|
||||
[[constraint]]
|
||||
branch = "llvm8"
|
||||
name = "tinygo.org/x/go-llvm"
|
||||
|
||||
[[constraint]]
|
||||
branch = "master"
|
||||
name = "golang.org/x/tools"
|
||||
|
||||
[prune]
|
||||
go-tests = true
|
||||
unused-packages = true
|
||||
@@ -1,40 +1,93 @@
|
||||
|
||||
# aliases
|
||||
all: tinygo
|
||||
tinygo: build/tinygo
|
||||
|
||||
# Default build and source directories, as created by `make llvm-build`.
|
||||
LLVM_BUILDDIR ?= llvm-build
|
||||
CLANG_SRC ?= llvm-project/clang
|
||||
LLD_SRC ?= llvm-project/lld
|
||||
|
||||
.PHONY: all tinygo build/tinygo test $(LLVM_BUILDDIR) llvm-source clean fmt gen-device gen-device-nrf gen-device-avr
|
||||
# Go binary and GOROOT to select
|
||||
GO ?= go
|
||||
export GOROOT = $(shell $(GO) env GOROOT)
|
||||
|
||||
# md5sum binary
|
||||
MD5SUM = md5sum
|
||||
|
||||
# Python binary
|
||||
PYTHON ?= python
|
||||
|
||||
# tinygo binary for tests
|
||||
TINYGO ?= tinygo
|
||||
|
||||
# Use CCACHE for LLVM if possible
|
||||
ifneq (, $(shell which ccache))
|
||||
LLVM_OPTION += '-DLLVM_CCACHE_BUILD=ON'
|
||||
endif
|
||||
|
||||
# Allow enabling LLVM assertions
|
||||
ifeq (1, $(ASSERT))
|
||||
LLVM_OPTION += '-DLLVM_ENABLE_ASSERTIONS=ON'
|
||||
else
|
||||
LLVM_OPTION += '-DLLVM_ENABLE_ASSERTIONS=OFF'
|
||||
endif
|
||||
|
||||
.PHONY: all tinygo test $(LLVM_BUILDDIR) llvm-source clean fmt gen-device gen-device-nrf gen-device-avr
|
||||
|
||||
LLVM_COMPONENTS = all-targets analysis asmparser asmprinter bitreader bitwriter codegen core coroutines debuginfodwarf executionengine instrumentation interpreter ipo irreader linker lto mc mcjit objcarcopts option profiledata scalaropts support target
|
||||
|
||||
UNAME_S := $(shell uname -s)
|
||||
ifeq ($(UNAME_S),Linux)
|
||||
ifeq ($(OS),Windows_NT)
|
||||
EXE = .exe
|
||||
START_GROUP = -Wl,--start-group
|
||||
END_GROUP = -Wl,--end-group
|
||||
|
||||
# LLVM compiled using MinGW on Windows appears to have problems with threads.
|
||||
# Without this flag, linking results in errors like these:
|
||||
# libLLVMSupport.a(Threading.cpp.obj):Threading.cpp:(.text+0x55): undefined reference to `std::thread::hardware_concurrency()'
|
||||
LLVM_OPTION += -DLLVM_ENABLE_THREADS=OFF
|
||||
|
||||
CGO_LDFLAGS += -static -static-libgcc -static-libstdc++
|
||||
CGO_LDFLAGS_EXTRA += -lversion
|
||||
|
||||
# Build libclang manually because the CMake-based build system on Windows
|
||||
# doesn't allow building libclang as a static library.
|
||||
LIBCLANG_PATH = $(abspath build/libclang-custom.a)
|
||||
LIBCLANG_FILES = $(abspath $(wildcard $(LLVM_BUILDDIR)/tools/clang/tools/libclang/CMakeFiles/libclang.dir/*.cpp.obj))
|
||||
|
||||
# Add the libclang dependency to the tinygo binary target.
|
||||
tinygo: $(LIBCLANG_PATH)
|
||||
test: $(LIBCLANG_PATH)
|
||||
# Build libclang.
|
||||
$(LIBCLANG_PATH): $(LIBCLANG_FILES)
|
||||
@mkdir -p build
|
||||
ar rcs $(LIBCLANG_PATH) $^
|
||||
|
||||
else ifeq ($(shell uname -s),Darwin)
|
||||
MD5SUM = md5
|
||||
LIBCLANG_PATH = $(abspath $(LLVM_BUILDDIR))/lib/libclang.a
|
||||
else
|
||||
LIBCLANG_PATH = $(abspath $(LLVM_BUILDDIR))/lib/libclang.a
|
||||
START_GROUP = -Wl,--start-group
|
||||
END_GROUP = -Wl,--end-group
|
||||
endif
|
||||
|
||||
CLANG_LIBS = $(START_GROUP) $(abspath $(LLVM_BUILDDIR))/lib/libclang.a -lclangAnalysis -lclangARCMigrate -lclangAST -lclangASTMatchers -lclangBasic -lclangCodeGen -lclangCrossTU -lclangDriver -lclangDynamicASTMatchers -lclangEdit -lclangFormat -lclangFrontend -lclangFrontendTool -lclangHandleCXX -lclangHandleLLVM -lclangIndex -lclangLex -lclangParse -lclangRewrite -lclangRewriteFrontend -lclangSema -lclangSerialization -lclangStaticAnalyzerCheckers -lclangStaticAnalyzerCore -lclangStaticAnalyzerFrontend -lclangTooling -lclangToolingASTDiff -lclangToolingCore -lclangToolingInclusions $(END_GROUP) -lstdc++
|
||||
CLANG_LIBS = $(START_GROUP) -lclangAnalysis -lclangARCMigrate -lclangAST -lclangASTMatchers -lclangBasic -lclangCodeGen -lclangCrossTU -lclangDriver -lclangDynamicASTMatchers -lclangEdit -lclangFormat -lclangFrontend -lclangFrontendTool -lclangHandleCXX -lclangHandleLLVM -lclangIndex -lclangLex -lclangParse -lclangRewrite -lclangRewriteFrontend -lclangSema -lclangSerialization -lclangStaticAnalyzerCheckers -lclangStaticAnalyzerCore -lclangStaticAnalyzerFrontend -lclangTooling -lclangToolingASTDiff -lclangToolingCore -lclangToolingInclusions $(END_GROUP) -lstdc++
|
||||
|
||||
LLD_LIBS = $(START_GROUP) -llldCOFF -llldCommon -llldCore -llldDriver -llldELF -llldMachO -llldMinGW -llldReaderWriter -llldWasm -llldYAML $(END_GROUP)
|
||||
|
||||
|
||||
# For static linking.
|
||||
ifneq ("$(wildcard $(LLVM_BUILDDIR)/bin/llvm-config)","")
|
||||
ifneq ("$(wildcard $(LLVM_BUILDDIR)/bin/llvm-config*)","")
|
||||
CGO_CPPFLAGS=$(shell $(LLVM_BUILDDIR)/bin/llvm-config --cppflags) -I$(abspath $(CLANG_SRC))/include -I$(abspath $(LLD_SRC))/include
|
||||
CGO_CXXFLAGS=-std=c++11
|
||||
CGO_LDFLAGS=-L$(LLVM_BUILDDIR)/lib $(CLANG_LIBS) $(LLD_LIBS) $(shell $(LLVM_BUILDDIR)/bin/llvm-config --ldflags --libs --system-libs $(LLVM_COMPONENTS))
|
||||
CGO_LDFLAGS+=$(LIBCLANG_PATH) -std=c++11 -L$(abspath $(LLVM_BUILDDIR)/lib) $(CLANG_LIBS) $(LLD_LIBS) $(shell $(LLVM_BUILDDIR)/bin/llvm-config --ldflags --libs --system-libs $(LLVM_COMPONENTS)) -lstdc++ $(CGO_LDFLAGS_EXTRA)
|
||||
endif
|
||||
|
||||
|
||||
clean:
|
||||
@rm -rf build
|
||||
|
||||
FMT_PATHS = ./*.go cgo compiler interp ir loader src/device/arm src/examples src/machine src/os src/reflect src/runtime src/sync src/syscall
|
||||
FMT_PATHS = ./*.go cgo compiler interp ir loader src/device/arm src/examples src/machine src/os src/reflect src/runtime src/sync src/syscall src/internal/reflectlite transform
|
||||
fmt:
|
||||
@gofmt -l -w $(FMT_PATHS)
|
||||
fmt-check:
|
||||
@@ -44,25 +97,25 @@ fmt-check:
|
||||
gen-device: gen-device-avr gen-device-nrf gen-device-sam gen-device-sifive gen-device-stm32
|
||||
|
||||
gen-device-avr:
|
||||
./tools/gen-device-avr.py lib/avr/packs/atmega src/device/avr/
|
||||
./tools/gen-device-avr.py lib/avr/packs/tiny src/device/avr/
|
||||
go fmt ./src/device/avr
|
||||
$(PYTHON) ./tools/gen-device-avr.py lib/avr/packs/atmega src/device/avr/
|
||||
$(PYTHON) ./tools/gen-device-avr.py lib/avr/packs/tiny src/device/avr/
|
||||
GO111MODULE=off $(GO) fmt ./src/device/avr
|
||||
|
||||
gen-device-nrf:
|
||||
./tools/gen-device-svd.py lib/nrfx/mdk/ src/device/nrf/ --source=https://github.com/NordicSemiconductor/nrfx/tree/master/mdk
|
||||
go fmt ./src/device/nrf
|
||||
$(PYTHON) ./tools/gen-device-svd.py lib/nrfx/mdk/ src/device/nrf/ --source=https://github.com/NordicSemiconductor/nrfx/tree/master/mdk
|
||||
GO111MODULE=off $(GO) fmt ./src/device/nrf
|
||||
|
||||
gen-device-sam:
|
||||
./tools/gen-device-svd.py lib/cmsis-svd/data/Atmel/ src/device/sam/ --source=https://github.com/posborne/cmsis-svd/tree/master/data/Atmel
|
||||
go fmt ./src/device/sam
|
||||
$(PYTHON) ./tools/gen-device-svd.py lib/cmsis-svd/data/Atmel/ src/device/sam/ --source=https://github.com/posborne/cmsis-svd/tree/master/data/Atmel
|
||||
GO111MODULE=off $(GO) fmt ./src/device/sam
|
||||
|
||||
gen-device-sifive:
|
||||
./tools/gen-device-svd.py lib/cmsis-svd/data/SiFive-Community/ src/device/sifive/ --source=https://github.com/AdaCore/svd2ada/tree/master/CMSIS-SVD/SiFive-Community
|
||||
go fmt ./src/device/sifive
|
||||
$(PYTHON) ./tools/gen-device-svd.py lib/cmsis-svd/data/SiFive-Community/ src/device/sifive/ --source=https://github.com/AdaCore/svd2ada/tree/master/CMSIS-SVD/SiFive-Community
|
||||
GO111MODULE=off $(GO) fmt ./src/device/sifive
|
||||
|
||||
gen-device-stm32:
|
||||
./tools/gen-device-svd.py lib/cmsis-svd/data/STMicro/ src/device/stm32/ --source=https://github.com/posborne/cmsis-svd/tree/master/data/STMicro
|
||||
go fmt ./src/device/stm32
|
||||
$(PYTHON) ./tools/gen-device-svd.py lib/cmsis-svd/data/STMicro/ src/device/stm32/ --source=https://github.com/posborne/cmsis-svd/tree/master/data/STMicro
|
||||
GO111MODULE=off $(GO) fmt ./src/device/stm32
|
||||
|
||||
|
||||
# Get LLVM sources.
|
||||
@@ -73,7 +126,7 @@ llvm-source: llvm-project/README.md
|
||||
# Configure LLVM.
|
||||
TINYGO_SOURCE_DIR=$(shell pwd)
|
||||
$(LLVM_BUILDDIR)/build.ninja: llvm-source
|
||||
mkdir -p $(LLVM_BUILDDIR); cd $(LLVM_BUILDDIR); cmake -G Ninja $(TINYGO_SOURCE_DIR)/llvm-project/llvm "-DLLVM_TARGETS_TO_BUILD=X86;ARM;AArch64;WebAssembly" "-DLLVM_EXPERIMENTAL_TARGETS_TO_BUILD=AVR;RISCV" -DCMAKE_BUILD_TYPE=Release -DLLVM_ENABLE_ASSERTIONS=OFF -DLIBCLANG_BUILD_STATIC=ON -DLLVM_ENABLE_TERMINFO=OFF -DLLVM_ENABLE_ZLIB=OFF -DLLVM_ENABLE_PROJECTS="clang;lld" -DLLVM_TOOL_CLANG_TOOLS_EXTRA_BUILD=OFF
|
||||
mkdir -p $(LLVM_BUILDDIR); cd $(LLVM_BUILDDIR); cmake -G Ninja $(TINYGO_SOURCE_DIR)/llvm-project/llvm "-DLLVM_TARGETS_TO_BUILD=X86;ARM;AArch64;WebAssembly" "-DLLVM_EXPERIMENTAL_TARGETS_TO_BUILD=AVR;RISCV" -DCMAKE_BUILD_TYPE=Release -DLIBCLANG_BUILD_STATIC=ON -DLLVM_ENABLE_TERMINFO=OFF -DLLVM_ENABLE_ZLIB=OFF -DLLVM_ENABLE_PROJECTS="clang;lld" -DLLVM_TOOL_CLANG_TOOLS_EXTRA_BUILD=OFF $(LLVM_OPTION)
|
||||
|
||||
# Build LLVM.
|
||||
$(LLVM_BUILDDIR): $(LLVM_BUILDDIR)/build.ninja
|
||||
@@ -81,12 +134,12 @@ $(LLVM_BUILDDIR): $(LLVM_BUILDDIR)/build.ninja
|
||||
|
||||
|
||||
# Build the Go compiler.
|
||||
build/tinygo:
|
||||
tinygo:
|
||||
@if [ ! -f "$(LLVM_BUILDDIR)/bin/llvm-config" ]; then echo "Fetch and build LLVM first by running:"; echo " make llvm-source"; echo " make $(LLVM_BUILDDIR)"; exit 1; fi
|
||||
CGO_CPPFLAGS="$(CGO_CPPFLAGS)" CGO_CXXFLAGS="$(CGO_CXXFLAGS)" CGO_LDFLAGS="$(CGO_LDFLAGS)" go build -o build/tinygo -tags byollvm .
|
||||
CGO_CPPFLAGS="$(CGO_CPPFLAGS)" CGO_CXXFLAGS="$(CGO_CXXFLAGS)" CGO_LDFLAGS="$(CGO_LDFLAGS)" $(GO) build -o build/tinygo$(EXE) -tags byollvm .
|
||||
|
||||
test:
|
||||
CGO_CPPFLAGS="$(CGO_CPPFLAGS)" CGO_CXXFLAGS="$(CGO_CXXFLAGS)" CGO_LDFLAGS="$(CGO_LDFLAGS)" go test -v -tags byollvm .
|
||||
CGO_CPPFLAGS="$(CGO_CPPFLAGS)" CGO_CXXFLAGS="$(CGO_CXXFLAGS)" CGO_LDFLAGS="$(CGO_LDFLAGS)" $(GO) test -v -tags byollvm ./interp ./transform .
|
||||
|
||||
tinygo-test:
|
||||
cd tests/tinygotest && tinygo test
|
||||
@@ -94,47 +147,84 @@ tinygo-test:
|
||||
.PHONY: smoketest
|
||||
smoketest:
|
||||
# test all examples
|
||||
tinygo build -size short -o test.elf -target=pca10040 examples/blinky1
|
||||
tinygo build -size short -o test.elf -target=pca10040 examples/adc
|
||||
tinygo build -size short -o test.elf -target=pca10040 examples/blinkm
|
||||
tinygo build -size short -o test.elf -target=pca10040 examples/blinky2
|
||||
tinygo build -size short -o test.elf -target=pca10040 examples/button
|
||||
tinygo build -size short -o test.elf -target=pca10040 examples/button2
|
||||
tinygo build -size short -o test.elf -target=pca10040 examples/echo
|
||||
tinygo build -size short -o test.elf -target=circuitplay-express examples/i2s
|
||||
tinygo build -size short -o test.elf -target=pca10040 examples/mcp3008
|
||||
tinygo build -size short -o test.elf -target=microbit examples/microbit-blink
|
||||
tinygo build -size short -o test.elf -target=pca10040 examples/pwm
|
||||
tinygo build -size short -o test.elf -target=pca10040 examples/serial
|
||||
tinygo build -size short -o test.elf -target=pca10040 examples/test
|
||||
$(TINYGO) build -size short -o test.hex -target=pca10040 examples/blinky1
|
||||
@$(MD5SUM) test.hex
|
||||
$(TINYGO) build -size short -o test.hex -target=pca10040 examples/adc
|
||||
@$(MD5SUM) test.hex
|
||||
$(TINYGO) build -size short -o test.hex -target=pca10040 examples/blinkm
|
||||
@$(MD5SUM) test.hex
|
||||
$(TINYGO) build -size short -o test.hex -target=pca10040 examples/blinky2
|
||||
@$(MD5SUM) test.hex
|
||||
$(TINYGO) build -size short -o test.hex -target=pca10040 examples/button
|
||||
@$(MD5SUM) test.hex
|
||||
$(TINYGO) build -size short -o test.hex -target=pca10040 examples/button2
|
||||
@$(MD5SUM) test.hex
|
||||
$(TINYGO) build -size short -o test.hex -target=pca10040 examples/echo
|
||||
@$(MD5SUM) test.hex
|
||||
$(TINYGO) build -size short -o test.hex -target=circuitplay-express examples/i2s
|
||||
@$(MD5SUM) test.hex
|
||||
$(TINYGO) build -size short -o test.hex -target=pca10040 examples/mcp3008
|
||||
@$(MD5SUM) test.hex
|
||||
$(TINYGO) build -size short -o test.hex -target=microbit examples/microbit-blink
|
||||
@$(MD5SUM) test.hex
|
||||
$(TINYGO) build -size short -o test.hex -target=pca10040 examples/pwm
|
||||
@$(MD5SUM) test.hex
|
||||
$(TINYGO) build -size short -o test.hex -target=pca10040 examples/serial
|
||||
@$(MD5SUM) test.hex
|
||||
$(TINYGO) build -size short -o test.hex -target=pca10040 examples/test
|
||||
@$(MD5SUM) test.hex
|
||||
# test all targets/boards
|
||||
tinygo build -o test.wasm -tags=pca10040 examples/blinky2
|
||||
tinygo build -size short -o test.elf -target=microbit examples/echo
|
||||
tinygo build -size short -o test.elf -target=nrf52840-mdk examples/blinky1
|
||||
tinygo build -size short -o test.elf -target=pca10031 examples/blinky1
|
||||
tinygo build -size short -o test.elf -target=bluepill examples/blinky1
|
||||
tinygo build -size short -o test.elf -target=reelboard examples/blinky1
|
||||
tinygo build -size short -o test.elf -target=reelboard examples/blinky2
|
||||
tinygo build -size short -o test.elf -target=pca10056 examples/blinky1
|
||||
tinygo build -size short -o test.elf -target=pca10056 examples/blinky2
|
||||
tinygo build -size short -o test.elf -target=itsybitsy-m0 examples/blinky1
|
||||
tinygo build -size short -o test.elf -target=feather-m0 examples/blinky1
|
||||
tinygo build -size short -o test.elf -target=trinket-m0 examples/blinky1
|
||||
tinygo build -size short -o test.elf -target=circuitplay-express examples/blinky1
|
||||
tinygo build -size short -o test.elf -target=stm32f4disco examples/blinky1
|
||||
tinygo build -size short -o test.elf -target=stm32f4disco examples/blinky2
|
||||
tinygo build -size short -o test.elf -target=circuitplay-express examples/i2s
|
||||
$(TINYGO) build -o test.wasm -tags=pca10040 examples/blinky2
|
||||
$(TINYGO) build -size short -o test.hex -target=microbit examples/echo
|
||||
@$(MD5SUM) test.hex
|
||||
$(TINYGO) build -size short -o test.hex -target=nrf52840-mdk examples/blinky1
|
||||
@$(MD5SUM) test.hex
|
||||
$(TINYGO) build -size short -o test.hex -target=pca10031 examples/blinky1
|
||||
@$(MD5SUM) test.hex
|
||||
$(TINYGO) build -size short -o test.hex -target=bluepill examples/blinky1
|
||||
@$(MD5SUM) test.hex
|
||||
$(TINYGO) build -size short -o test.hex -target=reelboard examples/blinky1
|
||||
@$(MD5SUM) test.hex
|
||||
$(TINYGO) build -size short -o test.hex -target=reelboard examples/blinky2
|
||||
@$(MD5SUM) test.hex
|
||||
$(TINYGO) build -size short -o test.hex -target=pca10056 examples/blinky1
|
||||
@$(MD5SUM) test.hex
|
||||
$(TINYGO) build -size short -o test.hex -target=pca10056 examples/blinky2
|
||||
@$(MD5SUM) test.hex
|
||||
$(TINYGO) build -size short -o test.hex -target=itsybitsy-m0 examples/blinky1
|
||||
@$(MD5SUM) test.hex
|
||||
$(TINYGO) build -size short -o test.hex -target=feather-m0 examples/blinky1
|
||||
@$(MD5SUM) test.hex
|
||||
$(TINYGO) build -size short -o test.hex -target=trinket-m0 examples/blinky1
|
||||
@$(MD5SUM) test.hex
|
||||
$(TINYGO) build -size short -o test.hex -target=circuitplay-express examples/blinky1
|
||||
@$(MD5SUM) test.hex
|
||||
$(TINYGO) build -size short -o test.hex -target=stm32f4disco examples/blinky1
|
||||
@$(MD5SUM) test.hex
|
||||
$(TINYGO) build -size short -o test.hex -target=stm32f4disco examples/blinky2
|
||||
@$(MD5SUM) test.hex
|
||||
$(TINYGO) build -size short -o test.hex -target=circuitplay-express examples/i2s
|
||||
@$(MD5SUM) test.hex
|
||||
$(TINYGO) build -size short -o test.gba -target=gameboy-advance examples/gba-display
|
||||
@$(MD5SUM) test.gba
|
||||
$(TINYGO) build -size short -o test.hex -target=itsybitsy-m4 examples/blinky1
|
||||
@$(MD5SUM) test.hex
|
||||
$(TINYGO) build -size short -o test.hex -target=nucleo-f103rb examples/blinky1
|
||||
@$(MD5SUM) test.hex
|
||||
ifneq ($(AVR), 0)
|
||||
tinygo build -size short -o test.elf -target=arduino examples/blinky1
|
||||
tinygo build -size short -o test.elf -target=digispark examples/blinky1
|
||||
$(TINYGO) build -size short -o test.hex -target=arduino examples/blinky1
|
||||
@$(MD5SUM) test.hex
|
||||
$(TINYGO) build -size short -o test.hex -target=digispark examples/blinky1
|
||||
@$(MD5SUM) test.hex
|
||||
endif
|
||||
ifneq ($(RISCV), 0)
|
||||
tinygo build -size short -o test.elf -target=hifive1b examples/blinky1
|
||||
$(TINYGO) build -size short -o test.hex -target=hifive1b examples/blinky1
|
||||
@$(MD5SUM) test.hex
|
||||
endif
|
||||
tinygo build -o wasm.wasm -target=wasm examples/wasm/export
|
||||
tinygo build -o wasm.wasm -target=wasm examples/wasm/main
|
||||
$(TINYGO) build -o wasm.wasm -target=wasm examples/wasm/export
|
||||
$(TINYGO) build -o wasm.wasm -target=wasm examples/wasm/main
|
||||
|
||||
release: build/tinygo gen-device
|
||||
release: tinygo gen-device
|
||||
@mkdir -p build/release/tinygo/bin
|
||||
@mkdir -p build/release/tinygo/lib/clang/include
|
||||
@mkdir -p build/release/tinygo/lib/CMSIS/CMSIS
|
||||
@@ -144,7 +234,7 @@ release: build/tinygo gen-device
|
||||
@mkdir -p build/release/tinygo/pkg/armv7m-none-eabi
|
||||
@mkdir -p build/release/tinygo/pkg/armv7em-none-eabi
|
||||
@echo copying source files
|
||||
@cp -p build/tinygo build/release/tinygo/bin
|
||||
@cp -p build/tinygo$(EXE) build/release/tinygo/bin
|
||||
@cp -p $(abspath $(CLANG_SRC))/lib/Headers/*.h build/release/tinygo/lib/clang/include
|
||||
@cp -rp lib/CMSIS/CMSIS/Include build/release/tinygo/lib/CMSIS/CMSIS
|
||||
@cp -rp lib/CMSIS/README.md build/release/tinygo/lib/CMSIS
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
# TinyGo - Go compiler for small places
|
||||
|
||||
[](https://circleci.com/gh/tinygo-org/tinygo/tree/dev)
|
||||
[](https://circleci.com/gh/tinygo-org/tinygo/tree/dev) [](https://dev.azure.com/tinygo/tinygo/_build/latest?definitionId=1&branchName=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.
|
||||
|
||||
It reuses libraries used by the [Go language tools](https://golang.org/pkg/go/) alongside [LLVM](http://llvm.org) to provide an alternative way to compile programs written in the Go programming language.
|
||||
|
||||
@@ -43,23 +43,27 @@ See the [getting started instructions](https://tinygo.org/getting-started/) for
|
||||
|
||||
You can compile TinyGo programs for microcontrollers, WebAssembly and Linux.
|
||||
|
||||
The following 15 microcontroller boards are currently supported:
|
||||
The following 19 microcontroller boards are currently supported:
|
||||
|
||||
* [Adafruit Circuit Playground Express](https://www.adafruit.com/product/3333)
|
||||
* [Adafruit Feather M0](https://www.adafruit.com/product/2772)
|
||||
* [Adafruit ItsyBitsy M0](https://www.adafruit.com/product/3727)
|
||||
* [Adafruit ItsyBitsy M4](https://www.adafruit.com/product/3800)
|
||||
* [Adafruit Trinket M0](https://www.adafruit.com/product/3500)
|
||||
* [Arduino Nano33 IoT](https://store.arduino.cc/nano-33-iot)
|
||||
* [Arduino Uno](https://store.arduino.cc/arduino-uno-rev3)
|
||||
* [BBC micro:bit](https://microbit.org/)
|
||||
* [ST Micro STM32F103XX "Bluepill"](http://wiki.stm32duino.com/index.php?title=Blue_Pill)
|
||||
* [ST Micro STM32F407 "Discovery"](https://www.st.com/en/evaluation-tools/stm32f4discovery.html)
|
||||
* [Digispark](http://digistump.com/products/1)
|
||||
* [Game Boy Advance](https://en.wikipedia.org/wiki/Game_Boy_Advance)
|
||||
* [Makerdiary nRF52840-MDK](https://wiki.makerdiary.com/nrf52840-mdk/)
|
||||
* [Nordic Semiconductor PCA10031](https://www.nordicsemi.com/eng/Products/nRF51-Dongle)
|
||||
* [Nordic Semiconductor PCA10040](https://www.nordicsemi.com/eng/Products/Bluetooth-low-energy/nRF52-DK)
|
||||
* [Nordic Semiconductor PCA10056](https://www.nordicsemi.com/Software-and-Tools/Development-Kits/nRF52840-DK)
|
||||
* [Makerdiary nRF52840-MDK](https://wiki.makerdiary.com/nrf52840-mdk/)
|
||||
* [Phytec reel board](https://www.phytec.eu/product-eu/internet-of-things/reelboard/)
|
||||
* [SiFIve HiFive1](https://www.sifive.com/boards/hifive1)
|
||||
* [ST Micro "Nucleo F103RB"](https://www.st.com/en/evaluation-tools/nucleo-f103rb.html)
|
||||
* [ST Micro STM32F103XX "Bluepill"](http://wiki.stm32duino.com/index.php?title=Blue_Pill)
|
||||
* [ST Micro STM32F407 "Discovery"](https://www.st.com/en/evaluation-tools/stm32f4discovery.html)
|
||||
|
||||
For more information, see [this list of boards](https://tinygo.org/microcontrollers/). Pull requests for additional support are welcome!
|
||||
|
||||
|
||||
@@ -0,0 +1,61 @@
|
||||
jobs:
|
||||
- job: Build
|
||||
timeoutInMinutes: 180
|
||||
pool:
|
||||
vmImage: 'VS2017-Win2016'
|
||||
steps:
|
||||
- checkout: self
|
||||
- task: CacheBeta@0
|
||||
displayName: Cache LLVM source
|
||||
inputs:
|
||||
key: llvm-source-8-windows-v0
|
||||
path: llvm-project
|
||||
- task: Bash@3
|
||||
displayName: Download LLVM source
|
||||
inputs:
|
||||
targetType: inline
|
||||
script: make llvm-source
|
||||
- task: CacheBeta@0
|
||||
displayName: Cache LLVM build
|
||||
inputs:
|
||||
key: llvm-build-8-windows-v1
|
||||
path: llvm-build
|
||||
- task: Bash@3
|
||||
displayName: Build LLVM
|
||||
inputs:
|
||||
targetType: inline
|
||||
script: |
|
||||
if [ ! -f llvm-build/lib/liblldELF.a ]
|
||||
then
|
||||
choco install ninja
|
||||
make llvm-build
|
||||
fi
|
||||
- task: Bash@3
|
||||
displayName: Install QEMU
|
||||
inputs:
|
||||
targetType: inline
|
||||
script: choco install qemu
|
||||
- task: Bash@3
|
||||
displayName: Test TinyGo
|
||||
inputs:
|
||||
targetType: inline
|
||||
script: |
|
||||
export PATH="$PATH:./llvm-build/bin:/c/Program Files/qemu"
|
||||
make test
|
||||
- task: Bash@3
|
||||
displayName: Build TinyGo release tarball
|
||||
inputs:
|
||||
targetType: inline
|
||||
script: |
|
||||
export PATH="$PATH:./llvm-build/bin:/c/Program Files/qemu"
|
||||
make release -j4
|
||||
- publish: $(System.DefaultWorkingDirectory)/build/release.tar.gz
|
||||
displayName: Publish tarball as artifact
|
||||
artifact: tinygo.windows-amd64.tar.gz
|
||||
- task: Bash@3
|
||||
displayName: Smoke tests
|
||||
inputs:
|
||||
targetType: inline
|
||||
script: |
|
||||
export PATH="$PATH:./llvm-build/bin:/c/Program Files/qemu"
|
||||
make smoketest TINYGO=build/tinygo AVR=0 RISCV=0
|
||||
+6
-14
@@ -5,16 +5,9 @@ import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Get the cache directory, usually ~/.cache/tinygo
|
||||
func cacheDir() string {
|
||||
dir, err := os.UserCacheDir()
|
||||
if err != nil {
|
||||
panic("could not find cache dir: " + err.Error())
|
||||
}
|
||||
return filepath.Join(dir, "tinygo")
|
||||
}
|
||||
"github.com/tinygo-org/tinygo/goenv"
|
||||
)
|
||||
|
||||
// Return the newest timestamp of all the file paths passed in. Used to check
|
||||
// for stale caches.
|
||||
@@ -41,8 +34,7 @@ func cacheTimestamp(paths []string) (time.Time, error) {
|
||||
// TODO: the configKey is currently ignored. It is supposed to be used as extra
|
||||
// data for the cache key, like the compiler version and arguments.
|
||||
func cacheLoad(name, configKey string, sourceFiles []string) (string, error) {
|
||||
dir := cacheDir()
|
||||
cachepath := filepath.Join(dir, name)
|
||||
cachepath := filepath.Join(goenv.Get("GOCACHE"), name)
|
||||
cacheStat, err := os.Stat(cachepath)
|
||||
if os.IsNotExist(err) {
|
||||
return "", nil // does not exist
|
||||
@@ -76,7 +68,7 @@ func cacheStore(tmppath, name, configKey string, sourceFiles []string) (string,
|
||||
|
||||
// TODO: check the config key
|
||||
|
||||
dir := cacheDir()
|
||||
dir := goenv.Get("GOCACHE")
|
||||
err := os.MkdirAll(dir, 0777)
|
||||
if err != nil {
|
||||
return "", err
|
||||
@@ -117,10 +109,10 @@ func moveFile(src, dst string) error {
|
||||
return err
|
||||
}
|
||||
|
||||
err = os.Rename(dst+".tmp", dst)
|
||||
err = outf.Close()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return outf.Close()
|
||||
return os.Rename(dst+".tmp", dst)
|
||||
}
|
||||
|
||||
+3
-2
@@ -10,6 +10,7 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/blakesmith/ar"
|
||||
"github.com/tinygo-org/tinygo/goenv"
|
||||
)
|
||||
|
||||
// These are the GENERIC_SOURCES according to CMakeList.txt.
|
||||
@@ -169,13 +170,13 @@ func builtinFiles(target string) []string {
|
||||
|
||||
// builtinsDir returns the directory where the sources for compiler-rt are kept.
|
||||
func builtinsDir() string {
|
||||
return filepath.Join(sourceDir(), "lib", "compiler-rt", "lib", "builtins")
|
||||
return filepath.Join(goenv.Get("TINYGOROOT"), "lib", "compiler-rt", "lib", "builtins")
|
||||
}
|
||||
|
||||
// Get the builtins archive, possibly generating it as needed.
|
||||
func loadBuiltins(target string) (path string, err error) {
|
||||
// Try to load a precompiled compiler-rt library.
|
||||
precompiledPath := filepath.Join(sourceDir(), "pkg", target, "compiler-rt.a")
|
||||
precompiledPath := filepath.Join(goenv.Get("TINYGOROOT"), "pkg", target, "compiler-rt.a")
|
||||
if _, err := os.Stat(precompiledPath); err == nil {
|
||||
// Found a precompiled compiler-rt for this OS/architecture. Return the
|
||||
// path directly.
|
||||
|
||||
+9
-1
@@ -24,6 +24,14 @@ func init() {
|
||||
commands["ld.lld"] = append(commands["ld.lld"], "/usr/local/opt/llvm/bin/ld.lld")
|
||||
commands["wasm-ld"] = append(commands["wasm-ld"], "/usr/local/opt/llvm/bin/wasm-ld")
|
||||
}
|
||||
// Add the path for when LLVM was installed with the installer from
|
||||
// llvm.org, which by default doesn't add LLVM to the $PATH environment
|
||||
// variable.
|
||||
if runtime.GOOS == "windows" {
|
||||
commands["clang"] = append(commands["clang"], "clang", "C:\\Program Files\\LLVM\\bin\\clang.exe")
|
||||
commands["ld.lld"] = append(commands["ld.lld"], "lld", "C:\\Program Files\\LLVM\\bin\\lld.exe")
|
||||
commands["wasm-ld"] = append(commands["wasm-ld"], "C:\\Program Files\\LLVM\\bin\\wasm-ld.exe")
|
||||
}
|
||||
}
|
||||
|
||||
func execCommand(cmdNames []string, args ...string) error {
|
||||
@@ -33,7 +41,7 @@ func execCommand(cmdNames []string, args ...string) error {
|
||||
cmd.Stderr = os.Stderr
|
||||
err := cmd.Run()
|
||||
if err != nil {
|
||||
if err, ok := err.(*exec.Error); ok && err.Err == exec.ErrNotFound {
|
||||
if err, ok := err.(*exec.Error); ok && (err.Err == exec.ErrNotFound || err.Err.Error() == "file does not exist") {
|
||||
// this command was not found, try the next
|
||||
continue
|
||||
}
|
||||
|
||||
+16
-4
@@ -22,7 +22,7 @@ func (c *Compiler) emitLookupBoundsCheck(frame *Frame, arrayLen, index llvm.Valu
|
||||
if index.Type().IntTypeWidth() < arrayLen.Type().IntTypeWidth() {
|
||||
// Sometimes, the index can be e.g. an uint8 or int8, and we have to
|
||||
// correctly extend that type.
|
||||
if indexType.(*types.Basic).Info()&types.IsUnsigned == 0 {
|
||||
if indexType.Underlying().(*types.Basic).Info()&types.IsUnsigned == 0 {
|
||||
index = c.builder.CreateZExt(index, arrayLen.Type(), "")
|
||||
} else {
|
||||
index = c.builder.CreateSExt(index, arrayLen.Type(), "")
|
||||
@@ -56,7 +56,7 @@ func (c *Compiler) emitLookupBoundsCheck(frame *Frame, arrayLen, index llvm.Valu
|
||||
// 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, max llvm.Value, lowType, highType, maxType *types.Basic) {
|
||||
if frame.fn.IsNoBounds() {
|
||||
// The //go:nobounds pragma was added to the function to avoid bounds
|
||||
// checking.
|
||||
@@ -71,6 +71,9 @@ func (c *Compiler) emitSliceBoundsCheck(frame *Frame, capacity, low, high llvm.V
|
||||
if high.Type().IntTypeWidth() > capacityType.IntTypeWidth() {
|
||||
capacityType = high.Type()
|
||||
}
|
||||
if max.Type().IntTypeWidth() > capacityType.IntTypeWidth() {
|
||||
capacityType = max.Type()
|
||||
}
|
||||
if capacityType != capacity.Type() {
|
||||
capacity = c.builder.CreateZExt(capacity, capacityType, "")
|
||||
}
|
||||
@@ -90,6 +93,13 @@ func (c *Compiler) emitSliceBoundsCheck(frame *Frame, capacity, low, high llvm.V
|
||||
high = c.builder.CreateSExt(high, capacityType, "")
|
||||
}
|
||||
}
|
||||
if max.Type().IntTypeWidth() < capacityType.IntTypeWidth() {
|
||||
if maxType.Info()&types.IsUnsigned != 0 {
|
||||
max = c.builder.CreateZExt(max, capacityType, "")
|
||||
} else {
|
||||
max = c.builder.CreateSExt(max, capacityType, "")
|
||||
}
|
||||
}
|
||||
|
||||
faultBlock := c.ctx.AddBasicBlock(frame.fn.LLVMFn, "slice.outofbounds")
|
||||
nextBlock := c.ctx.AddBasicBlock(frame.fn.LLVMFn, "slice.next")
|
||||
@@ -97,8 +107,10 @@ func (c *Compiler) emitSliceBoundsCheck(frame *Frame, capacity, low, high llvm.V
|
||||
|
||||
// Now do the bounds check: low > high || high > capacity
|
||||
outOfBounds1 := c.builder.CreateICmp(llvm.IntUGT, low, high, "slice.lowhigh")
|
||||
outOfBounds2 := c.builder.CreateICmp(llvm.IntUGT, high, capacity, "slice.highcap")
|
||||
outOfBounds := c.builder.CreateOr(outOfBounds1, outOfBounds2, "slice.outofbounds")
|
||||
outOfBounds2 := c.builder.CreateICmp(llvm.IntUGT, high, max, "slice.highmax")
|
||||
outOfBounds3 := c.builder.CreateICmp(llvm.IntUGT, max, capacity, "slice.maxcap")
|
||||
outOfBounds := c.builder.CreateOr(outOfBounds1, outOfBounds2, "slice.lowmax")
|
||||
outOfBounds = c.builder.CreateOr(outOfBounds, outOfBounds3, "slice.lowcap")
|
||||
c.builder.CreateCondBr(outOfBounds, faultBlock, nextBlock)
|
||||
|
||||
// Fail: this is a nil pointer, exit with a panic.
|
||||
|
||||
+5
-1
@@ -1,6 +1,7 @@
|
||||
package compiler
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"golang.org/x/tools/go/ssa"
|
||||
"tinygo.org/x/go-llvm"
|
||||
)
|
||||
@@ -20,6 +21,9 @@ func (c *Compiler) createRuntimeCall(fnName string, args []llvm.Value, name stri
|
||||
panic("trying to call runtime." + fnName)
|
||||
}
|
||||
fn := c.ir.GetFunction(member.(*ssa.Function))
|
||||
if fn.LLVMFn.IsNil() {
|
||||
panic(fmt.Errorf("function %s does not appear in LLVM IR", fnName))
|
||||
}
|
||||
if !fn.IsExported() {
|
||||
args = append(args, llvm.Undef(c.i8ptrType)) // unused context parameter
|
||||
args = append(args, llvm.ConstPointerNull(c.i8ptrType)) // coroutine handle
|
||||
@@ -163,7 +167,7 @@ func (c *Compiler) collapseFormalParamInternal(t llvm.Type, fields []llvm.Value)
|
||||
switch t.TypeKind() {
|
||||
case llvm.StructTypeKind:
|
||||
if len(c.flattenAggregateType(t)) <= MaxFieldsPerParam {
|
||||
value := c.getZeroValue(t)
|
||||
value := llvm.ConstNull(t)
|
||||
for i, subtyp := range t.StructElementTypes() {
|
||||
structField, remaining := c.collapseFormalParamInternal(subtyp, fields)
|
||||
fields = remaining
|
||||
|
||||
+8
-27
@@ -4,32 +4,17 @@ package compiler
|
||||
// or pseudo-operations that are lowered during goroutine lowering.
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"go/types"
|
||||
|
||||
"golang.org/x/tools/go/ssa"
|
||||
"tinygo.org/x/go-llvm"
|
||||
)
|
||||
|
||||
// emitMakeChan returns a new channel value for the given channel type.
|
||||
func (c *Compiler) emitMakeChan(expr *ssa.MakeChan) (llvm.Value, error) {
|
||||
chanType := c.getLLVMType(expr.Type())
|
||||
size := c.targetData.TypeAllocSize(chanType.ElementType())
|
||||
sizeValue := llvm.ConstInt(c.uintptrType, size, false)
|
||||
ptr := c.createRuntimeCall("alloc", []llvm.Value{sizeValue}, "chan.alloc")
|
||||
ptr = c.builder.CreateBitCast(ptr, chanType, "chan")
|
||||
// Set the elementSize field
|
||||
elementSizePtr := c.builder.CreateGEP(ptr, []llvm.Value{
|
||||
llvm.ConstInt(c.ctx.Int32Type(), 0, false),
|
||||
llvm.ConstInt(c.ctx.Int32Type(), 0, false),
|
||||
}, "")
|
||||
func (c *Compiler) emitMakeChan(frame *Frame, expr *ssa.MakeChan) llvm.Value {
|
||||
elementSize := c.targetData.TypeAllocSize(c.getLLVMType(expr.Type().(*types.Chan).Elem()))
|
||||
if elementSize > 0xffff {
|
||||
return ptr, c.makeError(expr.Pos(), fmt.Sprintf("element size is %d bytes, which is bigger than the maximum of %d bytes", elementSize, 0xffff))
|
||||
}
|
||||
elementSizeValue := llvm.ConstInt(c.ctx.Int16Type(), elementSize, false)
|
||||
c.builder.CreateStore(elementSizeValue, elementSizePtr)
|
||||
return ptr, nil
|
||||
elementSizeValue := llvm.ConstInt(c.uintptrType, elementSize, false)
|
||||
bufSize := c.getValue(frame, expr.Size)
|
||||
return c.createRuntimeCall("chanMake", []llvm.Value{elementSizeValue, bufSize}, "")
|
||||
}
|
||||
|
||||
// emitChanSend emits a pseudo chan send operation. It is lowered to the actual
|
||||
@@ -44,8 +29,7 @@ func (c *Compiler) emitChanSend(frame *Frame, instr *ssa.Send) {
|
||||
c.builder.CreateStore(chanValue, valueAlloca)
|
||||
|
||||
// Do the send.
|
||||
coroutine := c.createRuntimeCall("getCoroutine", nil, "")
|
||||
c.createRuntimeCall("chanSend", []llvm.Value{coroutine, ch, valueAllocaCast}, "")
|
||||
c.createRuntimeCall("chanSend", []llvm.Value{ch, valueAllocaCast}, "")
|
||||
|
||||
// End the lifetime of the alloca.
|
||||
// This also works around a bug in CoroSplit, at least in LLVM 8:
|
||||
@@ -63,14 +47,11 @@ func (c *Compiler) emitChanRecv(frame *Frame, unop *ssa.UnOp) llvm.Value {
|
||||
valueAlloca, valueAllocaCast, valueAllocaSize := c.createTemporaryAlloca(valueType, "chan.value")
|
||||
|
||||
// Do the receive.
|
||||
coroutine := c.createRuntimeCall("getCoroutine", nil, "")
|
||||
c.createRuntimeCall("chanRecv", []llvm.Value{coroutine, ch, valueAllocaCast}, "")
|
||||
commaOk := c.createRuntimeCall("chanRecv", []llvm.Value{ch, valueAllocaCast}, "")
|
||||
received := c.builder.CreateLoad(valueAlloca, "chan.received")
|
||||
c.emitLifetimeEnd(valueAllocaCast, valueAllocaSize)
|
||||
|
||||
if unop.CommaOk {
|
||||
commaOk := c.createRuntimeCall("getTaskPromiseData", []llvm.Value{coroutine}, "chan.commaOk.wide")
|
||||
commaOk = c.builder.CreateTrunc(commaOk, c.ctx.Int1Type(), "chan.commaOk")
|
||||
tuple := llvm.Undef(c.ctx.StructType([]llvm.Type{valueType, c.ctx.Int1Type()}, false))
|
||||
tuple = c.builder.CreateInsertValue(tuple, received, 0, "")
|
||||
tuple = c.builder.CreateInsertValue(tuple, commaOk, 1, "")
|
||||
@@ -95,7 +76,7 @@ func (c *Compiler) emitSelect(frame *Frame, expr *ssa.Select) llvm.Value {
|
||||
if expr.Blocking {
|
||||
// Blocks forever:
|
||||
// select {}
|
||||
c.createRuntimeCall("deadlockStub", nil, "")
|
||||
c.createRuntimeCall("deadlock", nil, "")
|
||||
return llvm.Undef(llvmType)
|
||||
} else {
|
||||
// No-op:
|
||||
@@ -124,7 +105,7 @@ func (c *Compiler) emitSelect(frame *Frame, expr *ssa.Select) llvm.Value {
|
||||
chanSelectStateType := c.getLLVMRuntimeType("chanSelectState")
|
||||
for _, state := range expr.States {
|
||||
ch := c.getValue(frame, state.Chan)
|
||||
selectState := c.getZeroValue(chanSelectStateType)
|
||||
selectState := llvm.ConstNull(chanSelectStateType)
|
||||
selectState = c.builder.CreateInsertValue(selectState, ch, 0, "")
|
||||
switch state.Dir {
|
||||
case types.RecvOnly:
|
||||
|
||||
@@ -0,0 +1,128 @@
|
||||
package compiler
|
||||
|
||||
// This file implements a set of sanity checks for the IR that is generated.
|
||||
// It can catch some mistakes that LLVM's verifier cannot.
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"tinygo.org/x/go-llvm"
|
||||
)
|
||||
|
||||
func (c *Compiler) checkType(t llvm.Type, checked map[llvm.Type]struct{}, specials map[llvm.TypeKind]llvm.Type) {
|
||||
if t.IsNil() {
|
||||
panic(t)
|
||||
}
|
||||
|
||||
// prevent infinite recursion for self-referential types
|
||||
if _, ok := checked[t]; ok {
|
||||
return
|
||||
}
|
||||
checked[t] = struct{}{}
|
||||
|
||||
// check for any context mismatches
|
||||
switch {
|
||||
case t.Context() == c.ctx:
|
||||
// this is correct
|
||||
case t.Context() == llvm.GlobalContext():
|
||||
// somewhere we accidentally used the global context instead of a real context
|
||||
panic(fmt.Errorf("type %q uses global context", t.String()))
|
||||
default:
|
||||
// we used some other context by accident
|
||||
panic(fmt.Errorf("type %q uses context %v instead of the main context %v", t.Context(), c.ctx))
|
||||
}
|
||||
|
||||
// if this is a composite type, check the components of the type
|
||||
switch t.TypeKind() {
|
||||
case llvm.VoidTypeKind, llvm.LabelTypeKind, llvm.TokenTypeKind, llvm.MetadataTypeKind:
|
||||
// there should only be one of any of these
|
||||
if s, ok := specials[t.TypeKind()]; !ok {
|
||||
specials[t.TypeKind()] = t
|
||||
} else if s != t {
|
||||
panic(fmt.Errorf("duplicate special type %q: %v and %v", t.TypeKind().String(), t, s))
|
||||
}
|
||||
case llvm.FloatTypeKind, llvm.DoubleTypeKind, llvm.X86_FP80TypeKind, llvm.FP128TypeKind, llvm.PPC_FP128TypeKind:
|
||||
// floating point numbers are primitives - nothing to recurse
|
||||
case llvm.IntegerTypeKind:
|
||||
// integers are primitives - nothing to recurse
|
||||
case llvm.FunctionTypeKind:
|
||||
// check arguments and return(s)
|
||||
for _, v := range t.ParamTypes() {
|
||||
c.checkType(v, checked, specials)
|
||||
}
|
||||
c.checkType(t.ReturnType(), checked, specials)
|
||||
case llvm.StructTypeKind:
|
||||
// check all elements
|
||||
for _, v := range t.StructElementTypes() {
|
||||
c.checkType(v, checked, specials)
|
||||
}
|
||||
case llvm.ArrayTypeKind:
|
||||
// check element type
|
||||
c.checkType(t.ElementType(), checked, specials)
|
||||
case llvm.PointerTypeKind:
|
||||
// check underlying type
|
||||
c.checkType(t.ElementType(), checked, specials)
|
||||
case llvm.VectorTypeKind:
|
||||
// check element type
|
||||
c.checkType(t.ElementType(), checked, specials)
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Compiler) checkValue(v llvm.Value, types map[llvm.Type]struct{}, specials map[llvm.TypeKind]llvm.Type) {
|
||||
// check type
|
||||
c.checkType(v.Type(), types, specials)
|
||||
}
|
||||
|
||||
func (c *Compiler) checkInstruction(inst llvm.Value, types map[llvm.Type]struct{}, specials map[llvm.TypeKind]llvm.Type) {
|
||||
// check value properties
|
||||
c.checkValue(inst, types, specials)
|
||||
|
||||
// check operands
|
||||
for i := 0; i < inst.OperandsCount(); i++ {
|
||||
c.checkValue(inst.Operand(i), types, specials)
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Compiler) checkBasicBlock(bb llvm.BasicBlock, types map[llvm.Type]struct{}, specials map[llvm.TypeKind]llvm.Type) {
|
||||
// check basic block value and type
|
||||
c.checkValue(bb.AsValue(), types, specials)
|
||||
|
||||
// check instructions
|
||||
for inst := bb.FirstInstruction(); !inst.IsNil(); inst = llvm.NextInstruction(inst) {
|
||||
c.checkInstruction(inst, types, specials)
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Compiler) checkFunction(fn llvm.Value, types map[llvm.Type]struct{}, specials map[llvm.TypeKind]llvm.Type) {
|
||||
// check function value and type
|
||||
c.checkValue(fn, types, specials)
|
||||
|
||||
// check basic blocks
|
||||
for bb := fn.FirstBasicBlock(); !bb.IsNil(); bb = llvm.NextBasicBlock(bb) {
|
||||
c.checkBasicBlock(bb, types, specials)
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Compiler) checkModule() {
|
||||
// check for any context mismatches
|
||||
switch {
|
||||
case c.mod.Context() == c.ctx:
|
||||
// this is correct
|
||||
case c.mod.Context() == llvm.GlobalContext():
|
||||
// somewhere we accidentally used the global context instead of a real context
|
||||
panic(errors.New("module uses global context"))
|
||||
default:
|
||||
// we used some other context by accident
|
||||
panic(fmt.Errorf("module uses context %v instead of the main context %v", c.mod.Context(), c.ctx))
|
||||
}
|
||||
|
||||
types := map[llvm.Type]struct{}{}
|
||||
specials := map[llvm.TypeKind]llvm.Type{}
|
||||
for fn := c.mod.FirstFunction(); !fn.IsNil(); fn = llvm.NextFunction(fn) {
|
||||
c.checkFunction(fn, types, specials)
|
||||
}
|
||||
for g := c.mod.FirstGlobal(); !g.IsNil(); g = llvm.NextGlobal(g) {
|
||||
c.checkValue(g, types, specials)
|
||||
}
|
||||
}
|
||||
+186
-104
@@ -1,6 +1,7 @@
|
||||
package compiler
|
||||
|
||||
import (
|
||||
"debug/dwarf"
|
||||
"errors"
|
||||
"fmt"
|
||||
"go/ast"
|
||||
@@ -30,6 +31,32 @@ func init() {
|
||||
// The TinyGo import path.
|
||||
const tinygoPath = "github.com/tinygo-org/tinygo"
|
||||
|
||||
// functionsUsedInTransform is a list of function symbols that may be used
|
||||
// during TinyGo optimization passes so they have to be marked as external
|
||||
// linkage until all TinyGo passes have finished.
|
||||
var functionsUsedInTransforms = []string{
|
||||
"runtime.alloc",
|
||||
"runtime.free",
|
||||
"runtime.scheduler",
|
||||
"runtime.nilPanic",
|
||||
}
|
||||
|
||||
var taskFunctionsUsedInTransforms = []string{
|
||||
"runtime.startGoroutine",
|
||||
}
|
||||
|
||||
var coroFunctionsUsedInTransforms = []string{
|
||||
"runtime.avrSleep",
|
||||
"runtime.getFakeCoroutine",
|
||||
"runtime.setTaskStatePtr",
|
||||
"runtime.getTaskStatePtr",
|
||||
"runtime.activateTask",
|
||||
"runtime.noret",
|
||||
"runtime.getParentHandle",
|
||||
"runtime.getCoroutine",
|
||||
"runtime.llvmCoroRefHolder",
|
||||
}
|
||||
|
||||
// Configure the compiler.
|
||||
type Config struct {
|
||||
Triple string // LLVM target triple, e.g. x86_64-unknown-linux-gnu (empty string means default)
|
||||
@@ -38,11 +65,13 @@ type Config struct {
|
||||
GOOS string //
|
||||
GOARCH string //
|
||||
GC string // garbage collection strategy
|
||||
PanicStrategy string // panic strategy ("abort" or "trap")
|
||||
Scheduler string // scheduler implementation ("coroutines" or "tasks")
|
||||
PanicStrategy string // panic strategy ("print" or "trap")
|
||||
CFlags []string // cflags to pass to cgo
|
||||
LDFlags []string // ldflags to pass to cgo
|
||||
ClangHeaders string // Clang built-in header include path
|
||||
DumpSSA bool // dump Go SSA, for compiler debugging
|
||||
VerifyIR bool // run extra checks on the IR
|
||||
Debug bool // add debug symbols for gdb
|
||||
GOROOT string // GOROOT
|
||||
TINYGOROOT string // GOROOT for TinyGo
|
||||
@@ -64,6 +93,7 @@ type Compiler struct {
|
||||
dibuilder *llvm.DIBuilder
|
||||
cu llvm.Metadata
|
||||
difiles map[string]llvm.Metadata
|
||||
ditypes map[types.Type]llvm.Metadata
|
||||
machine llvm.TargetMachine
|
||||
targetData llvm.TargetData
|
||||
intType llvm.Type
|
||||
@@ -109,6 +139,7 @@ func NewCompiler(pkgName string, config Config) (*Compiler, error) {
|
||||
c := &Compiler{
|
||||
Config: config,
|
||||
difiles: make(map[string]llvm.Metadata),
|
||||
ditypes: make(map[types.Type]llvm.Metadata),
|
||||
}
|
||||
|
||||
target, err := llvm.GetTargetFromTriple(config.Triple)
|
||||
@@ -160,11 +191,6 @@ func (c *Compiler) Module() llvm.Module {
|
||||
return c.mod
|
||||
}
|
||||
|
||||
// Return the LLVM target data object. Only valid after a successful compile.
|
||||
func (c *Compiler) TargetData() llvm.TargetData {
|
||||
return c.targetData
|
||||
}
|
||||
|
||||
// selectGC picks an appropriate GC strategy if none was provided.
|
||||
func (c *Compiler) selectGC() string {
|
||||
if c.GC != "" {
|
||||
@@ -173,6 +199,31 @@ func (c *Compiler) selectGC() string {
|
||||
return "conservative"
|
||||
}
|
||||
|
||||
// selectScheduler picks an appropriate scheduler for the target if none was
|
||||
// given.
|
||||
func (c *Compiler) selectScheduler() string {
|
||||
if c.Scheduler != "" {
|
||||
// A scheduler was specified in the target description.
|
||||
return c.Scheduler
|
||||
}
|
||||
// Fall back to coroutines, which are supported everywhere.
|
||||
return "coroutines"
|
||||
}
|
||||
|
||||
// getFunctionsUsedInTransforms gets a list of all special functions that should be preserved during transforms and optimization.
|
||||
func (c *Compiler) getFunctionsUsedInTransforms() []string {
|
||||
fnused := functionsUsedInTransforms
|
||||
switch c.selectScheduler() {
|
||||
case "coroutines":
|
||||
fnused = append(append([]string{}, fnused...), coroFunctionsUsedInTransforms...)
|
||||
case "tasks":
|
||||
fnused = append(append([]string{}, fnused...), taskFunctionsUsedInTransforms...)
|
||||
default:
|
||||
panic(fmt.Errorf("invalid scheduler %q", c.selectScheduler()))
|
||||
}
|
||||
return fnused
|
||||
}
|
||||
|
||||
// Compile the given package path or .go file path. Return an error when this
|
||||
// fails (in any stage).
|
||||
func (c *Compiler) Compile(mainPath string) []error {
|
||||
@@ -189,6 +240,7 @@ func (c *Compiler) Compile(mainPath string) []error {
|
||||
if err != nil {
|
||||
return []error{err}
|
||||
}
|
||||
buildTags := append([]string{"tinygo", "gc." + c.selectGC(), "scheduler." + c.selectScheduler()}, c.BuildTags...)
|
||||
lprogram := &loader.Program{
|
||||
Build: &build.Context{
|
||||
GOARCH: c.GOARCH,
|
||||
@@ -198,7 +250,7 @@ func (c *Compiler) Compile(mainPath string) []error {
|
||||
CgoEnabled: true,
|
||||
UseAllFiles: false,
|
||||
Compiler: "gc", // must be one of the recognized compilers
|
||||
BuildTags: append([]string{"tinygo", "gc." + c.selectGC()}, c.BuildTags...),
|
||||
BuildTags: buildTags,
|
||||
},
|
||||
OverlayBuild: &build.Context{
|
||||
GOARCH: c.GOARCH,
|
||||
@@ -208,7 +260,7 @@ func (c *Compiler) Compile(mainPath string) []error {
|
||||
CgoEnabled: true,
|
||||
UseAllFiles: false,
|
||||
Compiler: "gc", // must be one of the recognized compilers
|
||||
BuildTags: append([]string{"tinygo", "gc." + c.selectGC()}, c.BuildTags...),
|
||||
BuildTags: buildTags,
|
||||
},
|
||||
OverlayPath: func(path string) string {
|
||||
// Return the (overlay) import path when it should be overlaid, and
|
||||
@@ -219,14 +271,14 @@ func (c *Compiler) Compile(mainPath string) []error {
|
||||
path = path[len(tinygoPath+"/src/"):]
|
||||
}
|
||||
switch path {
|
||||
case "machine", "os", "reflect", "runtime", "runtime/volatile", "sync", "testing":
|
||||
case "machine", "os", "reflect", "runtime", "runtime/volatile", "sync", "testing", "internal/reflectlite":
|
||||
return path
|
||||
default:
|
||||
if strings.HasPrefix(path, "device/") || strings.HasPrefix(path, "examples/") {
|
||||
return path
|
||||
} else if path == "syscall" {
|
||||
for _, tag := range c.BuildTags {
|
||||
if tag == "avr" || tag == "cortexm" || tag == "darwin" || tag == "riscv" {
|
||||
if tag == "baremetal" || tag == "darwin" {
|
||||
return path
|
||||
}
|
||||
}
|
||||
@@ -335,13 +387,15 @@ func (c *Compiler) Compile(mainPath string) []error {
|
||||
// would be optimized away.
|
||||
realMain := c.mod.NamedFunction(c.ir.MainPkg().Pkg.Path() + ".main")
|
||||
realMain.SetLinkage(llvm.ExternalLinkage) // keep alive until goroutine lowering
|
||||
c.mod.NamedFunction("runtime.alloc").SetLinkage(llvm.ExternalLinkage)
|
||||
c.mod.NamedFunction("runtime.free").SetLinkage(llvm.ExternalLinkage)
|
||||
c.mod.NamedFunction("runtime.sleepTask").SetLinkage(llvm.ExternalLinkage)
|
||||
c.mod.NamedFunction("runtime.setTaskPromisePtr").SetLinkage(llvm.ExternalLinkage)
|
||||
c.mod.NamedFunction("runtime.getTaskPromisePtr").SetLinkage(llvm.ExternalLinkage)
|
||||
c.mod.NamedFunction("runtime.activateTask").SetLinkage(llvm.ExternalLinkage)
|
||||
c.mod.NamedFunction("runtime.scheduler").SetLinkage(llvm.ExternalLinkage)
|
||||
|
||||
// Make sure these functions are kept in tact during TinyGo transformation passes.
|
||||
for _, name := range c.getFunctionsUsedInTransforms() {
|
||||
fn := c.mod.NamedFunction(name)
|
||||
if fn.IsNil() {
|
||||
panic(fmt.Errorf("missing core function %q", name))
|
||||
}
|
||||
fn.SetLinkage(llvm.ExternalLinkage)
|
||||
}
|
||||
|
||||
// Load some attributes
|
||||
getAttr := func(attrName string) llvm.Attribute {
|
||||
@@ -533,42 +587,6 @@ func (c *Compiler) getLLVMType(goType types.Type) llvm.Type {
|
||||
}
|
||||
}
|
||||
|
||||
// Return a zero LLVM value for any LLVM type. Setting this value as an
|
||||
// initializer has the same effect as setting 'zeroinitializer' on a value.
|
||||
// Sadly, I haven't found a way to do it directly with the Go API but this works
|
||||
// just fine.
|
||||
func (c *Compiler) getZeroValue(typ llvm.Type) llvm.Value {
|
||||
switch typ.TypeKind() {
|
||||
case llvm.ArrayTypeKind:
|
||||
subTyp := typ.ElementType()
|
||||
subVal := c.getZeroValue(subTyp)
|
||||
vals := make([]llvm.Value, typ.ArrayLength())
|
||||
for i := range vals {
|
||||
vals[i] = subVal
|
||||
}
|
||||
return llvm.ConstArray(subTyp, vals)
|
||||
case llvm.FloatTypeKind, llvm.DoubleTypeKind:
|
||||
return llvm.ConstFloat(typ, 0.0)
|
||||
case llvm.IntegerTypeKind:
|
||||
return llvm.ConstInt(typ, 0, false)
|
||||
case llvm.PointerTypeKind:
|
||||
return llvm.ConstPointerNull(typ)
|
||||
case llvm.StructTypeKind:
|
||||
types := typ.StructElementTypes()
|
||||
vals := make([]llvm.Value, len(types))
|
||||
for i, subTyp := range types {
|
||||
vals[i] = c.getZeroValue(subTyp)
|
||||
}
|
||||
if typ.StructName() != "" {
|
||||
return llvm.ConstNamedStruct(typ, vals)
|
||||
} else {
|
||||
return c.ctx.ConstStruct(vals, false)
|
||||
}
|
||||
default:
|
||||
panic("unknown LLVM zero inititializer: " + typ.String())
|
||||
}
|
||||
}
|
||||
|
||||
// Is this a pointer type of some sort? Can be unsafe.Pointer or any *T pointer.
|
||||
func isPointer(typ types.Type) bool {
|
||||
if _, ok := typ.(*types.Pointer); ok {
|
||||
@@ -582,6 +600,17 @@ func isPointer(typ types.Type) bool {
|
||||
|
||||
// Get the DWARF type for this Go type.
|
||||
func (c *Compiler) getDIType(typ types.Type) llvm.Metadata {
|
||||
if md, ok := c.ditypes[typ]; ok {
|
||||
return md
|
||||
}
|
||||
md := c.createDIType(typ)
|
||||
c.ditypes[typ] = md
|
||||
return md
|
||||
}
|
||||
|
||||
// createDIType creates a new DWARF type. Don't call this function directly,
|
||||
// call getDIType instead.
|
||||
func (c *Compiler) createDIType(typ types.Type) llvm.Metadata {
|
||||
llvmType := c.getLLVMType(typ)
|
||||
sizeInBytes := c.targetData.TypeAllocSize(llvmType)
|
||||
switch typ := typ.(type) {
|
||||
@@ -718,14 +747,17 @@ func (c *Compiler) getDIType(typ types.Type) llvm.Metadata {
|
||||
},
|
||||
})
|
||||
case *types.Struct:
|
||||
// Placeholder metadata node, to be replaced afterwards.
|
||||
temporaryMDNode := c.dibuilder.CreateReplaceableCompositeType(llvm.Metadata{}, llvm.DIReplaceableCompositeType{
|
||||
Tag: dwarf.TagStructType,
|
||||
SizeInBits: sizeInBytes * 8,
|
||||
AlignInBits: uint32(c.targetData.ABITypeAlignment(llvmType)) * 8,
|
||||
})
|
||||
c.ditypes[typ] = temporaryMDNode
|
||||
elements := make([]llvm.Metadata, typ.NumFields())
|
||||
for i := range elements {
|
||||
field := typ.Field(i)
|
||||
fieldType := field.Type()
|
||||
if _, ok := fieldType.Underlying().(*types.Pointer); ok {
|
||||
// XXX hack to avoid recursive types
|
||||
fieldType = types.Typ[types.UnsafePointer]
|
||||
}
|
||||
llvmField := c.getLLVMType(fieldType)
|
||||
elements[i] = c.dibuilder.CreateMemberType(llvm.Metadata{}, llvm.DIMemberType{
|
||||
Name: field.Name(),
|
||||
@@ -735,11 +767,13 @@ func (c *Compiler) getDIType(typ types.Type) llvm.Metadata {
|
||||
Type: c.getDIType(fieldType),
|
||||
})
|
||||
}
|
||||
return c.dibuilder.CreateStructType(llvm.Metadata{}, llvm.DIStructType{
|
||||
md := c.dibuilder.CreateStructType(llvm.Metadata{}, llvm.DIStructType{
|
||||
SizeInBits: sizeInBytes * 8,
|
||||
AlignInBits: uint32(c.targetData.ABITypeAlignment(llvmType)) * 8,
|
||||
Elements: elements,
|
||||
})
|
||||
temporaryMDNode.ReplaceAllUsesWith(md)
|
||||
return md
|
||||
default:
|
||||
panic("unknown type while generating DWARF debug type: " + typ.String())
|
||||
}
|
||||
@@ -791,6 +825,11 @@ func (c *Compiler) parseFuncDecl(f *ir.Function) *Frame {
|
||||
// External/exported functions may not retain pointer values.
|
||||
// https://golang.org/cmd/cgo/#hdr-Passing_pointers
|
||||
if f.IsExported() {
|
||||
// Set the wasm-import-module attribute if the function's module is set.
|
||||
if f.Module() != "" {
|
||||
wasmImportModuleAttr := c.ctx.CreateStringAttribute("wasm-import-module", f.Module())
|
||||
frame.fn.LLVMFn.AddFunctionAttr(wasmImportModuleAttr)
|
||||
}
|
||||
nocaptureKind := llvm.AttributeKindID("nocapture")
|
||||
nocapture := c.ctx.CreateEnumAttribute(nocaptureKind, 0)
|
||||
for i, typ := range paramTypes {
|
||||
@@ -1030,36 +1069,47 @@ func (c *Compiler) parseInstr(frame *Frame, instr ssa.Instruction) {
|
||||
case *ssa.Defer:
|
||||
c.emitDefer(frame, instr)
|
||||
case *ssa.Go:
|
||||
if instr.Call.IsInvoke() {
|
||||
c.addError(instr.Pos(), "todo: go on method receiver")
|
||||
return
|
||||
}
|
||||
callee := instr.Call.StaticCallee()
|
||||
if callee == nil {
|
||||
c.addError(instr.Pos(), "todo: go on non-direct function (function pointer, etc.)")
|
||||
return
|
||||
}
|
||||
calleeFn := c.ir.GetFunction(callee)
|
||||
|
||||
// Mark this function as a 'go' invocation and break invalid
|
||||
// interprocedural optimizations. For example, heap-to-stack
|
||||
// transformations are not sound as goroutines can outlive their parent.
|
||||
calleeType := calleeFn.LLVMFn.Type()
|
||||
calleeValue := c.builder.CreateBitCast(calleeFn.LLVMFn, c.i8ptrType, "")
|
||||
calleeValue = c.createRuntimeCall("makeGoroutine", []llvm.Value{calleeValue}, "")
|
||||
calleeValue = c.builder.CreateBitCast(calleeValue, calleeType, "")
|
||||
|
||||
// Get all function parameters to pass to the goroutine.
|
||||
var params []llvm.Value
|
||||
for _, param := range instr.Call.Args {
|
||||
params = append(params, c.getValue(frame, param))
|
||||
}
|
||||
if !calleeFn.IsExported() {
|
||||
params = append(params, llvm.Undef(c.i8ptrType)) // context parameter
|
||||
params = append(params, llvm.Undef(c.i8ptrType)) // parent coroutine handle
|
||||
}
|
||||
|
||||
c.createCall(calleeValue, params, "")
|
||||
// Start a new goroutine.
|
||||
if callee := instr.Call.StaticCallee(); callee != nil {
|
||||
// Static callee is known. This makes it easier to start a new
|
||||
// goroutine.
|
||||
calleeFn := c.ir.GetFunction(callee)
|
||||
if !calleeFn.IsExported() && c.selectScheduler() != "tasks" {
|
||||
// For coroutine scheduling, this is only required when calling
|
||||
// an external function.
|
||||
// For tasks, because all params are stored in a single object,
|
||||
// no unnecessary parameters should be stored anyway.
|
||||
params = append(params, llvm.Undef(c.i8ptrType)) // context parameter
|
||||
params = append(params, llvm.ConstPointerNull(c.i8ptrType)) // parent coroutine handle
|
||||
}
|
||||
c.emitStartGoroutine(calleeFn.LLVMFn, params)
|
||||
} else if !instr.Call.IsInvoke() {
|
||||
// This is a function pointer.
|
||||
// At the moment, two extra params are passed to the newly started
|
||||
// goroutine:
|
||||
// * The function context, for closures.
|
||||
// * The parent handle (for coroutines) or the function pointer
|
||||
// itself (for tasks).
|
||||
funcPtr, context := c.decodeFuncValue(c.getValue(frame, instr.Call.Value), instr.Call.Value.Type().(*types.Signature))
|
||||
params = append(params, context) // context parameter
|
||||
switch c.selectScheduler() {
|
||||
case "coroutines":
|
||||
params = append(params, llvm.ConstPointerNull(c.i8ptrType)) // parent coroutine handle
|
||||
case "tasks":
|
||||
params = append(params, funcPtr)
|
||||
default:
|
||||
panic("unknown scheduler type")
|
||||
}
|
||||
c.emitStartGoroutine(funcPtr, params)
|
||||
} else {
|
||||
c.addError(instr.Pos(), "todo: go on interface call")
|
||||
}
|
||||
case *ssa.If:
|
||||
cond := c.getValue(frame, instr.Cond)
|
||||
block := instr.Block()
|
||||
@@ -1086,7 +1136,7 @@ func (c *Compiler) parseInstr(frame *Frame, instr ssa.Instruction) {
|
||||
c.builder.CreateRet(c.getValue(frame, instr.Results[0]))
|
||||
} else {
|
||||
// Multiple return values. Put them all in a struct.
|
||||
retVal := c.getZeroValue(frame.fn.LLVMFn.Type().ElementType().ReturnType())
|
||||
retVal := llvm.ConstNull(frame.fn.LLVMFn.Type().ElementType().ReturnType())
|
||||
for i, result := range instr.Results {
|
||||
val := c.getValue(frame, result)
|
||||
retVal = c.builder.CreateInsertValue(retVal, val, i, "")
|
||||
@@ -1414,7 +1464,7 @@ func (c *Compiler) parseExpr(frame *Frame, expr ssa.Value) (llvm.Value, error) {
|
||||
} else {
|
||||
buf := c.createEntryBlockAlloca(typ, expr.Comment)
|
||||
if c.targetData.TypeAllocSize(typ) != 0 {
|
||||
c.builder.CreateStore(c.getZeroValue(typ), buf) // zero-initialize var
|
||||
c.builder.CreateStore(llvm.ConstNull(typ), buf) // zero-initialize var
|
||||
}
|
||||
return buf, nil
|
||||
}
|
||||
@@ -1607,7 +1657,7 @@ func (c *Compiler) parseExpr(frame *Frame, expr ssa.Value) (llvm.Value, error) {
|
||||
panic("unknown lookup type: " + expr.String())
|
||||
}
|
||||
case *ssa.MakeChan:
|
||||
return c.emitMakeChan(expr)
|
||||
return c.emitMakeChan(frame, expr), nil
|
||||
case *ssa.MakeClosure:
|
||||
return c.parseMakeClosure(frame, expr)
|
||||
case *ssa.MakeInterface:
|
||||
@@ -1650,7 +1700,9 @@ func (c *Compiler) parseExpr(frame *Frame, expr ssa.Value) (llvm.Value, error) {
|
||||
}
|
||||
|
||||
// Bounds checking.
|
||||
c.emitSliceBoundsCheck(frame, maxSize, sliceLen, sliceCap, expr.Len.Type().(*types.Basic), expr.Cap.Type().(*types.Basic))
|
||||
lenType := expr.Len.Type().(*types.Basic)
|
||||
capType := expr.Cap.Type().(*types.Basic)
|
||||
c.emitSliceBoundsCheck(frame, maxSize, sliceLen, sliceCap, sliceCap, lenType, capType, capType)
|
||||
|
||||
// Allocate the backing array.
|
||||
sliceCapCast, err := c.parseConvert(expr.Cap.Type(), types.Typ[types.Uintptr], sliceCap, expr.Pos())
|
||||
@@ -1719,18 +1771,15 @@ func (c *Compiler) parseExpr(frame *Frame, expr ssa.Value) (llvm.Value, error) {
|
||||
panic("unknown type in range: " + typ.String())
|
||||
}
|
||||
it, _, _ := c.createTemporaryAlloca(iteratorType, "range.it")
|
||||
c.builder.CreateStore(c.getZeroValue(iteratorType), it)
|
||||
c.builder.CreateStore(llvm.ConstNull(iteratorType), it)
|
||||
return it, nil
|
||||
case *ssa.Select:
|
||||
return c.emitSelect(frame, expr), nil
|
||||
case *ssa.Slice:
|
||||
if expr.Max != nil {
|
||||
return llvm.Value{}, c.makeError(expr.Pos(), "todo: full slice expressions (with max): "+expr.Type().String())
|
||||
}
|
||||
value := c.getValue(frame, expr.X)
|
||||
|
||||
var lowType, highType *types.Basic
|
||||
var low, high llvm.Value
|
||||
var lowType, highType, maxType *types.Basic
|
||||
var low, high, max llvm.Value
|
||||
|
||||
if expr.Low != nil {
|
||||
lowType = expr.Low.Type().Underlying().(*types.Basic)
|
||||
@@ -1761,6 +1810,20 @@ func (c *Compiler) parseExpr(frame *Frame, expr ssa.Value) (llvm.Value, error) {
|
||||
highType = types.Typ[types.Uintptr]
|
||||
}
|
||||
|
||||
if expr.Max != nil {
|
||||
maxType = expr.Max.Type().Underlying().(*types.Basic)
|
||||
max = c.getValue(frame, expr.Max)
|
||||
if max.Type().IntTypeWidth() < c.uintptrType.IntTypeWidth() {
|
||||
if maxType.Info()&types.IsUnsigned != 0 {
|
||||
max = c.builder.CreateZExt(max, c.uintptrType, "")
|
||||
} else {
|
||||
max = c.builder.CreateSExt(max, c.uintptrType, "")
|
||||
}
|
||||
}
|
||||
} else {
|
||||
maxType = types.Typ[types.Uintptr]
|
||||
}
|
||||
|
||||
switch typ := expr.X.Type().Underlying().(type) {
|
||||
case *types.Pointer: // pointer to array
|
||||
// slice an array
|
||||
@@ -1769,25 +1832,31 @@ func (c *Compiler) parseExpr(frame *Frame, expr ssa.Value) (llvm.Value, error) {
|
||||
if high.IsNil() {
|
||||
high = llvmLen
|
||||
}
|
||||
if max.IsNil() {
|
||||
max = llvmLen
|
||||
}
|
||||
indices := []llvm.Value{
|
||||
llvm.ConstInt(c.ctx.Int32Type(), 0, false),
|
||||
low,
|
||||
}
|
||||
|
||||
c.emitSliceBoundsCheck(frame, llvmLen, low, high, lowType, highType)
|
||||
c.emitSliceBoundsCheck(frame, llvmLen, low, high, max, lowType, highType, maxType)
|
||||
|
||||
// Truncate ints bigger than uintptr. This is after the bounds
|
||||
// check so it's safe.
|
||||
if c.targetData.TypeAllocSize(low.Type()) > c.targetData.TypeAllocSize(c.uintptrType) {
|
||||
low = c.builder.CreateTrunc(low, c.uintptrType, "")
|
||||
}
|
||||
if c.targetData.TypeAllocSize(high.Type()) > c.targetData.TypeAllocSize(c.uintptrType) {
|
||||
high = c.builder.CreateTrunc(high, c.uintptrType, "")
|
||||
}
|
||||
if c.targetData.TypeAllocSize(low.Type()) > c.targetData.TypeAllocSize(c.uintptrType) {
|
||||
low = c.builder.CreateTrunc(low, c.uintptrType, "")
|
||||
if c.targetData.TypeAllocSize(max.Type()) > c.targetData.TypeAllocSize(c.uintptrType) {
|
||||
max = c.builder.CreateTrunc(max, c.uintptrType, "")
|
||||
}
|
||||
|
||||
sliceLen := c.builder.CreateSub(high, low, "slice.len")
|
||||
slicePtr := c.builder.CreateInBoundsGEP(value, indices, "slice.ptr")
|
||||
sliceCap := c.builder.CreateSub(llvmLen, low, "slice.cap")
|
||||
sliceCap := c.builder.CreateSub(max, low, "slice.cap")
|
||||
|
||||
slice := c.ctx.ConstStruct([]llvm.Value{
|
||||
llvm.Undef(slicePtr.Type()),
|
||||
@@ -1807,8 +1876,11 @@ func (c *Compiler) parseExpr(frame *Frame, expr ssa.Value) (llvm.Value, error) {
|
||||
if high.IsNil() {
|
||||
high = oldLen
|
||||
}
|
||||
if max.IsNil() {
|
||||
max = oldCap
|
||||
}
|
||||
|
||||
c.emitSliceBoundsCheck(frame, oldCap, low, high, lowType, highType)
|
||||
c.emitSliceBoundsCheck(frame, oldCap, low, high, max, lowType, highType, maxType)
|
||||
|
||||
// Truncate ints bigger than uintptr. This is after the bounds
|
||||
// check so it's safe.
|
||||
@@ -1818,10 +1890,13 @@ func (c *Compiler) parseExpr(frame *Frame, expr ssa.Value) (llvm.Value, error) {
|
||||
if c.targetData.TypeAllocSize(high.Type()) > c.targetData.TypeAllocSize(c.uintptrType) {
|
||||
high = c.builder.CreateTrunc(high, c.uintptrType, "")
|
||||
}
|
||||
if c.targetData.TypeAllocSize(max.Type()) > c.targetData.TypeAllocSize(c.uintptrType) {
|
||||
max = c.builder.CreateTrunc(max, c.uintptrType, "")
|
||||
}
|
||||
|
||||
newPtr := c.builder.CreateInBoundsGEP(oldPtr, []llvm.Value{low}, "")
|
||||
newLen := c.builder.CreateSub(high, low, "")
|
||||
newCap := c.builder.CreateSub(oldCap, low, "")
|
||||
newCap := c.builder.CreateSub(max, low, "")
|
||||
slice := c.ctx.ConstStruct([]llvm.Value{
|
||||
llvm.Undef(newPtr.Type()),
|
||||
llvm.Undef(c.uintptrType),
|
||||
@@ -1837,13 +1912,18 @@ func (c *Compiler) parseExpr(frame *Frame, expr ssa.Value) (llvm.Value, error) {
|
||||
return llvm.Value{}, c.makeError(expr.Pos(), "unknown slice type: "+typ.String())
|
||||
}
|
||||
// slice a string
|
||||
if expr.Max != nil {
|
||||
// This might as well be a panic, as the frontend should have
|
||||
// handled this already.
|
||||
return llvm.Value{}, c.makeError(expr.Pos(), "slicing a string with a max parameter is not allowed by the spec")
|
||||
}
|
||||
oldPtr := c.builder.CreateExtractValue(value, 0, "")
|
||||
oldLen := c.builder.CreateExtractValue(value, 1, "")
|
||||
if high.IsNil() {
|
||||
high = oldLen
|
||||
}
|
||||
|
||||
c.emitSliceBoundsCheck(frame, oldLen, low, high, lowType, highType)
|
||||
c.emitSliceBoundsCheck(frame, oldLen, low, high, high, lowType, highType, maxType)
|
||||
|
||||
// Truncate ints bigger than uintptr. This is after the bounds
|
||||
// check so it's safe.
|
||||
@@ -2273,12 +2353,12 @@ func (c *Compiler) parseConst(prefix string, expr *ssa.Const) llvm.Value {
|
||||
if expr.Value != nil {
|
||||
panic("expected nil chan constant")
|
||||
}
|
||||
return c.getZeroValue(c.getLLVMType(expr.Type()))
|
||||
return llvm.ConstNull(c.getLLVMType(expr.Type()))
|
||||
case *types.Signature:
|
||||
if expr.Value != nil {
|
||||
panic("expected nil signature constant")
|
||||
}
|
||||
return c.getZeroValue(c.getLLVMType(expr.Type()))
|
||||
return llvm.ConstNull(c.getLLVMType(expr.Type()))
|
||||
case *types.Interface:
|
||||
if expr.Value != nil {
|
||||
panic("expected nil interface constant")
|
||||
@@ -2313,7 +2393,7 @@ func (c *Compiler) parseConst(prefix string, expr *ssa.Const) llvm.Value {
|
||||
panic("non-nil map constant")
|
||||
}
|
||||
llvmType := c.getLLVMType(typ)
|
||||
return c.getZeroValue(llvmType)
|
||||
return llvm.ConstNull(llvmType)
|
||||
default:
|
||||
panic("unknown constant: " + expr.String())
|
||||
}
|
||||
@@ -2385,6 +2465,8 @@ func (c *Compiler) parseConvert(typeFrom, typeTo types.Type, value llvm.Value, p
|
||||
switch typeFrom.Elem().(*types.Basic).Kind() {
|
||||
case types.Byte:
|
||||
return c.createRuntimeCall("stringFromBytes", []llvm.Value{value}, ""), nil
|
||||
case types.Rune:
|
||||
return c.createRuntimeCall("stringFromRunes", []llvm.Value{value}, ""), nil
|
||||
default:
|
||||
return llvm.Value{}, c.makeError(pos, "todo: convert to string: "+typeFrom.String())
|
||||
}
|
||||
@@ -2503,7 +2585,7 @@ func (c *Compiler) parseUnOp(frame *Frame, unop *ssa.UnOp) (llvm.Value, error) {
|
||||
unop.X.Type().Underlying().(*types.Pointer).Elem()
|
||||
if c.targetData.TypeAllocSize(x.Type().ElementType()) == 0 {
|
||||
// zero-length data
|
||||
return c.getZeroValue(x.Type().ElementType()), nil
|
||||
return llvm.ConstNull(x.Type().ElementType()), nil
|
||||
} else if strings.HasSuffix(unop.X.String(), "$funcaddr") {
|
||||
// CGo function pointer. The cgo part has rewritten CGo function
|
||||
// pointers as stub global variables of the form:
|
||||
@@ -2670,7 +2752,7 @@ func (c *Compiler) ExternalInt64AsPtr() error {
|
||||
// correct calling convention.
|
||||
fn.SetLinkage(llvm.InternalLinkage)
|
||||
fn.SetUnnamedAddr(true)
|
||||
entryBlock := llvm.AddBasicBlock(externalFn, "entry")
|
||||
entryBlock := c.ctx.AddBasicBlock(externalFn, "entry")
|
||||
c.builder.SetInsertPointAtEnd(entryBlock)
|
||||
var callParams []llvm.Value
|
||||
if fnType.ReturnType() == int64Type {
|
||||
|
||||
+6
-6
@@ -122,7 +122,7 @@ func (c *Compiler) emitDefer(frame *Frame, instr *ssa.Defer) {
|
||||
|
||||
// Make a struct out of the collected values to put in the defer frame.
|
||||
deferFrameType := c.ctx.StructType(valueTypes, false)
|
||||
deferFrame := c.getZeroValue(deferFrameType)
|
||||
deferFrame := llvm.ConstNull(deferFrameType)
|
||||
for i, value := range values {
|
||||
deferFrame = c.builder.CreateInsertValue(deferFrame, value, i, "")
|
||||
}
|
||||
@@ -157,10 +157,10 @@ func (c *Compiler) emitRunDefers(frame *Frame) {
|
||||
// }
|
||||
|
||||
// Create loop.
|
||||
loophead := llvm.AddBasicBlock(frame.fn.LLVMFn, "rundefers.loophead")
|
||||
loop := llvm.AddBasicBlock(frame.fn.LLVMFn, "rundefers.loop")
|
||||
unreachable := llvm.AddBasicBlock(frame.fn.LLVMFn, "rundefers.default")
|
||||
end := llvm.AddBasicBlock(frame.fn.LLVMFn, "rundefers.end")
|
||||
loophead := c.ctx.AddBasicBlock(frame.fn.LLVMFn, "rundefers.loophead")
|
||||
loop := c.ctx.AddBasicBlock(frame.fn.LLVMFn, "rundefers.loop")
|
||||
unreachable := c.ctx.AddBasicBlock(frame.fn.LLVMFn, "rundefers.default")
|
||||
end := c.ctx.AddBasicBlock(frame.fn.LLVMFn, "rundefers.end")
|
||||
c.builder.CreateBr(loophead)
|
||||
|
||||
// Create loop head:
|
||||
@@ -192,7 +192,7 @@ func (c *Compiler) emitRunDefers(frame *Frame) {
|
||||
// Create switch case, for example:
|
||||
// case 0:
|
||||
// // run first deferred call
|
||||
block := llvm.AddBasicBlock(frame.fn.LLVMFn, "rundefers.callback")
|
||||
block := c.ctx.AddBasicBlock(frame.fn.LLVMFn, "rundefers.callback")
|
||||
sw.AddCase(llvm.ConstInt(c.uintptrType, uint64(i), false), block)
|
||||
c.builder.SetInsertPointAtEnd(block)
|
||||
switch callback := callback.(type) {
|
||||
|
||||
+94
-78
@@ -152,11 +152,11 @@ func (c *Compiler) LowerFuncValues() {
|
||||
// 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 {
|
||||
// rawPtr := runtime.getFuncPtr(func.ptr)
|
||||
// if rawPtr == nil {
|
||||
// runtime.nilPanic()
|
||||
// }
|
||||
// result := func.rawPtr(...args, func.context)
|
||||
// result := rawPtr(...args, func.context)
|
||||
// into this:
|
||||
// if false {
|
||||
// runtime.nilPanic()
|
||||
@@ -175,95 +175,111 @@ func (c *Compiler) LowerFuncValues() {
|
||||
|
||||
// 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() {
|
||||
for _, callIntPtr := range getUses(getFuncPtrCall) {
|
||||
if !callIntPtr.IsACallInst().IsNil() && callIntPtr.CalledValue().Name() == "runtime.makeGoroutine" {
|
||||
for _, inttoptr := range getUses(callIntPtr) {
|
||||
if inttoptr.IsAIntToPtrInst().IsNil() {
|
||||
panic("expected a inttoptr")
|
||||
}
|
||||
for _, use := range getUses(inttoptr) {
|
||||
c.addFuncLoweringSwitch(funcID, use, c.emitStartGoroutine, functions)
|
||||
use.EraseFromParentAsInstruction()
|
||||
}
|
||||
inttoptr.EraseFromParentAsInstruction()
|
||||
}
|
||||
callIntPtr.EraseFromParentAsInstruction()
|
||||
continue
|
||||
}
|
||||
if callIntPtr.IsAIntToPtrInst().IsNil() {
|
||||
panic("expected inttoptr")
|
||||
}
|
||||
for _, ptrUse := range getUses(inttoptr) {
|
||||
for _, ptrUse := range getUses(callIntPtr) {
|
||||
if !ptrUse.IsABitCastInst().IsNil() {
|
||||
for _, bitcastUse := range getUses(ptrUse) {
|
||||
if bitcastUse.IsACallInst().IsNil() || bitcastUse.CalledValue().Name() != "runtime.isnil" {
|
||||
if bitcastUse.IsACallInst().IsNil() || bitcastUse.CalledValue().IsAFunction().IsNil() {
|
||||
panic("expected a call instruction")
|
||||
}
|
||||
switch bitcastUse.CalledValue().Name() {
|
||||
case "runtime.isnil":
|
||||
bitcastUse.ReplaceAllUsesWith(llvm.ConstInt(c.ctx.Int1Type(), 0, false))
|
||||
bitcastUse.EraseFromParentAsInstruction()
|
||||
default:
|
||||
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 if !ptrUse.IsACallInst().IsNil() && ptrUse.CalledValue() == callIntPtr {
|
||||
c.addFuncLoweringSwitch(funcID, ptrUse, func(funcPtr llvm.Value, params []llvm.Value) llvm.Value {
|
||||
return c.builder.CreateCall(funcPtr, params, "")
|
||||
}, functions)
|
||||
} else {
|
||||
panic("unexpected getFuncPtrCall")
|
||||
}
|
||||
ptrUse.EraseFromParentAsInstruction()
|
||||
}
|
||||
}
|
||||
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()
|
||||
callIntPtr.EraseFromParentAsInstruction()
|
||||
}
|
||||
getFuncPtrCall.EraseFromParentAsInstruction()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// addFuncLoweringSwitch creates a new switch on a function ID and inserts calls
|
||||
// to the newly created direct calls. The funcID is the number to switch on,
|
||||
// call is the call instruction to replace, and createCall is the callback that
|
||||
// actually creates the new call. By changing createCall to something other than
|
||||
// c.builder.CreateCall, instead of calling a function it can start a new
|
||||
// goroutine for example.
|
||||
func (c *Compiler) addFuncLoweringSwitch(funcID, call llvm.Value, createCall func(funcPtr llvm.Value, params []llvm.Value) llvm.Value, functions funcWithUsesList) {
|
||||
// The block that cannot be reached with correct funcValues (to help the
|
||||
// optimizer).
|
||||
c.builder.SetInsertPointBefore(call)
|
||||
defaultBlock := c.ctx.AddBasicBlock(call.InstructionParent().Parent(), "func.default")
|
||||
c.builder.SetInsertPointAtEnd(defaultBlock)
|
||||
c.builder.CreateUnreachable()
|
||||
|
||||
// Create the switch.
|
||||
c.builder.SetInsertPointBefore(call)
|
||||
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 := c.ctx.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, call.OperandsCount()-1)
|
||||
for i := range callParams {
|
||||
callParams[i] = call.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 := c.ctx.InsertBasicBlock(nextBlock, "func.call"+strconv.Itoa(fn.id))
|
||||
c.builder.SetInsertPointAtEnd(bb)
|
||||
result := 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 call.Type().TypeKind() != llvm.VoidTypeKind {
|
||||
c.builder.SetInsertPointBefore(nextBlock.FirstInstruction())
|
||||
phi := c.builder.CreatePHI(call.Type(), "")
|
||||
phi.AddIncoming(phiValues, phiBlocks)
|
||||
call.ReplaceAllUsesWith(phi)
|
||||
}
|
||||
}
|
||||
|
||||
+9
-20
@@ -32,10 +32,15 @@ const (
|
||||
// funcImplementation picks an appropriate func value implementation for the
|
||||
// target.
|
||||
func (c *Compiler) funcImplementation() funcValueImplementation {
|
||||
if c.GOARCH == "wasm" {
|
||||
// Always pick the switch implementation, as it allows the use of blocking
|
||||
// inside a function that is used as a func value.
|
||||
switch c.selectScheduler() {
|
||||
case "coroutines":
|
||||
return funcValueSwitch
|
||||
} else {
|
||||
case "tasks":
|
||||
return funcValueDoubleword
|
||||
default:
|
||||
panic("unknown scheduler type")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -48,7 +53,7 @@ func (c *Compiler) createFuncValue(funcPtr, context llvm.Value, sig *types.Signa
|
||||
// Closure is: {context, function pointer}
|
||||
funcValueScalar = funcPtr
|
||||
case funcValueSwitch:
|
||||
sigGlobal := c.getFuncSignature(sig)
|
||||
sigGlobal := c.getTypeCode(sig)
|
||||
funcValueWithSignatureGlobalName := funcPtr.Name() + "$withSignature"
|
||||
funcValueWithSignatureGlobal := c.mod.NamedGlobal(funcValueWithSignatureGlobalName)
|
||||
if funcValueWithSignatureGlobal.IsNil() {
|
||||
@@ -73,22 +78,6 @@ func (c *Compiler) createFuncValue(funcPtr, context llvm.Value, sig *types.Signa
|
||||
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 {
|
||||
@@ -110,7 +99,7 @@ func (c *Compiler) decodeFuncValue(funcValue llvm.Value, sig *types.Signature) (
|
||||
funcPtr = c.builder.CreateExtractValue(funcValue, 1, "")
|
||||
case funcValueSwitch:
|
||||
llvmSig := c.getRawFuncType(sig)
|
||||
sigGlobal := c.getFuncSignature(sig)
|
||||
sigGlobal := c.getTypeCode(sig)
|
||||
funcPtr = c.createRuntimeCall("getFuncPtr", []llvm.Value{funcValue, sigGlobal}, "")
|
||||
funcPtr = c.builder.CreateIntToPtr(funcPtr, llvmSig, "")
|
||||
default:
|
||||
|
||||
+6
-6
@@ -18,7 +18,7 @@ func (c *Compiler) needsStackObjects() bool {
|
||||
return false
|
||||
}
|
||||
for _, tag := range c.BuildTags {
|
||||
if tag == "cortexm" || tag == "tinygo.riscv" {
|
||||
if tag == "baremetal" {
|
||||
return false
|
||||
}
|
||||
}
|
||||
@@ -134,7 +134,7 @@ func (c *Compiler) makeGCStackSlots() bool {
|
||||
}
|
||||
stackChainStart := c.mod.NamedGlobal("runtime.stackChainStart")
|
||||
if !stackChainStart.IsNil() {
|
||||
stackChainStart.SetInitializer(c.getZeroValue(stackChainStart.Type().ElementType()))
|
||||
stackChainStart.SetInitializer(llvm.ConstNull(stackChainStart.Type().ElementType()))
|
||||
stackChainStart.SetGlobalConstant(true)
|
||||
}
|
||||
}
|
||||
@@ -198,7 +198,7 @@ func (c *Compiler) makeGCStackSlots() bool {
|
||||
panic("stack chain start not found!")
|
||||
}
|
||||
stackChainStartType := stackChainStart.Type().ElementType()
|
||||
stackChainStart.SetInitializer(c.getZeroValue(stackChainStartType))
|
||||
stackChainStart.SetInitializer(llvm.ConstNull(stackChainStartType))
|
||||
|
||||
// Iterate until runtime.trackPointer has no uses left.
|
||||
for use := trackPointer.FirstUse(); !use.IsNil(); use = trackPointer.FirstUse() {
|
||||
@@ -303,7 +303,7 @@ func (c *Compiler) makeGCStackSlots() bool {
|
||||
// Create the stack object at the function entry.
|
||||
c.builder.SetInsertPointBefore(fn.EntryBasicBlock().FirstInstruction())
|
||||
stackObject := c.builder.CreateAlloca(stackObjectType, "gc.stackobject")
|
||||
initialStackObject := c.getZeroValue(stackObjectType)
|
||||
initialStackObject := llvm.ConstNull(stackObjectType)
|
||||
numSlots := (c.targetData.TypeAllocSize(stackObjectType) - c.targetData.TypeAllocSize(c.i8ptrType)*2) / uint64(c.targetData.ABITypeAlignment(c.uintptrType))
|
||||
numSlotsValue := llvm.ConstInt(c.uintptrType, numSlots, false)
|
||||
initialStackObject = llvm.ConstInsertValue(initialStackObject, numSlotsValue, []uint32{1})
|
||||
@@ -398,10 +398,10 @@ func (c *Compiler) addGlobalsBitmap() bool {
|
||||
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)
|
||||
bitmapArray := llvm.ConstArray(c.ctx.Int8Type(), bitmapValues)
|
||||
bitmapNew := llvm.AddGlobal(c.mod, bitmapArray.Type(), "runtime.trackedGlobalsBitmap.tmp")
|
||||
bitmapOld := c.mod.NamedGlobal("runtime.trackedGlobalsBitmap")
|
||||
bitmapOld.ReplaceAllUsesWith(bitmapNew)
|
||||
bitmapOld.ReplaceAllUsesWith(llvm.ConstBitCast(bitmapNew, bitmapOld.Type()))
|
||||
bitmapNew.SetInitializer(bitmapArray)
|
||||
bitmapNew.SetName("runtime.trackedGlobalsBitmap")
|
||||
|
||||
|
||||
+592
-258
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,135 @@
|
||||
package compiler
|
||||
|
||||
// This file implements the 'go' keyword to start a new goroutine. See
|
||||
// goroutine-lowering.go for more details.
|
||||
|
||||
import "tinygo.org/x/go-llvm"
|
||||
|
||||
// emitStartGoroutine starts a new goroutine with the provided function pointer
|
||||
// and parameters.
|
||||
//
|
||||
// Because a go statement doesn't return anything, return undef.
|
||||
func (c *Compiler) emitStartGoroutine(funcPtr llvm.Value, params []llvm.Value) llvm.Value {
|
||||
switch c.selectScheduler() {
|
||||
case "tasks":
|
||||
paramBundle := c.emitPointerPack(params)
|
||||
paramBundle = c.builder.CreatePtrToInt(paramBundle, c.uintptrType, "")
|
||||
|
||||
calleeValue := c.createGoroutineStartWrapper(funcPtr)
|
||||
c.createRuntimeCall("startGoroutine", []llvm.Value{calleeValue, paramBundle}, "")
|
||||
case "coroutines":
|
||||
// We roundtrip through runtime.makeGoroutine as a signal (to find these
|
||||
// calls) and to break any optimizations LLVM will try to do: they are
|
||||
// invalid if we called this as a regular function to be updated later.
|
||||
calleeValue := c.builder.CreatePtrToInt(funcPtr, c.uintptrType, "")
|
||||
calleeValue = c.createRuntimeCall("makeGoroutine", []llvm.Value{calleeValue}, "")
|
||||
calleeValue = c.builder.CreateIntToPtr(calleeValue, funcPtr.Type(), "")
|
||||
c.createCall(calleeValue, params, "")
|
||||
default:
|
||||
panic("unreachable")
|
||||
}
|
||||
return llvm.Undef(funcPtr.Type().ElementType().ReturnType())
|
||||
}
|
||||
|
||||
// createGoroutineStartWrapper creates a wrapper for the task-based
|
||||
// implementation of goroutines. For example, to call a function like this:
|
||||
//
|
||||
// func add(x, y int) int { ... }
|
||||
//
|
||||
// It creates a wrapper like this:
|
||||
//
|
||||
// func add$gowrapper(ptr *unsafe.Pointer) {
|
||||
// args := (*struct{
|
||||
// x, y int
|
||||
// })(ptr)
|
||||
// add(args.x, args.y)
|
||||
// }
|
||||
//
|
||||
// This is useful because the task-based goroutine start implementation only
|
||||
// allows a single (pointer) argument to the newly started goroutine. Also, it
|
||||
// ignores the return value because newly started goroutines do not have a
|
||||
// return value.
|
||||
func (c *Compiler) createGoroutineStartWrapper(fn llvm.Value) llvm.Value {
|
||||
var wrapper llvm.Value
|
||||
|
||||
if !fn.IsAFunction().IsNil() {
|
||||
// See whether this wrapper has already been created. If so, return it.
|
||||
name := fn.Name()
|
||||
wrapper = c.mod.NamedFunction(name + "$gowrapper")
|
||||
if !wrapper.IsNil() {
|
||||
return c.builder.CreatePtrToInt(wrapper, c.uintptrType, "")
|
||||
}
|
||||
|
||||
// Save the current position in the IR builder.
|
||||
currentBlock := c.builder.GetInsertBlock()
|
||||
defer c.builder.SetInsertPointAtEnd(currentBlock)
|
||||
|
||||
// Create the wrapper.
|
||||
wrapperType := llvm.FunctionType(c.ctx.VoidType(), []llvm.Type{c.i8ptrType}, false)
|
||||
wrapper = llvm.AddFunction(c.mod, name+"$gowrapper", wrapperType)
|
||||
wrapper.SetLinkage(llvm.PrivateLinkage)
|
||||
wrapper.SetUnnamedAddr(true)
|
||||
entry := c.ctx.AddBasicBlock(wrapper, "entry")
|
||||
c.builder.SetInsertPointAtEnd(entry)
|
||||
|
||||
// Create the list of params for the call.
|
||||
paramTypes := fn.Type().ElementType().ParamTypes()
|
||||
params := c.emitPointerUnpack(wrapper.Param(0), paramTypes[:len(paramTypes)-2])
|
||||
params = append(params, llvm.Undef(c.i8ptrType), llvm.ConstPointerNull(c.i8ptrType))
|
||||
|
||||
// Create the call.
|
||||
c.builder.CreateCall(fn, params, "")
|
||||
|
||||
} else {
|
||||
// For a function pointer like this:
|
||||
//
|
||||
// var funcPtr func(x, y int) int
|
||||
//
|
||||
// A wrapper like the following is created:
|
||||
//
|
||||
// func .gowrapper(ptr *unsafe.Pointer) {
|
||||
// args := (*struct{
|
||||
// x, y int
|
||||
// fn func(x, y int) int
|
||||
// })(ptr)
|
||||
// args.fn(x, y)
|
||||
// }
|
||||
//
|
||||
// With a bit of luck, identical wrapper functions like these can be
|
||||
// merged into one.
|
||||
|
||||
// Save the current position in the IR builder.
|
||||
currentBlock := c.builder.GetInsertBlock()
|
||||
defer c.builder.SetInsertPointAtEnd(currentBlock)
|
||||
|
||||
// Create the wrapper.
|
||||
wrapperType := llvm.FunctionType(c.ctx.VoidType(), []llvm.Type{c.i8ptrType}, false)
|
||||
wrapper = llvm.AddFunction(c.mod, ".gowrapper", wrapperType)
|
||||
wrapper.SetLinkage(llvm.InternalLinkage)
|
||||
wrapper.SetUnnamedAddr(true)
|
||||
entry := c.ctx.AddBasicBlock(wrapper, "entry")
|
||||
c.builder.SetInsertPointAtEnd(entry)
|
||||
|
||||
// Get the list of parameters, with the extra parameters at the end.
|
||||
paramTypes := fn.Type().ElementType().ParamTypes()
|
||||
paramTypes[len(paramTypes)-1] = fn.Type() // the last element is the function pointer
|
||||
params := c.emitPointerUnpack(wrapper.Param(0), paramTypes)
|
||||
|
||||
// Get the function pointer.
|
||||
fnPtr := params[len(params)-1]
|
||||
|
||||
// Ignore the last param, which isn't used anymore.
|
||||
// TODO: avoid this extra "parent handle" parameter in most functions.
|
||||
params[len(params)-1] = llvm.Undef(c.i8ptrType)
|
||||
|
||||
// Create the call.
|
||||
c.builder.CreateCall(fnPtr, params, "")
|
||||
}
|
||||
|
||||
// Finish the function. Every basic block must end in a terminator, and
|
||||
// because goroutines never return a value we can simply return void.
|
||||
c.builder.CreateRetVoid()
|
||||
|
||||
// Return a ptrtoint of the wrapper, not the function itself.
|
||||
return c.builder.CreatePtrToInt(wrapper, c.uintptrType, "")
|
||||
}
|
||||
@@ -603,9 +603,9 @@ func (p *lowerInterfacesPass) createInterfaceImplementsFunc(itf *interfaceInfo)
|
||||
// TODO: debug info
|
||||
|
||||
// Create all used basic blocks.
|
||||
entry := llvm.AddBasicBlock(fn, "entry")
|
||||
thenBlock := llvm.AddBasicBlock(fn, "then")
|
||||
elseBlock := llvm.AddBasicBlock(fn, "else")
|
||||
entry := p.ctx.AddBasicBlock(fn, "entry")
|
||||
thenBlock := p.ctx.AddBasicBlock(fn, "then")
|
||||
elseBlock := p.ctx.AddBasicBlock(fn, "else")
|
||||
|
||||
// Add all possible types as cases.
|
||||
p.builder.SetInsertPointAtEnd(entry)
|
||||
@@ -661,11 +661,11 @@ func (p *lowerInterfacesPass) createInterfaceMethodFunc(itf *interfaceInfo, sign
|
||||
// TODO: debug info
|
||||
|
||||
// Create entry block.
|
||||
entry := llvm.AddBasicBlock(fn, "entry")
|
||||
entry := p.ctx.AddBasicBlock(fn, "entry")
|
||||
|
||||
// Create default block and make it unreachable (which it is, because all
|
||||
// possible types are checked).
|
||||
defaultBlock := llvm.AddBasicBlock(fn, "default")
|
||||
defaultBlock := p.ctx.AddBasicBlock(fn, "default")
|
||||
p.builder.SetInsertPointAtEnd(defaultBlock)
|
||||
p.builder.CreateUnreachable()
|
||||
|
||||
@@ -684,7 +684,7 @@ func (p *lowerInterfacesPass) createInterfaceMethodFunc(itf *interfaceInfo, sign
|
||||
|
||||
// Define all possible functions that can be called.
|
||||
for _, typ := range itf.types {
|
||||
bb := llvm.AddBasicBlock(fn, typ.name)
|
||||
bb := p.ctx.AddBasicBlock(fn, typ.name)
|
||||
sw.AddCase(llvm.ConstInt(p.uintptrType, typ.num, false), bb)
|
||||
|
||||
// The function we will redirect to when the interface has this type.
|
||||
|
||||
+97
-17
@@ -45,27 +45,100 @@ func (c *Compiler) parseMakeInterface(val llvm.Value, typ types.Type, pos token.
|
||||
// It returns a pointer to an external global which should be replaced with the
|
||||
// real type in the interface lowering pass.
|
||||
func (c *Compiler) getTypeCode(typ types.Type) llvm.Value {
|
||||
globalName := "type:" + getTypeCodeName(typ)
|
||||
globalName := "reflect/types.type:" + getTypeCodeName(typ)
|
||||
global := c.mod.NamedGlobal(globalName)
|
||||
if global.IsNil() {
|
||||
// Create a new typecode global.
|
||||
global = llvm.AddGlobal(c.mod, c.getLLVMRuntimeType("typecodeID"), globalName)
|
||||
// Some type classes contain more information for underlying types or
|
||||
// element types. Store it directly in the typecode global to make
|
||||
// reflect lowering simpler.
|
||||
var references llvm.Value
|
||||
var length int64
|
||||
switch typ := typ.(type) {
|
||||
case *types.Named:
|
||||
references = c.getTypeCode(typ.Underlying())
|
||||
case *types.Chan:
|
||||
references = c.getTypeCode(typ.Elem())
|
||||
case *types.Pointer:
|
||||
references = c.getTypeCode(typ.Elem())
|
||||
case *types.Slice:
|
||||
references = c.getTypeCode(typ.Elem())
|
||||
case *types.Array:
|
||||
references = c.getTypeCode(typ.Elem())
|
||||
length = typ.Len()
|
||||
case *types.Struct:
|
||||
// Take a pointer to the typecodeID of the first field (if it exists).
|
||||
structGlobal := c.makeStructTypeFields(typ)
|
||||
references = llvm.ConstBitCast(structGlobal, global.Type())
|
||||
}
|
||||
if !references.IsNil() {
|
||||
// Set the 'references' field of the runtime.typecodeID struct.
|
||||
globalValue := llvm.ConstNull(global.Type().ElementType())
|
||||
globalValue = llvm.ConstInsertValue(globalValue, references, []uint32{0})
|
||||
if length != 0 {
|
||||
lengthValue := llvm.ConstInt(c.uintptrType, uint64(length), false)
|
||||
globalValue = llvm.ConstInsertValue(globalValue, lengthValue, []uint32{1})
|
||||
}
|
||||
global.SetInitializer(globalValue)
|
||||
global.SetLinkage(llvm.PrivateLinkage)
|
||||
}
|
||||
global.SetGlobalConstant(true)
|
||||
}
|
||||
return global
|
||||
}
|
||||
|
||||
// makeStructTypeFields creates a new global that stores all type information
|
||||
// related to this struct type, and returns the resulting global. This global is
|
||||
// actually an array of all the fields in the structs.
|
||||
func (c *Compiler) makeStructTypeFields(typ *types.Struct) llvm.Value {
|
||||
// The global is an array of runtime.structField structs.
|
||||
runtimeStructField := c.getLLVMRuntimeType("structField")
|
||||
structGlobalType := llvm.ArrayType(runtimeStructField, typ.NumFields())
|
||||
structGlobal := llvm.AddGlobal(c.mod, structGlobalType, "reflect/types.structFields")
|
||||
structGlobalValue := llvm.ConstNull(structGlobalType)
|
||||
for i := 0; i < typ.NumFields(); i++ {
|
||||
fieldGlobalValue := llvm.ConstNull(runtimeStructField)
|
||||
fieldGlobalValue = llvm.ConstInsertValue(fieldGlobalValue, c.getTypeCode(typ.Field(i).Type()), []uint32{0})
|
||||
fieldName := c.makeGlobalArray([]byte(typ.Field(i).Name()), "reflect/types.structFieldName", c.ctx.Int8Type())
|
||||
fieldName.SetLinkage(llvm.PrivateLinkage)
|
||||
fieldName.SetUnnamedAddr(true)
|
||||
fieldName = llvm.ConstGEP(fieldName, []llvm.Value{
|
||||
llvm.ConstInt(llvm.Int32Type(), 0, false),
|
||||
llvm.ConstInt(llvm.Int32Type(), 0, false),
|
||||
})
|
||||
fieldGlobalValue = llvm.ConstInsertValue(fieldGlobalValue, fieldName, []uint32{1})
|
||||
if typ.Tag(i) != "" {
|
||||
fieldTag := c.makeGlobalArray([]byte(typ.Tag(i)), "reflect/types.structFieldTag", c.ctx.Int8Type())
|
||||
fieldTag.SetLinkage(llvm.PrivateLinkage)
|
||||
fieldTag.SetUnnamedAddr(true)
|
||||
fieldTag = llvm.ConstGEP(fieldTag, []llvm.Value{
|
||||
llvm.ConstInt(llvm.Int32Type(), 0, false),
|
||||
llvm.ConstInt(llvm.Int32Type(), 0, false),
|
||||
})
|
||||
fieldGlobalValue = llvm.ConstInsertValue(fieldGlobalValue, fieldTag, []uint32{2})
|
||||
}
|
||||
if typ.Field(i).Embedded() {
|
||||
fieldEmbedded := llvm.ConstInt(c.ctx.Int1Type(), 1, false)
|
||||
fieldGlobalValue = llvm.ConstInsertValue(fieldGlobalValue, fieldEmbedded, []uint32{3})
|
||||
}
|
||||
structGlobalValue = llvm.ConstInsertValue(structGlobalValue, fieldGlobalValue, []uint32{uint32(i)})
|
||||
}
|
||||
structGlobal.SetInitializer(structGlobalValue)
|
||||
structGlobal.SetUnnamedAddr(true)
|
||||
structGlobal.SetLinkage(llvm.PrivateLinkage)
|
||||
return structGlobal
|
||||
}
|
||||
|
||||
// getTypeCodeName returns a name for this type that can be used in the
|
||||
// interface lowering pass to assign type codes as expected by the reflect
|
||||
// package. See getTypeCodeNum.
|
||||
func getTypeCodeName(t types.Type) string {
|
||||
name := ""
|
||||
if named, ok := t.(*types.Named); ok {
|
||||
name = "~" + named.String() + ":"
|
||||
t = t.Underlying()
|
||||
}
|
||||
switch t := t.(type) {
|
||||
case *types.Named:
|
||||
return "named:" + t.String()
|
||||
case *types.Array:
|
||||
return "array:" + name + strconv.FormatInt(t.Len(), 10) + ":" + getTypeCodeName(t.Elem())
|
||||
return "array:" + strconv.FormatInt(t.Len(), 10) + ":" + getTypeCodeName(t.Elem())
|
||||
case *types.Basic:
|
||||
var kind string
|
||||
switch t.Kind() {
|
||||
@@ -108,21 +181,21 @@ func getTypeCodeName(t types.Type) string {
|
||||
default:
|
||||
panic("unknown basic type: " + t.Name())
|
||||
}
|
||||
return "basic:" + name + kind
|
||||
return "basic:" + kind
|
||||
case *types.Chan:
|
||||
return "chan:" + name + getTypeCodeName(t.Elem())
|
||||
return "chan:" + getTypeCodeName(t.Elem())
|
||||
case *types.Interface:
|
||||
methods := make([]string, t.NumMethods())
|
||||
for i := 0; i < t.NumMethods(); i++ {
|
||||
methods[i] = getTypeCodeName(t.Method(i).Type())
|
||||
}
|
||||
return "interface:" + name + "{" + strings.Join(methods, ",") + "}"
|
||||
return "interface:" + "{" + strings.Join(methods, ",") + "}"
|
||||
case *types.Map:
|
||||
keyType := getTypeCodeName(t.Key())
|
||||
elemType := getTypeCodeName(t.Elem())
|
||||
return "map:" + name + "{" + keyType + "," + elemType + "}"
|
||||
return "map:" + "{" + keyType + "," + elemType + "}"
|
||||
case *types.Pointer:
|
||||
return "pointer:" + name + getTypeCodeName(t.Elem())
|
||||
return "pointer:" + getTypeCodeName(t.Elem())
|
||||
case *types.Signature:
|
||||
params := make([]string, t.Params().Len())
|
||||
for i := 0; i < t.Params().Len(); i++ {
|
||||
@@ -132,9 +205,9 @@ func getTypeCodeName(t types.Type) string {
|
||||
for i := 0; i < t.Results().Len(); i++ {
|
||||
results[i] = getTypeCodeName(t.Results().At(i).Type())
|
||||
}
|
||||
return "func:" + name + "{" + strings.Join(params, ",") + "}{" + strings.Join(results, ",") + "}"
|
||||
return "func:" + "{" + strings.Join(params, ",") + "}{" + strings.Join(results, ",") + "}"
|
||||
case *types.Slice:
|
||||
return "slice:" + name + getTypeCodeName(t.Elem())
|
||||
return "slice:" + getTypeCodeName(t.Elem())
|
||||
case *types.Struct:
|
||||
elems := make([]string, t.NumFields())
|
||||
if t.NumFields() > 2 && t.Field(0).Name() == "C union" {
|
||||
@@ -142,9 +215,16 @@ func getTypeCodeName(t types.Type) string {
|
||||
panic("cgo unions are not allowed in interfaces")
|
||||
}
|
||||
for i := 0; i < t.NumFields(); i++ {
|
||||
elems[i] = getTypeCodeName(t.Field(i).Type())
|
||||
embedded := ""
|
||||
if t.Field(i).Embedded() {
|
||||
embedded = "#"
|
||||
}
|
||||
elems[i] = embedded + t.Field(i).Name() + ":" + getTypeCodeName(t.Field(i).Type())
|
||||
if t.Tag(i) != "" {
|
||||
elems[i] += "`" + t.Tag(i) + "`"
|
||||
}
|
||||
}
|
||||
return "struct:" + name + "{" + strings.Join(elems, ",") + "}"
|
||||
return "struct:" + "{" + strings.Join(elems, ",") + "}"
|
||||
default:
|
||||
panic("unknown type: " + t.String())
|
||||
}
|
||||
@@ -300,7 +380,7 @@ func (c *Compiler) parseTypeAssert(frame *Frame, expr *ssa.TypeAssert) llvm.Valu
|
||||
// Continue after the if statement.
|
||||
c.builder.SetInsertPointAtEnd(nextBlock)
|
||||
phi := c.builder.CreatePHI(assertedType, "typeassert.value")
|
||||
phi.AddIncoming([]llvm.Value{c.getZeroValue(assertedType), valueOk}, []llvm.BasicBlock{prevBlock, okBlock})
|
||||
phi.AddIncoming([]llvm.Value{llvm.ConstNull(assertedType), valueOk}, []llvm.BasicBlock{prevBlock, okBlock})
|
||||
|
||||
if expr.CommaOk {
|
||||
tuple := c.ctx.ConstStruct([]llvm.Value{llvm.Undef(assertedType), llvm.Undef(c.ctx.Int1Type())}, false) // create empty tuple
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
package compiler
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
|
||||
"tinygo.org/x/go-llvm"
|
||||
)
|
||||
|
||||
@@ -52,6 +54,22 @@ func (c *Compiler) createTemporaryAlloca(t llvm.Type, name string) (alloca, bitc
|
||||
return
|
||||
}
|
||||
|
||||
// createInstructionAlloca creates an alloca in the entry block, and places lifetime control intrinsics around the instruction
|
||||
func (c *Compiler) createInstructionAlloca(t llvm.Type, inst llvm.Value, name string) llvm.Value {
|
||||
alloca := c.createEntryBlockAlloca(t, name)
|
||||
c.builder.SetInsertPointBefore(inst)
|
||||
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}, "")
|
||||
if next := llvm.NextInstruction(inst); !next.IsNil() {
|
||||
c.builder.SetInsertPointBefore(next)
|
||||
} else {
|
||||
c.builder.SetInsertPointAtEnd(inst.InstructionParent())
|
||||
}
|
||||
c.builder.CreateCall(c.getLifetimeEndFunc(), []llvm.Value{size, bitcast}, "")
|
||||
return alloca
|
||||
}
|
||||
|
||||
// emitLifetimeEnd signals the end of an (alloca) lifetime by calling the
|
||||
// llvm.lifetime.end intrinsic. It is commonly used together with
|
||||
// createTemporaryAlloca.
|
||||
@@ -152,3 +170,48 @@ func (c *Compiler) splitBasicBlock(afterInst llvm.Value, insertAfter llvm.BasicB
|
||||
|
||||
return newBlock
|
||||
}
|
||||
|
||||
// makeGlobalArray creates a new LLVM global with the given name and integers as
|
||||
// contents, and returns the global.
|
||||
// Note that it is left with the default linkage etc., you should set
|
||||
// linkage/constant/etc properties yourself.
|
||||
func (c *Compiler) makeGlobalArray(bufItf interface{}, name string, elementType llvm.Type) llvm.Value {
|
||||
buf := reflect.ValueOf(bufItf)
|
||||
globalType := llvm.ArrayType(elementType, buf.Len())
|
||||
global := llvm.AddGlobal(c.mod, globalType, name)
|
||||
value := llvm.Undef(globalType)
|
||||
for i := 0; i < buf.Len(); i++ {
|
||||
ch := buf.Index(i).Uint()
|
||||
value = llvm.ConstInsertValue(value, llvm.ConstInt(elementType, ch, false), []uint32{uint32(i)})
|
||||
}
|
||||
global.SetInitializer(value)
|
||||
return global
|
||||
}
|
||||
|
||||
// getGlobalBytes returns the slice contained in the array of the provided
|
||||
// global. It can recover the bytes originally created using makeGlobalArray, if
|
||||
// makeGlobalArray was given a byte slice.
|
||||
func getGlobalBytes(global llvm.Value) []byte {
|
||||
value := global.Initializer()
|
||||
buf := make([]byte, value.Type().ArrayLength())
|
||||
for i := range buf {
|
||||
buf[i] = byte(llvm.ConstExtractValue(value, []uint32{uint32(i)}).ZExtValue())
|
||||
}
|
||||
return buf
|
||||
}
|
||||
|
||||
// replaceGlobalByteWithArray replaces a global integer type in the module with
|
||||
// an integer array, using a GEP to make the types match. It is a convenience
|
||||
// function used for creating reflection sidetables, for example.
|
||||
func (c *Compiler) replaceGlobalIntWithArray(name string, buf interface{}) llvm.Value {
|
||||
oldGlobal := c.mod.NamedGlobal(name)
|
||||
global := c.makeGlobalArray(buf, name+".tmp", oldGlobal.Type().ElementType())
|
||||
gep := llvm.ConstGEP(global, []llvm.Value{
|
||||
llvm.ConstInt(c.ctx.Int32Type(), 0, false),
|
||||
llvm.ConstInt(c.ctx.Int32Type(), 0, false),
|
||||
})
|
||||
oldGlobal.ReplaceAllUsesWith(gep)
|
||||
oldGlobal.EraseFromParentAsGlobal()
|
||||
global.SetName(name)
|
||||
return global
|
||||
}
|
||||
|
||||
+20
-242
@@ -3,6 +3,7 @@ package compiler
|
||||
import (
|
||||
"errors"
|
||||
|
||||
"github.com/tinygo-org/tinygo/transform"
|
||||
"tinygo.org/x/go-llvm"
|
||||
)
|
||||
|
||||
@@ -22,6 +23,11 @@ func (c *Compiler) Optimize(optLevel, sizeLevel int, inlinerThreshold uint) erro
|
||||
c.replacePanicsWithTrap() // -panic=trap
|
||||
}
|
||||
|
||||
// run a check of all of our code
|
||||
if c.VerifyIR {
|
||||
c.checkModule()
|
||||
}
|
||||
|
||||
// Run function passes for each function.
|
||||
funcPasses := llvm.NewFunctionPassManagerForModule(c.mod)
|
||||
defer funcPasses.Dispose()
|
||||
@@ -43,9 +49,9 @@ func (c *Compiler) Optimize(optLevel, sizeLevel int, inlinerThreshold uint) erro
|
||||
goPasses.Run(c.mod)
|
||||
|
||||
// Run Go-specific optimization passes.
|
||||
c.OptimizeMaps()
|
||||
c.OptimizeStringToBytes()
|
||||
c.OptimizeAllocs()
|
||||
transform.OptimizeMaps(c.mod)
|
||||
transform.OptimizeStringToBytes(c.mod)
|
||||
transform.OptimizeAllocs(c.mod)
|
||||
c.LowerInterfaces()
|
||||
c.LowerFuncValues()
|
||||
|
||||
@@ -55,8 +61,8 @@ func (c *Compiler) Optimize(optLevel, sizeLevel int, inlinerThreshold uint) erro
|
||||
goPasses.Run(c.mod)
|
||||
|
||||
// Run TinyGo-specific interprocedural optimizations.
|
||||
c.OptimizeAllocs()
|
||||
c.OptimizeStringToBytes()
|
||||
transform.OptimizeAllocs(c.mod)
|
||||
transform.OptimizeStringToBytes(c.mod)
|
||||
|
||||
// Lower runtime.isnil calls to regular nil comparisons.
|
||||
isnil := c.mod.NamedFunction("runtime.isnil")
|
||||
@@ -101,6 +107,15 @@ func (c *Compiler) Optimize(optLevel, sizeLevel int, inlinerThreshold uint) erro
|
||||
}
|
||||
}
|
||||
|
||||
// After TinyGo-specific transforms have finished, undo exporting these functions.
|
||||
for _, name := range c.getFunctionsUsedInTransforms() {
|
||||
fn := c.mod.NamedFunction(name)
|
||||
if fn.IsNil() {
|
||||
continue
|
||||
}
|
||||
fn.SetLinkage(llvm.InternalLinkage)
|
||||
}
|
||||
|
||||
// Run function passes again, because without it, llvm.coro.size.i32()
|
||||
// doesn't get lowered.
|
||||
for fn := c.mod.FirstFunction(); !fn.IsNil(); fn = llvm.NextFunction(fn) {
|
||||
@@ -143,240 +158,3 @@ func (c *Compiler) replacePanicsWithTrap() {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Eliminate created but not used maps.
|
||||
//
|
||||
// In the future, this should statically allocate created but never modified
|
||||
// maps. This has not yet been implemented, however.
|
||||
func (c *Compiler) OptimizeMaps() {
|
||||
hashmapMake := c.mod.NamedFunction("runtime.hashmapMake")
|
||||
if hashmapMake.IsNil() {
|
||||
// nothing to optimize
|
||||
return
|
||||
}
|
||||
|
||||
hashmapBinarySet := c.mod.NamedFunction("runtime.hashmapBinarySet")
|
||||
hashmapStringSet := c.mod.NamedFunction("runtime.hashmapStringSet")
|
||||
|
||||
for _, makeInst := range getUses(hashmapMake) {
|
||||
updateInsts := []llvm.Value{}
|
||||
unknownUses := false // are there any uses other than setting a value?
|
||||
|
||||
for _, use := range getUses(makeInst) {
|
||||
if use := use.IsACallInst(); !use.IsNil() {
|
||||
switch use.CalledValue() {
|
||||
case hashmapBinarySet, hashmapStringSet:
|
||||
updateInsts = append(updateInsts, use)
|
||||
default:
|
||||
unknownUses = true
|
||||
}
|
||||
} else {
|
||||
unknownUses = true
|
||||
}
|
||||
}
|
||||
|
||||
if !unknownUses {
|
||||
// This map can be entirely removed, as it is only created but never
|
||||
// used.
|
||||
for _, inst := range updateInsts {
|
||||
inst.EraseFromParentAsInstruction()
|
||||
}
|
||||
makeInst.EraseFromParentAsInstruction()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Transform runtime.stringToBytes(...) calls into const []byte slices whenever
|
||||
// possible. This optimizes the following pattern:
|
||||
// w.Write([]byte("foo"))
|
||||
// where Write does not store to the slice.
|
||||
func (c *Compiler) OptimizeStringToBytes() {
|
||||
stringToBytes := c.mod.NamedFunction("runtime.stringToBytes")
|
||||
if stringToBytes.IsNil() {
|
||||
// nothing to optimize
|
||||
return
|
||||
}
|
||||
|
||||
for _, call := range getUses(stringToBytes) {
|
||||
strptr := call.Operand(0)
|
||||
strlen := call.Operand(1)
|
||||
|
||||
// strptr is always constant because strings are always constant.
|
||||
|
||||
convertedAllUses := true
|
||||
for _, use := range getUses(call) {
|
||||
nilValue := llvm.Value{}
|
||||
if use.IsAExtractValueInst() == nilValue {
|
||||
convertedAllUses = false
|
||||
continue
|
||||
}
|
||||
switch use.Type().TypeKind() {
|
||||
case llvm.IntegerTypeKind:
|
||||
// A length (len or cap). Propagate the length value.
|
||||
use.ReplaceAllUsesWith(strlen)
|
||||
use.EraseFromParentAsInstruction()
|
||||
case llvm.PointerTypeKind:
|
||||
// The string pointer itself.
|
||||
if !c.isReadOnly(use) {
|
||||
convertedAllUses = false
|
||||
continue
|
||||
}
|
||||
use.ReplaceAllUsesWith(strptr)
|
||||
use.EraseFromParentAsInstruction()
|
||||
default:
|
||||
// should not happen
|
||||
panic("unknown return type of runtime.stringToBytes: " + use.Type().String())
|
||||
}
|
||||
}
|
||||
if convertedAllUses {
|
||||
// Call to runtime.stringToBytes can be eliminated: both the input
|
||||
// and the output is constant.
|
||||
call.EraseFromParentAsInstruction()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Basic escape analysis: translate runtime.alloc calls into alloca
|
||||
// instructions.
|
||||
func (c *Compiler) OptimizeAllocs() {
|
||||
allocator := c.mod.NamedFunction("runtime.alloc")
|
||||
if allocator.IsNil() {
|
||||
// nothing to optimize
|
||||
return
|
||||
}
|
||||
|
||||
heapallocs := getUses(allocator)
|
||||
for _, heapalloc := range heapallocs {
|
||||
nilValue := llvm.Value{}
|
||||
if heapalloc.Operand(0).IsAConstant() == nilValue {
|
||||
// Do not allocate variable length arrays on the stack.
|
||||
continue
|
||||
}
|
||||
size := heapalloc.Operand(0).ZExtValue()
|
||||
if size > 256 {
|
||||
// The maximum value for a stack allocation.
|
||||
// TODO: tune this, this is just a random value.
|
||||
continue
|
||||
}
|
||||
|
||||
// In general the pattern is:
|
||||
// %0 = call i8* @runtime.alloc(i32 %size)
|
||||
// %1 = bitcast i8* %0 to type*
|
||||
// (use %1 only)
|
||||
// But the bitcast might sometimes be dropped when allocating an *i8.
|
||||
// The 'bitcast' variable below is thus usually a bitcast of the
|
||||
// heapalloc but not always.
|
||||
bitcast := heapalloc // instruction that creates the value
|
||||
if uses := getUses(heapalloc); len(uses) == 1 && uses[0].IsABitCastInst() != nilValue {
|
||||
// getting only bitcast use
|
||||
bitcast = uses[0]
|
||||
}
|
||||
if !c.doesEscape(bitcast) {
|
||||
// Insert alloca in the entry block. Do it here so that mem2reg can
|
||||
// promote it to a SSA value.
|
||||
fn := bitcast.InstructionParent().Parent()
|
||||
c.builder.SetInsertPointBefore(fn.EntryBasicBlock().FirstInstruction())
|
||||
alignment := c.targetData.ABITypeAlignment(c.i8ptrType)
|
||||
sizeInWords := (size + uint64(alignment) - 1) / uint64(alignment)
|
||||
allocaType := llvm.ArrayType(c.ctx.IntType(alignment*8), int(sizeInWords))
|
||||
alloca := c.builder.CreateAlloca(allocaType, "stackalloc.alloca")
|
||||
zero := c.getZeroValue(alloca.Type().ElementType())
|
||||
c.builder.CreateStore(zero, alloca)
|
||||
stackalloc := c.builder.CreateBitCast(alloca, bitcast.Type(), "stackalloc")
|
||||
bitcast.ReplaceAllUsesWith(stackalloc)
|
||||
if heapalloc != bitcast {
|
||||
bitcast.EraseFromParentAsInstruction()
|
||||
}
|
||||
heapalloc.EraseFromParentAsInstruction()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Very basic escape analysis.
|
||||
func (c *Compiler) doesEscape(value llvm.Value) bool {
|
||||
uses := getUses(value)
|
||||
for _, use := range uses {
|
||||
nilValue := llvm.Value{}
|
||||
if use.IsAGetElementPtrInst() != nilValue {
|
||||
if c.doesEscape(use) {
|
||||
return true
|
||||
}
|
||||
} else if use.IsABitCastInst() != nilValue {
|
||||
// A bitcast escapes if the casted-to value escapes.
|
||||
if c.doesEscape(use) {
|
||||
return true
|
||||
}
|
||||
} else if use.IsALoadInst() != nilValue {
|
||||
// Load does not escape.
|
||||
} else if use.IsAStoreInst() != nilValue {
|
||||
// Store only escapes when the value is stored to, not when the
|
||||
// value is stored into another value.
|
||||
if use.Operand(0) == value {
|
||||
return true
|
||||
}
|
||||
} else if use.IsACallInst() != nilValue {
|
||||
if !c.hasFlag(use, value, "nocapture") {
|
||||
return true
|
||||
}
|
||||
} else if use.IsAICmpInst() != nilValue {
|
||||
// Comparing pointers don't let the pointer escape.
|
||||
// This is often a compiler-inserted nil check.
|
||||
} else {
|
||||
// Unknown instruction, might escape.
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
// does not escape
|
||||
return false
|
||||
}
|
||||
|
||||
// Check whether the given value (which is of pointer type) is never stored to.
|
||||
func (c *Compiler) isReadOnly(value llvm.Value) bool {
|
||||
uses := getUses(value)
|
||||
for _, use := range uses {
|
||||
nilValue := llvm.Value{}
|
||||
if use.IsAGetElementPtrInst() != nilValue {
|
||||
if !c.isReadOnly(use) {
|
||||
return false
|
||||
}
|
||||
} else if use.IsACallInst() != nilValue {
|
||||
if !c.hasFlag(use, value, "readonly") {
|
||||
return false
|
||||
}
|
||||
} else {
|
||||
// Unknown instruction, might not be readonly.
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// Check whether all uses of this param as parameter to the call have the given
|
||||
// flag. In most cases, there will only be one use but a function could take the
|
||||
// same parameter twice, in which case both must have the flag.
|
||||
// A flag can be any enum flag, like "readonly".
|
||||
func (c *Compiler) hasFlag(call, param llvm.Value, kind string) bool {
|
||||
fn := call.CalledValue()
|
||||
nilValue := llvm.Value{}
|
||||
if fn.IsAFunction() == nilValue {
|
||||
// This is not a function but something else, like a function pointer.
|
||||
return false
|
||||
}
|
||||
kindID := llvm.AttributeKindID(kind)
|
||||
for i := 0; i < fn.ParamsCount(); i++ {
|
||||
if call.Operand(i) != param {
|
||||
// This is not the parameter we're checking.
|
||||
continue
|
||||
}
|
||||
index := i + 1 // param attributes start at 1
|
||||
attr := fn.GetEnumAttributeAtIndex(index, kindID)
|
||||
nilAttribute := llvm.Attribute{}
|
||||
if attr == nilAttribute {
|
||||
// At least one parameter doesn't have the flag (there may be
|
||||
// multiple).
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
+409
-83
@@ -1,10 +1,43 @@
|
||||
package compiler
|
||||
|
||||
// This file has some compiler support for run-time reflection using the reflect
|
||||
// package. In particular, it encodes type information in type codes in such a
|
||||
// way that the reflect package can decode the type from this information.
|
||||
// Where needed, it also adds some side tables for looking up more information
|
||||
// about a type, when that information cannot be stored directly in the type
|
||||
// code.
|
||||
//
|
||||
// Go has 26 different type kinds.
|
||||
//
|
||||
// Type kinds are subdivided in basic types (see the list of basicTypes below)
|
||||
// that are mostly numeric literals and non-basic (or "complex") types that are
|
||||
// more difficult to encode. These non-basic types come in two forms:
|
||||
// * Prefix types (pointer, slice, interface, channel): these just add
|
||||
// something to an existing type. For example, a pointer like *int just adds
|
||||
// the fact that it's a pointer to an existing type (int).
|
||||
// These are encoded efficiently by adding a prefix to a type code.
|
||||
// * Types with multiple fields (struct, array, func, map). All of these have
|
||||
// multiple fields contained within. Most obviously structs can contain many
|
||||
// types as fields. Also arrays contain not just the element type but also
|
||||
// the length parameter which can be any arbitrary number and thus may not
|
||||
// fit in a type code.
|
||||
// These types are encoded using side tables.
|
||||
//
|
||||
// This distinction is also important for how named types are encoded. At the
|
||||
// moment, named basic type just get a unique number assigned while named
|
||||
// non-basic types have their underlying type stored in a sidetable.
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"go/ast"
|
||||
"math/big"
|
||||
"strings"
|
||||
|
||||
"tinygo.org/x/go-llvm"
|
||||
)
|
||||
|
||||
// A list of basic types and their numbers. This list should be kept in sync
|
||||
// with the list of Kind constants of type.go in the reflect package.
|
||||
var basicTypes = map[string]int64{
|
||||
"bool": 1,
|
||||
"int": 2,
|
||||
@@ -26,25 +59,92 @@ var basicTypes = map[string]int64{
|
||||
"unsafeptr": 18,
|
||||
}
|
||||
|
||||
// A list of non-basic types. Adding 19 to this number will give the Kind as
|
||||
// used in src/reflect/types.go, and it must be kept in sync with that list.
|
||||
var nonBasicTypes = map[string]int64{
|
||||
"chan": 0,
|
||||
"interface": 1,
|
||||
"pointer": 2,
|
||||
"slice": 3,
|
||||
"array": 4,
|
||||
"func": 5,
|
||||
"map": 6,
|
||||
"struct": 7,
|
||||
}
|
||||
|
||||
// typeCodeAssignmentState keeps some global state around for type code
|
||||
// assignments, used to assign one unique type code to each Go type.
|
||||
type typeCodeAssignmentState struct {
|
||||
// An integer that's incremented each time it's used to give unique IDs to
|
||||
// type codes that are not yet fully supported otherwise by the reflect
|
||||
// package (or are simply unused in the compiled program).
|
||||
fallbackIndex int
|
||||
|
||||
// This is the length of an uintptr. Only used occasionally to know whether
|
||||
// a given number can be encoded as a varint.
|
||||
uintptrLen int
|
||||
|
||||
// Map of named types to their type code. It is important that named types
|
||||
// get unique IDs for each type.
|
||||
namedBasicTypes map[string]int
|
||||
namedNonBasicTypes map[string]int
|
||||
|
||||
// Map of array types to their type code.
|
||||
arrayTypes map[string]int
|
||||
arrayTypesSidetable []byte
|
||||
needsArrayTypesSidetable bool
|
||||
|
||||
// Map of struct types to their type code.
|
||||
structTypes map[string]int
|
||||
structTypesSidetable []byte
|
||||
needsStructNamesSidetable bool
|
||||
|
||||
// Map of struct names and tags to their name string.
|
||||
structNames map[string]int
|
||||
structNamesSidetable []byte
|
||||
needsStructTypesSidetable bool
|
||||
|
||||
// This byte array is stored in reflect.namedNonBasicTypesSidetable and is
|
||||
// used at runtime to get details about a named non-basic type.
|
||||
// Entries are varints (see makeVarint below and readVarint in
|
||||
// reflect/sidetables.go for the encoding): one varint per entry. The
|
||||
// integers in namedNonBasicTypes are indices into this array. Because these
|
||||
// are varints, most type codes are really small (just one byte).
|
||||
//
|
||||
// Note that this byte buffer is not created when it is not needed
|
||||
// (reflect.namedNonBasicTypesSidetable has no uses), see
|
||||
// needsNamedTypesSidetable.
|
||||
namedNonBasicTypesSidetable []uint64
|
||||
|
||||
// This indicates whether namedNonBasicTypesSidetable needs to be created at
|
||||
// all. If it is false, namedNonBasicTypesSidetable will contain simple
|
||||
// monotonically increasing numbers.
|
||||
needsNamedNonBasicTypesSidetable bool
|
||||
}
|
||||
|
||||
// assignTypeCodes is used to assign a type code to each type in the program
|
||||
// that is ever stored in an interface. It tries to use the smallest possible
|
||||
// numbers to make the code that works with interfaces as small as possible.
|
||||
func (c *Compiler) assignTypeCodes(typeSlice typeInfoSlice) {
|
||||
fn := c.mod.NamedFunction("reflect.ValueOf")
|
||||
if fn.IsNil() {
|
||||
// reflect.ValueOf is never used, so we can use the most efficient
|
||||
// encoding possible.
|
||||
for i, t := range typeSlice {
|
||||
t.num = uint64(i + 1)
|
||||
}
|
||||
return
|
||||
}
|
||||
// if reflect were not used, we could skip generating the sidetable
|
||||
// this does not help in practice, and is difficult to do correctly
|
||||
|
||||
// Assign typecodes the way the reflect package expects.
|
||||
fallbackIndex := 1
|
||||
namedTypes := make(map[string]int)
|
||||
state := typeCodeAssignmentState{
|
||||
fallbackIndex: 1,
|
||||
uintptrLen: c.uintptrType.IntTypeWidth(),
|
||||
namedBasicTypes: make(map[string]int),
|
||||
namedNonBasicTypes: make(map[string]int),
|
||||
arrayTypes: make(map[string]int),
|
||||
structTypes: make(map[string]int),
|
||||
structNames: make(map[string]int),
|
||||
needsNamedNonBasicTypesSidetable: len(getUses(c.mod.NamedGlobal("reflect.namedNonBasicTypesSidetable"))) != 0,
|
||||
needsStructTypesSidetable: len(getUses(c.mod.NamedGlobal("reflect.structTypesSidetable"))) != 0,
|
||||
needsStructNamesSidetable: len(getUses(c.mod.NamedGlobal("reflect.structNamesSidetable"))) != 0,
|
||||
needsArrayTypesSidetable: len(getUses(c.mod.NamedGlobal("reflect.arrayTypesSidetable"))) != 0,
|
||||
}
|
||||
for _, t := range typeSlice {
|
||||
if t.name[:5] != "type:" {
|
||||
panic("expected type name to start with 'type:'")
|
||||
}
|
||||
num := c.getTypeCodeNum(t.name[5:], &fallbackIndex, namedTypes)
|
||||
num := state.getTypeCodeNum(t.typecode)
|
||||
if num.BitLen() > c.uintptrType.IntTypeWidth() || !num.IsUint64() {
|
||||
// TODO: support this in some way, using a side table for example.
|
||||
// That's less efficient but better than not working at all.
|
||||
@@ -54,25 +154,41 @@ func (c *Compiler) assignTypeCodes(typeSlice typeInfoSlice) {
|
||||
}
|
||||
t.num = num.Uint64()
|
||||
}
|
||||
|
||||
// Only create this sidetable when it is necessary.
|
||||
if state.needsNamedNonBasicTypesSidetable {
|
||||
global := c.replaceGlobalIntWithArray("reflect.namedNonBasicTypesSidetable", state.namedNonBasicTypesSidetable)
|
||||
global.SetLinkage(llvm.InternalLinkage)
|
||||
global.SetUnnamedAddr(true)
|
||||
}
|
||||
if state.needsArrayTypesSidetable {
|
||||
global := c.replaceGlobalIntWithArray("reflect.arrayTypesSidetable", state.arrayTypesSidetable)
|
||||
global.SetLinkage(llvm.InternalLinkage)
|
||||
global.SetUnnamedAddr(true)
|
||||
}
|
||||
if state.needsStructTypesSidetable {
|
||||
global := c.replaceGlobalIntWithArray("reflect.structTypesSidetable", state.structTypesSidetable)
|
||||
global.SetLinkage(llvm.InternalLinkage)
|
||||
global.SetUnnamedAddr(true)
|
||||
}
|
||||
if state.needsStructNamesSidetable {
|
||||
global := c.replaceGlobalIntWithArray("reflect.structNamesSidetable", state.structNamesSidetable)
|
||||
global.SetLinkage(llvm.InternalLinkage)
|
||||
global.SetUnnamedAddr(true)
|
||||
}
|
||||
}
|
||||
|
||||
// getTypeCodeNum returns the typecode for a given type as expected by the
|
||||
// reflect package. Also see getTypeCodeName, which serializes types to a string
|
||||
// based on a types.Type value for this function.
|
||||
func (c *Compiler) getTypeCodeNum(id string, fallbackIndex *int, namedTypes map[string]int) *big.Int {
|
||||
func (state *typeCodeAssignmentState) getTypeCodeNum(typecode llvm.Value) *big.Int {
|
||||
// Note: see src/reflect/type.go for bit allocations.
|
||||
// A type can be named or unnamed. Example of both:
|
||||
// basic:~foo:uint64
|
||||
// basic:uint64
|
||||
// Extract the class (basic, slice, pointer, etc.), the name, and the
|
||||
// contents of this type ID string. Allocate bits based on that, as
|
||||
// src/runtime/types.go expects.
|
||||
class := id[:strings.IndexByte(id, ':')]
|
||||
value := id[len(class)+1:]
|
||||
class, value := getClassAndValueFromTypeCode(typecode)
|
||||
name := ""
|
||||
if value[0] == '~' {
|
||||
name = value[1:strings.IndexByte(value, ':')]
|
||||
value = value[len(name)+2:]
|
||||
if class == "named" {
|
||||
name = value
|
||||
typecode = llvm.ConstExtractValue(typecode.Initializer(), []uint32{0})
|
||||
class, value = getClassAndValueFromTypeCode(typecode)
|
||||
}
|
||||
if class == "basic" {
|
||||
// Basic types follow the following bit pattern:
|
||||
@@ -81,76 +197,286 @@ func (c *Compiler) getTypeCodeNum(id string, fallbackIndex *int, namedTypes map[
|
||||
// upper bits are used to indicate the named type.
|
||||
num, ok := basicTypes[value]
|
||||
if !ok {
|
||||
panic("invalid basic type: " + id)
|
||||
panic("invalid basic type: " + value)
|
||||
}
|
||||
if name != "" {
|
||||
// This type is named, set the upper bits to the name ID.
|
||||
num |= int64(getNamedTypeNum(namedTypes, name)) << 5
|
||||
num |= int64(state.getBasicNamedTypeNum(name)) << 5
|
||||
}
|
||||
return big.NewInt(num << 1)
|
||||
} else {
|
||||
// Complex types use the following bit pattern:
|
||||
// Non-baisc types use the following bit pattern:
|
||||
// ...nxxx1
|
||||
// where xxx indicates the complex type (any non-basic type). The upper
|
||||
// bits contain whatever the type contains. Types that wrap a single
|
||||
// other type (channel, interface, pointer, slice) just contain the bits
|
||||
// of the wrapped type. Other types (like struct) have a different
|
||||
// method of encoding the contents of the type.
|
||||
var num *big.Int
|
||||
// where xxx indicates the non-basic type. The upper bits contain
|
||||
// whatever the type contains. Types that wrap a single other type
|
||||
// (channel, interface, pointer, slice) just contain the bits of the
|
||||
// wrapped type. Other types (like struct) need more fields and thus
|
||||
// cannot be encoded as a simple prefix.
|
||||
var classNumber int64
|
||||
switch class {
|
||||
case "chan":
|
||||
num = c.getTypeCodeNum(value, fallbackIndex, namedTypes)
|
||||
classNumber = 0
|
||||
case "interface":
|
||||
num = big.NewInt(int64(*fallbackIndex))
|
||||
*fallbackIndex++
|
||||
classNumber = 1
|
||||
case "pointer":
|
||||
num = c.getTypeCodeNum(value, fallbackIndex, namedTypes)
|
||||
classNumber = 2
|
||||
case "slice":
|
||||
num = c.getTypeCodeNum(value, fallbackIndex, namedTypes)
|
||||
classNumber = 3
|
||||
case "array":
|
||||
num = big.NewInt(int64(*fallbackIndex))
|
||||
*fallbackIndex++
|
||||
classNumber = 4
|
||||
case "func":
|
||||
num = big.NewInt(int64(*fallbackIndex))
|
||||
*fallbackIndex++
|
||||
classNumber = 5
|
||||
case "map":
|
||||
num = big.NewInt(int64(*fallbackIndex))
|
||||
*fallbackIndex++
|
||||
classNumber = 6
|
||||
case "struct":
|
||||
num = big.NewInt(int64(*fallbackIndex))
|
||||
*fallbackIndex++
|
||||
classNumber = 7
|
||||
default:
|
||||
panic("unknown type kind: " + id)
|
||||
}
|
||||
if name == "" {
|
||||
num.Lsh(num, 5).Or(num, big.NewInt((classNumber<<1)+1))
|
||||
if n, ok := nonBasicTypes[class]; ok {
|
||||
classNumber = n
|
||||
} else {
|
||||
// TODO: store num in a sidetable
|
||||
num = big.NewInt(int64(getNamedTypeNum(namedTypes, name))<<1 | 1)
|
||||
num.Lsh(num, 4).Or(num, big.NewInt((classNumber<<1)+1))
|
||||
panic("unknown type kind: " + class)
|
||||
}
|
||||
var num *big.Int
|
||||
lowBits := (classNumber << 1) + 1 // the 5 low bits of the typecode
|
||||
if name == "" {
|
||||
num = state.getNonBasicTypeCode(class, typecode)
|
||||
} else {
|
||||
// We must return a named type here. But first check whether it
|
||||
// has already been defined.
|
||||
if index, ok := state.namedNonBasicTypes[name]; ok {
|
||||
num := big.NewInt(int64(index))
|
||||
num.Lsh(num, 5).Or(num, big.NewInt((classNumber<<1)+1+(1<<4)))
|
||||
return num
|
||||
}
|
||||
lowBits |= 1 << 4 // set the 'n' bit (see above)
|
||||
if !state.needsNamedNonBasicTypesSidetable {
|
||||
// Use simple small integers in this case, to make these numbers
|
||||
// smaller.
|
||||
index := len(state.namedNonBasicTypes) + 1
|
||||
state.namedNonBasicTypes[name] = index
|
||||
num = big.NewInt(int64(index))
|
||||
} else {
|
||||
// We need to store full type information.
|
||||
// First allocate a number in the named non-basic type
|
||||
// sidetable.
|
||||
index := len(state.namedNonBasicTypesSidetable)
|
||||
state.namedNonBasicTypesSidetable = append(state.namedNonBasicTypesSidetable, 0)
|
||||
state.namedNonBasicTypes[name] = index
|
||||
// Get the typecode of the underlying type (which could be the
|
||||
// element type in the case of pointers, for example).
|
||||
num = state.getNonBasicTypeCode(class, typecode)
|
||||
if num.BitLen() > state.uintptrLen || !num.IsUint64() {
|
||||
panic("cannot store value in sidetable")
|
||||
}
|
||||
// Now update the side table with the number we just
|
||||
// determined. We need this multi-step approach to avoid stack
|
||||
// overflow due to adding types recursively in the case of
|
||||
// linked lists (a pointer which points to a struct that
|
||||
// contains that same pointer).
|
||||
state.namedNonBasicTypesSidetable[index] = num.Uint64()
|
||||
num = big.NewInt(int64(index))
|
||||
}
|
||||
}
|
||||
// Concatenate the 'num' and 'lowBits' bitstrings.
|
||||
num.Lsh(num, 5).Or(num, big.NewInt(lowBits))
|
||||
return num
|
||||
}
|
||||
}
|
||||
|
||||
// getNamedTypeNum returns an appropriate (unique) number for the given named
|
||||
// type. If the name already has a number that number is returned, else a new
|
||||
// number is returned. The number is always non-zero.
|
||||
func getNamedTypeNum(namedTypes map[string]int, name string) int {
|
||||
if num, ok := namedTypes[name]; ok {
|
||||
return num
|
||||
} else {
|
||||
num = len(namedTypes) + 1
|
||||
namedTypes[name] = num
|
||||
// getNonBasicTypeCode is used by getTypeCodeNum. It returns the upper bits of
|
||||
// the type code used there in the type code.
|
||||
func (state *typeCodeAssignmentState) getNonBasicTypeCode(class string, typecode llvm.Value) *big.Int {
|
||||
switch class {
|
||||
case "chan", "pointer", "slice":
|
||||
// Prefix-style type kinds. The upper bits contain the element type.
|
||||
sub := llvm.ConstExtractValue(typecode.Initializer(), []uint32{0})
|
||||
return state.getTypeCodeNum(sub)
|
||||
case "array":
|
||||
// An array is basically a pair of (typecode, length) stored in a
|
||||
// sidetable.
|
||||
return big.NewInt(int64(state.getArrayTypeNum(typecode)))
|
||||
case "struct":
|
||||
// More complicated type kind. The upper bits contain the index to the
|
||||
// struct type in the struct types sidetable.
|
||||
return big.NewInt(int64(state.getStructTypeNum(typecode)))
|
||||
default:
|
||||
// Type has not yet been implemented, so fall back by using a unique
|
||||
// number.
|
||||
num := big.NewInt(int64(state.fallbackIndex))
|
||||
state.fallbackIndex++
|
||||
return num
|
||||
}
|
||||
}
|
||||
|
||||
// getClassAndValueFromTypeCode takes a typecode (a llvm.Value of type
|
||||
// runtime.typecodeID), looks at the name, and extracts the typecode class and
|
||||
// value from it. For example, for a typecode with the following name:
|
||||
// reflect/types.type:pointer:named:reflect.ValueError
|
||||
// It extracts:
|
||||
// class = "pointer"
|
||||
// value = "named:reflect.ValueError"
|
||||
func getClassAndValueFromTypeCode(typecode llvm.Value) (class, value string) {
|
||||
typecodeName := typecode.Name()
|
||||
const prefix = "reflect/types.type:"
|
||||
if !strings.HasPrefix(typecodeName, prefix) {
|
||||
panic("unexpected typecode name: " + typecodeName)
|
||||
}
|
||||
id := typecodeName[len(prefix):]
|
||||
class = id[:strings.IndexByte(id, ':')]
|
||||
value = id[len(class)+1:]
|
||||
return
|
||||
}
|
||||
|
||||
// getBasicNamedTypeNum returns an appropriate (unique) number for the given
|
||||
// named type. If the name already has a number that number is returned, else a
|
||||
// new number is returned. The number is always non-zero.
|
||||
func (state *typeCodeAssignmentState) getBasicNamedTypeNum(name string) int {
|
||||
if num, ok := state.namedBasicTypes[name]; ok {
|
||||
return num
|
||||
}
|
||||
num := len(state.namedBasicTypes) + 1
|
||||
state.namedBasicTypes[name] = num
|
||||
return num
|
||||
}
|
||||
|
||||
// getArrayTypeNum returns the array type number, which is an index into the
|
||||
// reflect.arrayTypesSidetable or a unique number for this type if this table is
|
||||
// not used.
|
||||
func (state *typeCodeAssignmentState) getArrayTypeNum(typecode llvm.Value) int {
|
||||
name := typecode.Name()
|
||||
if num, ok := state.arrayTypes[name]; ok {
|
||||
// This array type already has an entry in the sidetable. Don't store
|
||||
// it twice.
|
||||
return num
|
||||
}
|
||||
|
||||
if !state.needsArrayTypesSidetable {
|
||||
// We don't need array sidetables, so we can just assign monotonically
|
||||
// increasing numbers to each array type.
|
||||
num := len(state.arrayTypes)
|
||||
state.arrayTypes[name] = num
|
||||
return num
|
||||
}
|
||||
|
||||
elemTypeCode := llvm.ConstExtractValue(typecode.Initializer(), []uint32{0})
|
||||
elemTypeNum := state.getTypeCodeNum(elemTypeCode)
|
||||
if elemTypeNum.BitLen() > state.uintptrLen || !elemTypeNum.IsUint64() {
|
||||
// TODO: make this a regular error
|
||||
panic("array element type has a type code that is too big")
|
||||
}
|
||||
|
||||
// The array side table is a sequence of {element type, array length}.
|
||||
arrayLength := llvm.ConstExtractValue(typecode.Initializer(), []uint32{1}).ZExtValue()
|
||||
buf := makeVarint(elemTypeNum.Uint64())
|
||||
buf = append(buf, makeVarint(arrayLength)...)
|
||||
|
||||
index := len(state.arrayTypesSidetable)
|
||||
state.arrayTypes[name] = index
|
||||
state.arrayTypesSidetable = append(state.arrayTypesSidetable, buf...)
|
||||
return index
|
||||
}
|
||||
|
||||
// getStructTypeNum returns the struct type number, which is an index into
|
||||
// reflect.structTypesSidetable or an unique number for every struct if this
|
||||
// sidetable is not needed in the to-be-compiled program.
|
||||
func (state *typeCodeAssignmentState) getStructTypeNum(typecode llvm.Value) int {
|
||||
name := typecode.Name()
|
||||
if num, ok := state.structTypes[name]; ok {
|
||||
// This struct already has an assigned type code.
|
||||
return num
|
||||
}
|
||||
|
||||
if !state.needsStructTypesSidetable {
|
||||
// We don't need struct sidetables, so we can just assign monotonically
|
||||
// increasing numbers to each struct type.
|
||||
num := len(state.structTypes)
|
||||
state.structTypes[name] = num
|
||||
return num
|
||||
}
|
||||
|
||||
// Get the fields this struct type contains.
|
||||
// The struct number will be the start index of
|
||||
structTypeGlobal := llvm.ConstExtractValue(typecode.Initializer(), []uint32{0}).Operand(0).Initializer()
|
||||
numFields := structTypeGlobal.Type().ArrayLength()
|
||||
|
||||
// The first data that is stored in the struct sidetable is the number of
|
||||
// fields this struct contains. This is usually just a single byte because
|
||||
// most structs don't contain that many fields, but make it a varint just
|
||||
// to be sure.
|
||||
buf := makeVarint(uint64(numFields))
|
||||
|
||||
// Iterate over every field in the struct.
|
||||
// Every field is stored sequentially in the struct sidetable. Fields can
|
||||
// be retrieved from this list of fields at runtime by iterating over all
|
||||
// of them until the right field has been found.
|
||||
// Perhaps adding some index would speed things up, but it would also make
|
||||
// the sidetable bigger.
|
||||
for i := 0; i < numFields; i++ {
|
||||
// Collect some information about this field.
|
||||
field := llvm.ConstExtractValue(structTypeGlobal, []uint32{uint32(i)})
|
||||
|
||||
nameGlobal := llvm.ConstExtractValue(field, []uint32{1})
|
||||
if nameGlobal == llvm.ConstPointerNull(nameGlobal.Type()) {
|
||||
panic("compiler: no name for this struct field")
|
||||
}
|
||||
fieldNameBytes := getGlobalBytes(nameGlobal.Operand(0))
|
||||
fieldNameNumber := state.getStructNameNumber(fieldNameBytes)
|
||||
|
||||
// See whether this struct field has an associated tag, and if so,
|
||||
// store that tag in the tags sidetable.
|
||||
tagGlobal := llvm.ConstExtractValue(field, []uint32{2})
|
||||
hasTag := false
|
||||
tagNumber := 0
|
||||
if tagGlobal != llvm.ConstPointerNull(tagGlobal.Type()) {
|
||||
hasTag = true
|
||||
tagBytes := getGlobalBytes(tagGlobal.Operand(0))
|
||||
tagNumber = state.getStructNameNumber(tagBytes)
|
||||
}
|
||||
|
||||
// The 'embedded' or 'anonymous' flag for this field.
|
||||
embedded := llvm.ConstExtractValue(field, []uint32{3}).ZExtValue() != 0
|
||||
|
||||
// The first byte in the struct types sidetable is a flags byte with
|
||||
// two bits in it.
|
||||
flagsByte := byte(0)
|
||||
if embedded {
|
||||
flagsByte |= 1
|
||||
}
|
||||
if hasTag {
|
||||
flagsByte |= 2
|
||||
}
|
||||
if ast.IsExported(string(fieldNameBytes)) {
|
||||
flagsByte |= 4
|
||||
}
|
||||
buf = append(buf, flagsByte)
|
||||
|
||||
// Get the type number and add it to the buffer.
|
||||
// All fields have a type, so include it directly here.
|
||||
typeNum := state.getTypeCodeNum(llvm.ConstExtractValue(field, []uint32{0}))
|
||||
if typeNum.BitLen() > state.uintptrLen || !typeNum.IsUint64() {
|
||||
// TODO: make this a regular error
|
||||
panic("struct field has a type code that is too big")
|
||||
}
|
||||
buf = append(buf, makeVarint(typeNum.Uint64())...)
|
||||
|
||||
// Add the name.
|
||||
buf = append(buf, makeVarint(uint64(fieldNameNumber))...)
|
||||
|
||||
// Add the tag, if there is one.
|
||||
if hasTag {
|
||||
buf = append(buf, makeVarint(uint64(tagNumber))...)
|
||||
}
|
||||
}
|
||||
|
||||
num := len(state.structTypesSidetable)
|
||||
state.structTypes[name] = num
|
||||
state.structTypesSidetable = append(state.structTypesSidetable, buf...)
|
||||
return num
|
||||
}
|
||||
|
||||
// getStructNameNumber stores this string (name or tag) onto the struct names
|
||||
// sidetable. The format is a varint of the length of the struct, followed by
|
||||
// the raw bytes of the name. Multiple identical strings are stored under the
|
||||
// same name for space efficiency.
|
||||
func (state *typeCodeAssignmentState) getStructNameNumber(nameBytes []byte) int {
|
||||
name := string(nameBytes)
|
||||
if n, ok := state.structNames[name]; ok {
|
||||
// This name was used before, re-use it now (for space efficiency).
|
||||
return n
|
||||
}
|
||||
// This name is not yet in the names sidetable. Add it now.
|
||||
n := len(state.structNamesSidetable)
|
||||
state.structNames[name] = n
|
||||
state.structNamesSidetable = append(state.structNamesSidetable, makeVarint(uint64(len(nameBytes)))...)
|
||||
state.structNamesSidetable = append(state.structNamesSidetable, nameBytes...)
|
||||
return n
|
||||
}
|
||||
|
||||
// makeVarint is a small helper function that returns the bytes of the number in
|
||||
// varint encoding.
|
||||
func makeVarint(n uint64) []byte {
|
||||
buf := make([]byte, binary.MaxVarintLen64)
|
||||
return buf[:binary.PutUvarint(buf, n)]
|
||||
}
|
||||
|
||||
+11
-1
@@ -7,6 +7,7 @@ import (
|
||||
"go/ast"
|
||||
"go/token"
|
||||
"go/types"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/tinygo-org/tinygo/loader"
|
||||
@@ -20,6 +21,7 @@ import (
|
||||
type globalInfo struct {
|
||||
linkName string // go:extern
|
||||
extern bool // go:extern
|
||||
align int // go:align
|
||||
}
|
||||
|
||||
// loadASTComments loads comments on globals from the AST, for use later in the
|
||||
@@ -61,9 +63,12 @@ func (c *Compiler) getGlobal(g *ssa.Global) llvm.Value {
|
||||
llvmType := c.getLLVMType(g.Type().(*types.Pointer).Elem())
|
||||
llvmGlobal = llvm.AddGlobal(c.mod, llvmType, info.linkName)
|
||||
if !info.extern {
|
||||
llvmGlobal.SetInitializer(c.getZeroValue(llvmType))
|
||||
llvmGlobal.SetInitializer(llvm.ConstNull(llvmType))
|
||||
llvmGlobal.SetLinkage(llvm.InternalLinkage)
|
||||
}
|
||||
if info.align > c.targetData.ABITypeAlignment(llvmType) {
|
||||
llvmGlobal.SetAlignment(info.align)
|
||||
}
|
||||
}
|
||||
return llvmGlobal
|
||||
}
|
||||
@@ -102,6 +107,11 @@ func (info *globalInfo) parsePragmas(doc *ast.CommentGroup) {
|
||||
if len(parts) == 2 {
|
||||
info.linkName = parts[1]
|
||||
}
|
||||
case "//go:align":
|
||||
align, err := strconv.Atoi(parts[1])
|
||||
if err == nil {
|
||||
info.align = align
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+28
-2
@@ -58,6 +58,32 @@ func (c *Compiler) emitSyscall(frame *Frame, call *ssa.CallCommon) (llvm.Value,
|
||||
fnType := llvm.FunctionType(c.uintptrType, argTypes, false)
|
||||
target := llvm.InlineAsm(fnType, "syscall", constraints, true, false, llvm.InlineAsmDialectIntel)
|
||||
syscallResult = c.builder.CreateCall(target, args, "")
|
||||
case c.GOARCH == "386" && c.GOOS == "linux":
|
||||
// Sources:
|
||||
// syscall(2) man page
|
||||
// https://stackoverflow.com/a/2538212
|
||||
// https://en.wikibooks.org/wiki/X86_Assembly/Interfacing_with_Linux#int_0x80
|
||||
args := []llvm.Value{num}
|
||||
argTypes := []llvm.Type{c.uintptrType}
|
||||
// Constraints will look something like:
|
||||
// "={eax},0,{ebx},{ecx},{edx},{esi},{edi},{ebp}"
|
||||
constraints := "={eax},0"
|
||||
for i, arg := range call.Args[1:] {
|
||||
constraints += "," + [...]string{
|
||||
"{ebx}",
|
||||
"{ecx}",
|
||||
"{edx}",
|
||||
"{esi}",
|
||||
"{edi}",
|
||||
"{ebp}",
|
||||
}[i]
|
||||
llvmValue := c.getValue(frame, arg)
|
||||
args = append(args, llvmValue)
|
||||
argTypes = append(argTypes, llvmValue.Type())
|
||||
}
|
||||
fnType := llvm.FunctionType(c.uintptrType, argTypes, false)
|
||||
target := llvm.InlineAsm(fnType, "int 0x80", constraints, true, false, llvm.InlineAsmDialectIntel)
|
||||
syscallResult = c.builder.CreateCall(target, args, "")
|
||||
case c.GOARCH == "arm" && c.GOOS == "linux":
|
||||
// Implement the EABI system call convention for Linux.
|
||||
// Source: syscall(2) man page.
|
||||
@@ -139,7 +165,7 @@ func (c *Compiler) emitSyscall(frame *Frame, call *ssa.CallCommon) (llvm.Value,
|
||||
inrange2 := c.builder.CreateICmp(llvm.IntSGT, syscallResult, llvm.ConstInt(c.uintptrType, 0xfffffffffffff000, true), "") // -4096
|
||||
hasError := c.builder.CreateAnd(inrange1, inrange2, "")
|
||||
errResult := c.builder.CreateSelect(hasError, c.builder.CreateSub(zero, syscallResult, ""), zero, "syscallError")
|
||||
retval := llvm.Undef(llvm.StructType([]llvm.Type{c.uintptrType, c.uintptrType, c.uintptrType}, false))
|
||||
retval := llvm.Undef(c.ctx.StructType([]llvm.Type{c.uintptrType, c.uintptrType, c.uintptrType}, false))
|
||||
retval = c.builder.CreateInsertValue(retval, syscallResult, 0, "")
|
||||
retval = c.builder.CreateInsertValue(retval, zero, 1, "")
|
||||
retval = c.builder.CreateInsertValue(retval, errResult, 2, "")
|
||||
@@ -155,7 +181,7 @@ func (c *Compiler) emitSyscall(frame *Frame, call *ssa.CallCommon) (llvm.Value,
|
||||
zero := llvm.ConstInt(c.uintptrType, 0, false)
|
||||
hasError := c.builder.CreateICmp(llvm.IntNE, syscallResult, llvm.ConstInt(c.uintptrType, 0, false), "")
|
||||
errResult := c.builder.CreateSelect(hasError, syscallResult, zero, "syscallError")
|
||||
retval := llvm.Undef(llvm.StructType([]llvm.Type{c.uintptrType, c.uintptrType, c.uintptrType}, false))
|
||||
retval := llvm.Undef(c.ctx.StructType([]llvm.Type{c.uintptrType, c.uintptrType, c.uintptrType}, false))
|
||||
retval = c.builder.CreateInsertValue(retval, syscallResult, 0, "")
|
||||
retval = c.builder.CreateInsertValue(retval, zero, 1, "")
|
||||
retval = c.builder.CreateInsertValue(retval, errResult, 2, "")
|
||||
|
||||
@@ -101,7 +101,7 @@ func (c *Compiler) emitPointerUnpack(ptr llvm.Value, valueTypes []llvm.Type) []l
|
||||
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)
|
||||
values[i] = llvm.ConstNull(valueType)
|
||||
continue
|
||||
}
|
||||
indices := []llvm.Value{
|
||||
|
||||
@@ -4,7 +4,10 @@ go 1.11
|
||||
|
||||
require (
|
||||
github.com/blakesmith/ar v0.0.0-20150311145944-8bd4349a67f2
|
||||
github.com/creack/goselect v0.1.0 // indirect
|
||||
github.com/marcinbor85/gohex v0.0.0-20180128172054-7a43cd876e46
|
||||
go.bug.st/serial.v1 v0.0.0-20180827123349-5f7892a7bb45
|
||||
golang.org/x/sys v0.0.0-20191010194322-b09406accb47 // indirect
|
||||
golang.org/x/tools v0.0.0-20190227180812-8dcc6e70cdef
|
||||
tinygo.org/x/go-llvm v0.0.0-20190224120431-7707ae5d1261
|
||||
tinygo.org/x/go-llvm v0.0.0-20190818154551-95bc4ffe1add
|
||||
)
|
||||
|
||||
@@ -1,14 +1,22 @@
|
||||
github.com/blakesmith/ar v0.0.0-20150311145944-8bd4349a67f2 h1:oMCHnXa6CCCafdPDbMh/lWRhRByN0VFLvv+g+ayx1SI=
|
||||
github.com/blakesmith/ar v0.0.0-20150311145944-8bd4349a67f2/go.mod h1:PkYb9DJNAwrSvRx5DYA+gUcOIgTGVMNkfSCbZM8cWpI=
|
||||
github.com/creack/goselect v0.1.0 h1:4QiXIhcpSQF50XGaBsFzesjwX/1qOY5bOveQPmN9CXY=
|
||||
github.com/creack/goselect v0.1.0/go.mod h1:gHrIcH/9UZDn2qgeTUeW5K9eZsVYCH6/60J/FHysWyE=
|
||||
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/marcinbor85/gohex v0.0.0-20180128172054-7a43cd876e46 h1:wXG2bA8fO7Vv7lLk2PihFMTqmbT173Tje39oKzQ50Mo=
|
||||
github.com/marcinbor85/gohex v0.0.0-20180128172054-7a43cd876e46/go.mod h1:Pb6XcsXyropB9LNHhnqaknG/vEwYztLkQzVCHv8sQ3M=
|
||||
go.bug.st/serial.v1 v0.0.0-20180827123349-5f7892a7bb45 h1:mACY1anK6HNCZtm/DK2Rf2ZPHggVqeB0+7rY9Gl6wyI=
|
||||
go.bug.st/serial.v1 v0.0.0-20180827123349-5f7892a7bb45/go.mod h1:dRSl/CVCTf56CkXgJMDOdSwNfo2g1orOGE/gBGdvjZw=
|
||||
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sys v0.0.0-20191010194322-b09406accb47 h1:/XfQ9z7ib8eEJX2hdgFTZJ/ntt0swNk5oYBziWeTCvY=
|
||||
golang.org/x/sys v0.0.0-20191010194322-b09406accb47/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/tools v0.0.0-20190227180812-8dcc6e70cdef h1:ymc9FeDom3RIEA3coKokSllBB1hRcMT0tZ1W3Jf9Ids=
|
||||
golang.org/x/tools v0.0.0-20190227180812-8dcc6e70cdef/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
|
||||
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||
tinygo.org/x/go-llvm v0.0.0-20190224120431-7707ae5d1261 h1:rJS2Hga39YAnm7DE4qrPm6Dr/67EOojL0XPzvbEeBiw=
|
||||
tinygo.org/x/go-llvm v0.0.0-20190224120431-7707ae5d1261/go.mod h1:fv1F0BSNpxMfCL0zF3M4OPFbgYHnhtB6ST0HvUtu/LE=
|
||||
tinygo.org/x/go-llvm v0.0.0-20190818154551-95bc4ffe1add h1:dFjMH1sLhYADg8UQm7DB56B7e+TfvAmWmEZLhyv3r/w=
|
||||
tinygo.org/x/go-llvm v0.0.0-20190818154551-95bc4ffe1add/go.mod h1:fv1F0BSNpxMfCL0zF3M4OPFbgYHnhtB6ST0HvUtu/LE=
|
||||
|
||||
+189
@@ -0,0 +1,189 @@
|
||||
// Package goenv returns environment variables that are used in various parts of
|
||||
// the compiler. You can query it manually with the `tinygo env` subcommand.
|
||||
package goenv
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
"os/user"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
)
|
||||
|
||||
// Keys is a slice of all available environment variable keys.
|
||||
var Keys = []string{
|
||||
"GOOS",
|
||||
"GOARCH",
|
||||
"GOROOT",
|
||||
"GOPATH",
|
||||
"GOCACHE",
|
||||
"TINYGOROOT",
|
||||
}
|
||||
|
||||
// TINYGOROOT is the path to the final location for checking tinygo files. If
|
||||
// unset (by a -X ldflag), then sourceDir() will fallback to the original build
|
||||
// directory.
|
||||
var TINYGOROOT string
|
||||
|
||||
// Get returns a single environment variable, possibly calculating it on-demand.
|
||||
// The empty string is returned for unknown environment variables.
|
||||
func Get(name string) string {
|
||||
switch name {
|
||||
case "GOOS":
|
||||
if dir := os.Getenv("GOOS"); dir != "" {
|
||||
return dir
|
||||
}
|
||||
return runtime.GOOS
|
||||
case "GOARCH":
|
||||
if dir := os.Getenv("GOARCH"); dir != "" {
|
||||
return dir
|
||||
}
|
||||
return runtime.GOARCH
|
||||
case "GOROOT":
|
||||
return getGoroot()
|
||||
case "GOPATH":
|
||||
if dir := os.Getenv("GOPATH"); dir != "" {
|
||||
return dir
|
||||
}
|
||||
|
||||
// fallback
|
||||
home := getHomeDir()
|
||||
return filepath.Join(home, "go")
|
||||
case "GOCACHE":
|
||||
// Get the cache directory, usually ~/.cache/tinygo
|
||||
dir, err := os.UserCacheDir()
|
||||
if err != nil {
|
||||
panic("could not find cache dir: " + err.Error())
|
||||
}
|
||||
return filepath.Join(dir, "tinygo")
|
||||
case "TINYGOROOT":
|
||||
return sourceDir()
|
||||
default:
|
||||
return ""
|
||||
}
|
||||
}
|
||||
|
||||
// Return the TINYGOROOT, or exit with an error.
|
||||
func sourceDir() string {
|
||||
// Use $TINYGOROOT as root, if available.
|
||||
root := os.Getenv("TINYGOROOT")
|
||||
if root != "" {
|
||||
if !isSourceDir(root) {
|
||||
fmt.Fprintln(os.Stderr, "error: $TINYGOROOT was not set to the correct root")
|
||||
os.Exit(1)
|
||||
}
|
||||
return root
|
||||
}
|
||||
|
||||
if TINYGOROOT != "" {
|
||||
if !isSourceDir(TINYGOROOT) {
|
||||
fmt.Fprintln(os.Stderr, "error: TINYGOROOT was not set to the correct root")
|
||||
os.Exit(1)
|
||||
}
|
||||
return TINYGOROOT
|
||||
}
|
||||
|
||||
// Find root from executable path.
|
||||
path, err := os.Executable()
|
||||
if err != nil {
|
||||
// Very unlikely. Bail out if it happens.
|
||||
panic("could not get executable path: " + err.Error())
|
||||
}
|
||||
root = filepath.Dir(filepath.Dir(path))
|
||||
if isSourceDir(root) {
|
||||
return root
|
||||
}
|
||||
|
||||
// Fallback: use the original directory from where it was built
|
||||
// https://stackoverflow.com/a/32163888/559350
|
||||
_, path, _, _ = runtime.Caller(0)
|
||||
root = filepath.Dir(filepath.Dir(path))
|
||||
if isSourceDir(root) {
|
||||
return root
|
||||
}
|
||||
|
||||
fmt.Fprintln(os.Stderr, "error: could not autodetect root directory, set the TINYGOROOT environment variable to override")
|
||||
os.Exit(1)
|
||||
panic("unreachable")
|
||||
}
|
||||
|
||||
// isSourceDir returns true if the directory looks like a TinyGo source directory.
|
||||
func isSourceDir(root string) bool {
|
||||
_, err := os.Stat(filepath.Join(root, "src/runtime/internal/sys/zversion.go"))
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
_, err = os.Stat(filepath.Join(root, "src/device/arm/arm.go"))
|
||||
return err == nil
|
||||
}
|
||||
|
||||
func getHomeDir() string {
|
||||
u, err := user.Current()
|
||||
if err != nil {
|
||||
panic("cannot get current user: " + err.Error())
|
||||
}
|
||||
if u.HomeDir == "" {
|
||||
// This is very unlikely, so panic here.
|
||||
// Not the nicest solution, however.
|
||||
panic("could not find home directory")
|
||||
}
|
||||
return u.HomeDir
|
||||
}
|
||||
|
||||
// getGoroot returns an appropriate GOROOT from various sources. If it can't be
|
||||
// found, it returns an empty string.
|
||||
func getGoroot() string {
|
||||
goroot := os.Getenv("GOROOT")
|
||||
if goroot != "" {
|
||||
// An explicitly set GOROOT always has preference.
|
||||
return goroot
|
||||
}
|
||||
|
||||
// Check for the location of the 'go' binary and base GOROOT on that.
|
||||
binpath, err := exec.LookPath("go")
|
||||
if err == nil {
|
||||
binpath, err = filepath.EvalSymlinks(binpath)
|
||||
if err == nil {
|
||||
goroot := filepath.Dir(filepath.Dir(binpath))
|
||||
if isGoroot(goroot) {
|
||||
return goroot
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Check what GOROOT was at compile time.
|
||||
if isGoroot(runtime.GOROOT()) {
|
||||
return runtime.GOROOT()
|
||||
}
|
||||
|
||||
// Check for some standard locations, as a last resort.
|
||||
var candidates []string
|
||||
switch runtime.GOOS {
|
||||
case "linux":
|
||||
candidates = []string{
|
||||
"/usr/local/go", // manually installed
|
||||
"/usr/lib/go", // from the distribution
|
||||
}
|
||||
case "darwin":
|
||||
candidates = []string{
|
||||
"/usr/local/go", // manually installed
|
||||
"/usr/local/opt/go/libexec", // from Homebrew
|
||||
}
|
||||
}
|
||||
|
||||
for _, candidate := range candidates {
|
||||
if isGoroot(candidate) {
|
||||
return candidate
|
||||
}
|
||||
}
|
||||
|
||||
// Can't find GOROOT...
|
||||
return ""
|
||||
}
|
||||
|
||||
// isGoroot checks whether the given path looks like a GOROOT.
|
||||
func isGoroot(goroot string) bool {
|
||||
_, err := os.Stat(filepath.Join(goroot, "src", "runtime", "internal", "sys", "zversion.go"))
|
||||
return err == nil
|
||||
}
|
||||
+47
-4
@@ -86,7 +86,7 @@ func (fr *frame) evalBasicBlock(bb, incoming llvm.BasicBlock, indent string) (re
|
||||
case !inst.IsAAllocaInst().IsNil():
|
||||
allocType := inst.Type().ElementType()
|
||||
alloca := llvm.AddGlobal(fr.Mod, allocType, fr.pkgName+"$alloca")
|
||||
alloca.SetInitializer(getZeroValue(allocType))
|
||||
alloca.SetInitializer(llvm.ConstNull(allocType))
|
||||
alloca.SetLinkage(llvm.InternalLinkage)
|
||||
fr.locals[inst] = &LocalValue{
|
||||
Underlying: alloca,
|
||||
@@ -253,7 +253,7 @@ func (fr *frame) evalBasicBlock(bb, incoming llvm.BasicBlock, indent string) (re
|
||||
allocType = llvm.ArrayType(allocType, elementCount)
|
||||
}
|
||||
alloc := llvm.AddGlobal(fr.Mod, allocType, fr.pkgName+"$alloc")
|
||||
alloc.SetInitializer(getZeroValue(allocType))
|
||||
alloc.SetInitializer(llvm.ConstNull(allocType))
|
||||
alloc.SetLinkage(llvm.InternalLinkage)
|
||||
result := &LocalValue{
|
||||
Underlying: alloc,
|
||||
@@ -312,10 +312,53 @@ func (fr *frame) evalBasicBlock(bb, incoming llvm.BasicBlock, indent string) (re
|
||||
stringType := fr.Mod.GetTypeByName("runtime._string")
|
||||
retPtr := llvm.ConstGEP(global, getLLVMIndices(fr.Mod.Context().Int32Type(), []uint32{0, 0}))
|
||||
retLen := llvm.ConstInt(stringType.StructElementTypes()[1], uint64(len(result)), false)
|
||||
ret := getZeroValue(stringType)
|
||||
ret := llvm.ConstNull(stringType)
|
||||
ret = llvm.ConstInsertValue(ret, retPtr, []uint32{0})
|
||||
ret = llvm.ConstInsertValue(ret, retLen, []uint32{1})
|
||||
fr.locals[inst] = &LocalValue{fr.Eval, ret}
|
||||
case callee.Name() == "runtime.sliceCopy":
|
||||
elementSize := fr.getLocal(inst.Operand(4)).(*LocalValue).Value().ZExtValue()
|
||||
dstArray := fr.getLocal(inst.Operand(0)).(*LocalValue).stripPointerCasts()
|
||||
srcArray := fr.getLocal(inst.Operand(1)).(*LocalValue).stripPointerCasts()
|
||||
dstLen := fr.getLocal(inst.Operand(2)).(*LocalValue)
|
||||
srcLen := fr.getLocal(inst.Operand(3)).(*LocalValue)
|
||||
if elementSize != 1 && dstArray.Type().ElementType().TypeKind() == llvm.ArrayTypeKind && srcArray.Type().ElementType().TypeKind() == llvm.ArrayTypeKind {
|
||||
// Slice data pointers are created by adding a global array
|
||||
// and getting the address of the first element using a GEP.
|
||||
// However, before the compiler can pass it to
|
||||
// runtime.sliceCopy, it has to perform a bitcast to a *i8,
|
||||
// to make it a unsafe.Pointer. Now, when the IR builder
|
||||
// sees a bitcast of a GEP with zero indices, it will make
|
||||
// a bitcast of the original array instead of the GEP,
|
||||
// which breaks our assumptions.
|
||||
// Re-add this GEP, in the hope that it it is then of the correct type...
|
||||
dstArray = dstArray.GetElementPtr([]uint32{0, 0}).(*LocalValue)
|
||||
srcArray = srcArray.GetElementPtr([]uint32{0, 0}).(*LocalValue)
|
||||
}
|
||||
if fr.Eval.TargetData.TypeAllocSize(dstArray.Type().ElementType()) != elementSize {
|
||||
return nil, nil, errors.New("interp: slice dst element size does not match pointer type")
|
||||
}
|
||||
if fr.Eval.TargetData.TypeAllocSize(srcArray.Type().ElementType()) != elementSize {
|
||||
return nil, nil, errors.New("interp: slice src element size does not match pointer type")
|
||||
}
|
||||
if dstArray.Type() != srcArray.Type() {
|
||||
return nil, nil, errors.New("interp: slice element types don't match")
|
||||
}
|
||||
length := dstLen.Value().SExtValue()
|
||||
if srcLength := srcLen.Value().SExtValue(); srcLength < length {
|
||||
length = srcLength
|
||||
}
|
||||
if length < 0 {
|
||||
return nil, nil, errors.New("interp: trying to copy a slice with negative length?")
|
||||
}
|
||||
for i := int64(0); i < length; i++ {
|
||||
// *dst = *src
|
||||
dstArray.Store(srcArray.Load())
|
||||
// dst++
|
||||
dstArray = dstArray.GetElementPtr([]uint32{1}).(*LocalValue)
|
||||
// src++
|
||||
srcArray = srcArray.GetElementPtr([]uint32{1}).(*LocalValue)
|
||||
}
|
||||
case callee.Name() == "runtime.stringToBytes":
|
||||
// convert a string to a []byte
|
||||
bufPtr := fr.getLocal(inst.Operand(0))
|
||||
@@ -335,7 +378,7 @@ func (fr *frame) evalBasicBlock(bb, incoming llvm.BasicBlock, indent string) (re
|
||||
sliceType := inst.Type()
|
||||
retPtr := llvm.ConstGEP(global, getLLVMIndices(fr.Mod.Context().Int32Type(), []uint32{0, 0}))
|
||||
retLen := llvm.ConstInt(sliceType.StructElementTypes()[1], uint64(len(result)), false)
|
||||
ret := getZeroValue(sliceType)
|
||||
ret := llvm.ConstNull(sliceType)
|
||||
ret = llvm.ConstInsertValue(ret, retPtr, []uint32{0}) // ptr
|
||||
ret = llvm.ConstInsertValue(ret, retLen, []uint32{1}) // len
|
||||
ret = llvm.ConstInsertValue(ret, retLen, []uint32{2}) // cap
|
||||
|
||||
+2
-2
@@ -24,7 +24,7 @@ type Eval struct {
|
||||
|
||||
// Run evaluates the function with the given name and then eliminates all
|
||||
// callers.
|
||||
func Run(mod llvm.Module, targetData llvm.TargetData, debug bool) error {
|
||||
func Run(mod llvm.Module, debug bool) error {
|
||||
if debug {
|
||||
println("\ncompile-time evaluation:")
|
||||
}
|
||||
@@ -32,7 +32,7 @@ func Run(mod llvm.Module, targetData llvm.TargetData, debug bool) error {
|
||||
name := "runtime.initAll"
|
||||
e := &Eval{
|
||||
Mod: mod,
|
||||
TargetData: targetData,
|
||||
TargetData: llvm.NewTargetData(mod.DataLayout()),
|
||||
Debug: debug,
|
||||
dirtyGlobals: map[llvm.Value]struct{}{},
|
||||
}
|
||||
|
||||
@@ -0,0 +1,100 @@
|
||||
package interp
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"tinygo.org/x/go-llvm"
|
||||
)
|
||||
|
||||
func TestInterp(t *testing.T) {
|
||||
for _, name := range []string{
|
||||
"basic",
|
||||
"slice-copy",
|
||||
} {
|
||||
name := name // make tc local to this closure
|
||||
t.Run(name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
runTest(t, "testdata/"+name)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func runTest(t *testing.T, pathPrefix string) {
|
||||
// Read the input IR.
|
||||
ctx := llvm.NewContext()
|
||||
buf, err := llvm.NewMemoryBufferFromFile(pathPrefix + ".ll")
|
||||
os.Stat(pathPrefix + ".ll") // make sure this file is tracked by `go test` caching
|
||||
if err != nil {
|
||||
t.Fatalf("could not read file %s: %v", pathPrefix+".ll", err)
|
||||
}
|
||||
mod, err := ctx.ParseIR(buf)
|
||||
if err != nil {
|
||||
t.Fatalf("could not load module:\n%v", err)
|
||||
}
|
||||
|
||||
// Perform the transform.
|
||||
err = Run(mod, false)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// Run some cleanup passes to get easy-to-read outputs.
|
||||
pm := llvm.NewPassManager()
|
||||
defer pm.Dispose()
|
||||
pm.AddGlobalOptimizerPass()
|
||||
pm.AddDeadStoreEliminationPass()
|
||||
pm.Run(mod)
|
||||
|
||||
// Read the expected output IR.
|
||||
out, err := ioutil.ReadFile(pathPrefix + ".out.ll")
|
||||
if err != nil {
|
||||
t.Fatalf("could not read output file %s: %v", pathPrefix+".out.ll", err)
|
||||
}
|
||||
|
||||
// See whether the transform output matches with the expected output IR.
|
||||
expected := string(out)
|
||||
actual := mod.String()
|
||||
if !fuzzyEqualIR(expected, actual) {
|
||||
t.Logf("output does not match expected output:\n%s", actual)
|
||||
t.Fail()
|
||||
}
|
||||
}
|
||||
|
||||
// fuzzyEqualIR returns true if the two LLVM IR strings passed in are roughly
|
||||
// equal. That means, only relevant lines are compared (excluding comments
|
||||
// etc.).
|
||||
func fuzzyEqualIR(s1, s2 string) bool {
|
||||
lines1 := filterIrrelevantIRLines(strings.Split(s1, "\n"))
|
||||
lines2 := filterIrrelevantIRLines(strings.Split(s2, "\n"))
|
||||
if len(lines1) != len(lines2) {
|
||||
return false
|
||||
}
|
||||
for i, line := range lines1 {
|
||||
if line != lines2[i] {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
// filterIrrelevantIRLines removes lines from the input slice of strings that
|
||||
// are not relevant in comparing IR. For example, empty lines and comments are
|
||||
// stripped out.
|
||||
func filterIrrelevantIRLines(lines []string) []string {
|
||||
var out []string
|
||||
for _, line := range lines {
|
||||
line = strings.TrimSpace(line) // drop '\r' on Windows
|
||||
if line == "" || line[0] == ';' {
|
||||
continue
|
||||
}
|
||||
if strings.HasPrefix(line, "source_filename = ") {
|
||||
continue
|
||||
}
|
||||
out = append(out, line)
|
||||
}
|
||||
return out
|
||||
}
|
||||
@@ -35,6 +35,8 @@ func (e *Eval) hasSideEffects(fn llvm.Value) *sideEffectResult {
|
||||
return &sideEffectResult{severity: sideEffectLimited}
|
||||
case "runtime.interfaceImplements":
|
||||
return &sideEffectResult{severity: sideEffectNone}
|
||||
case "runtime.sliceCopy":
|
||||
return &sideEffectResult{severity: sideEffectNone}
|
||||
case "runtime.trackPointer":
|
||||
return &sideEffectResult{severity: sideEffectNone}
|
||||
case "llvm.dbg.value":
|
||||
|
||||
Vendored
+42
@@ -0,0 +1,42 @@
|
||||
target datalayout = "e-m:e-i64:64-f80:128-n8:16:32:64-S128"
|
||||
target triple = "x86_64--linux"
|
||||
|
||||
@main.v1 = internal global i64 0
|
||||
|
||||
declare void @runtime.printint64(i64) unnamed_addr
|
||||
|
||||
declare void @runtime.printnl() unnamed_addr
|
||||
|
||||
define void @runtime.initAll() unnamed_addr {
|
||||
entry:
|
||||
call void @runtime.init()
|
||||
call void @main.init()
|
||||
ret void
|
||||
}
|
||||
|
||||
define void @main() unnamed_addr {
|
||||
entry:
|
||||
%0 = load i64, i64* @main.v1
|
||||
call void @runtime.printint64(i64 %0)
|
||||
call void @runtime.printnl()
|
||||
ret void
|
||||
}
|
||||
|
||||
define internal void @runtime.init() unnamed_addr {
|
||||
entry:
|
||||
ret void
|
||||
}
|
||||
|
||||
define internal void @main.init() unnamed_addr {
|
||||
entry:
|
||||
store i64 3, i64* @main.v1
|
||||
call void @"main.init#1"()
|
||||
ret void
|
||||
}
|
||||
|
||||
define internal void @"main.init#1"() unnamed_addr {
|
||||
entry:
|
||||
call void @runtime.printint64(i64 5)
|
||||
call void @runtime.printnl()
|
||||
ret void
|
||||
}
|
||||
Vendored
+20
@@ -0,0 +1,20 @@
|
||||
target datalayout = "e-m:e-i64:64-f80:128-n8:16:32:64-S128"
|
||||
target triple = "x86_64--linux"
|
||||
|
||||
declare void @runtime.printint64(i64) unnamed_addr
|
||||
|
||||
declare void @runtime.printnl() unnamed_addr
|
||||
|
||||
define void @runtime.initAll() unnamed_addr {
|
||||
entry:
|
||||
call void @runtime.printint64(i64 5)
|
||||
call void @runtime.printnl()
|
||||
ret void
|
||||
}
|
||||
|
||||
define void @main() unnamed_addr {
|
||||
entry:
|
||||
call void @runtime.printint64(i64 3)
|
||||
call void @runtime.printnl()
|
||||
ret void
|
||||
}
|
||||
Vendored
+86
@@ -0,0 +1,86 @@
|
||||
target datalayout = "e-m:e-i64:64-f80:128-n8:16:32:64-S128"
|
||||
target triple = "x86_64--linux"
|
||||
|
||||
@main.uint8SliceSrc.buf = internal global [2 x i8] c"\03d"
|
||||
@main.uint8SliceSrc = internal unnamed_addr global { i8*, i64, i64 } { i8* getelementptr inbounds ([2 x i8], [2 x i8]* @main.uint8SliceSrc.buf, i32 0, i32 0), i64 2, i64 2 }
|
||||
@main.uint8SliceDst = internal unnamed_addr global { i8*, i64, i64 } zeroinitializer
|
||||
@main.int16SliceSrc.buf = internal global [3 x i16] [i16 5, i16 123, i16 1024]
|
||||
@main.int16SliceSrc = internal unnamed_addr global { i16*, i64, i64 } { i16* getelementptr inbounds ([3 x i16], [3 x i16]* @main.int16SliceSrc.buf, i32 0, i32 0), i64 3, i64 3 }
|
||||
@main.int16SliceDst = internal unnamed_addr global { i16*, i64, i64 } zeroinitializer
|
||||
|
||||
declare i64 @runtime.sliceCopy(i8* %dst, i8* %src, i64 %dstLen, i64 %srcLen, i64 %elemSize) unnamed_addr
|
||||
|
||||
declare i8* @runtime.alloc(i64) unnamed_addr
|
||||
|
||||
declare void @runtime.printuint8(i8)
|
||||
|
||||
declare void @runtime.printint16(i16)
|
||||
|
||||
define void @runtime.initAll() unnamed_addr {
|
||||
entry:
|
||||
call void @main.init()
|
||||
ret void
|
||||
}
|
||||
|
||||
define void @main() unnamed_addr {
|
||||
entry:
|
||||
; print(uintSliceSrc[0])
|
||||
%uint8SliceSrc.buf = load i8*, i8** getelementptr inbounds ({ i8*, i64, i64 }, { i8*, i64, i64 }* @main.uint8SliceSrc, i64 0, i32 0)
|
||||
%uint8SliceSrc.val = load i8, i8* %uint8SliceSrc.buf
|
||||
call void @runtime.printuint8(i8 %uint8SliceSrc.val)
|
||||
|
||||
; print(uintSliceDst[0])
|
||||
%uint8SliceDst.buf = load i8*, i8** getelementptr inbounds ({ i8*, i64, i64 }, { i8*, i64, i64 }* @main.uint8SliceDst, i64 0, i32 0)
|
||||
%uint8SliceDst.val = load i8, i8* %uint8SliceDst.buf
|
||||
call void @runtime.printuint8(i8 %uint8SliceDst.val)
|
||||
|
||||
; print(int16SliceSrc[0])
|
||||
%int16SliceSrc.buf = load i16*, i16** getelementptr inbounds ({ i16*, i64, i64 }, { i16*, i64, i64 }* @main.int16SliceSrc, i64 0, i32 0)
|
||||
%int16SliceSrc.val = load i16, i16* %int16SliceSrc.buf
|
||||
call void @runtime.printint16(i16 %int16SliceSrc.val)
|
||||
|
||||
; print(int16SliceDst[0])
|
||||
%int16SliceDst.buf = load i16*, i16** getelementptr inbounds ({ i16*, i64, i64 }, { i16*, i64, i64 }* @main.int16SliceDst, i64 0, i32 0)
|
||||
%int16SliceDst.val = load i16, i16* %int16SliceDst.buf
|
||||
call void @runtime.printint16(i16 %int16SliceDst.val)
|
||||
ret void
|
||||
}
|
||||
|
||||
define internal void @main.init() unnamed_addr {
|
||||
entry:
|
||||
; equivalent of:
|
||||
; uint8SliceDst = make([]uint8, len(uint8SliceSrc))
|
||||
%uint8SliceSrc = load { i8*, i64, i64 }, { i8*, i64, i64 }* @main.uint8SliceSrc
|
||||
%uint8SliceSrc.len = extractvalue { i8*, i64, i64 } %uint8SliceSrc, 1
|
||||
%uint8SliceDst.buf = call i8* @runtime.alloc(i64 %uint8SliceSrc.len)
|
||||
%0 = insertvalue { i8*, i64, i64 } undef, i8* %uint8SliceDst.buf, 0
|
||||
%1 = insertvalue { i8*, i64, i64 } %0, i64 %uint8SliceSrc.len, 1
|
||||
%2 = insertvalue { i8*, i64, i64 } %1, i64 %uint8SliceSrc.len, 2
|
||||
store { i8*, i64, i64 } %2, { i8*, i64, i64 }* @main.uint8SliceDst
|
||||
|
||||
; equivalent of:
|
||||
; copy(uint8SliceDst, uint8SliceSrc)
|
||||
%uint8SliceSrc.buf = extractvalue { i8*, i64, i64 } %uint8SliceSrc, 0
|
||||
%copy.n = call i64 @runtime.sliceCopy(i8* %uint8SliceDst.buf, i8* %uint8SliceSrc.buf, i64 %uint8SliceSrc.len, i64 %uint8SliceSrc.len, i64 1)
|
||||
|
||||
; equivalent of:
|
||||
; int16SliceDst = make([]int16, len(int16SliceSrc))
|
||||
%int16SliceSrc = load { i16*, i64, i64 }, { i16*, i64, i64 }* @main.int16SliceSrc
|
||||
%int16SliceSrc.len = extractvalue { i16*, i64, i64 } %int16SliceSrc, 1
|
||||
%int16SliceSrc.len.bytes = mul i64 %int16SliceSrc.len, 2
|
||||
%int16SliceDst.buf.raw = call i8* @runtime.alloc(i64 %int16SliceSrc.len.bytes)
|
||||
%int16SliceDst.buf = bitcast i8* %int16SliceDst.buf.raw to i16*
|
||||
%3 = insertvalue { i16*, i64, i64 } undef, i16* %int16SliceDst.buf, 0
|
||||
%4 = insertvalue { i16*, i64, i64 } %3, i64 %int16SliceSrc.len, 1
|
||||
%5 = insertvalue { i16*, i64, i64 } %4, i64 %int16SliceSrc.len, 2
|
||||
store { i16*, i64, i64 } %5, { i16*, i64, i64 }* @main.int16SliceDst
|
||||
|
||||
; equivalent of:
|
||||
; copy(int16SliceDst, int16SliceSrc)
|
||||
%int16SliceSrc.buf = extractvalue { i16*, i64, i64 } %int16SliceSrc, 0
|
||||
%int16SliceSrc.buf.i8ptr = bitcast i16* %int16SliceSrc.buf to i8*
|
||||
%int16SliceDst.buf.i8ptr = bitcast i16* %int16SliceDst.buf to i8*
|
||||
%copy.n2 = call i64 @runtime.sliceCopy(i8* %int16SliceDst.buf.i8ptr, i8* %int16SliceSrc.buf.i8ptr, i64 %int16SliceSrc.len, i64 %int16SliceSrc.len, i64 2)
|
||||
|
||||
ret void
|
||||
}
|
||||
Vendored
+20
@@ -0,0 +1,20 @@
|
||||
target datalayout = "e-m:e-i64:64-f80:128-n8:16:32:64-S128"
|
||||
target triple = "x86_64--linux"
|
||||
|
||||
declare void @runtime.printuint8(i8) local_unnamed_addr
|
||||
|
||||
declare void @runtime.printint16(i16) local_unnamed_addr
|
||||
|
||||
define void @runtime.initAll() unnamed_addr {
|
||||
entry:
|
||||
ret void
|
||||
}
|
||||
|
||||
define void @main() unnamed_addr {
|
||||
entry:
|
||||
call void @runtime.printuint8(i8 3)
|
||||
call void @runtime.printuint8(i8 3)
|
||||
call void @runtime.printint16(i16 5)
|
||||
call void @runtime.printint16(i16 5)
|
||||
ret void
|
||||
}
|
||||
@@ -16,50 +16,6 @@ func getUses(value llvm.Value) []llvm.Value {
|
||||
return uses
|
||||
}
|
||||
|
||||
// Return a zero LLVM value for any LLVM type. Setting this value as an
|
||||
// initializer has the same effect as setting 'zeroinitializer' on a value.
|
||||
// Sadly, I haven't found a way to do it directly with the Go API but this works
|
||||
// just fine.
|
||||
func getZeroValue(typ llvm.Type) llvm.Value {
|
||||
switch typ.TypeKind() {
|
||||
case llvm.ArrayTypeKind:
|
||||
subTyp := typ.ElementType()
|
||||
subVal := getZeroValue(subTyp)
|
||||
vals := make([]llvm.Value, typ.ArrayLength())
|
||||
for i := range vals {
|
||||
vals[i] = subVal
|
||||
}
|
||||
return llvm.ConstArray(subTyp, vals)
|
||||
case llvm.FloatTypeKind, llvm.DoubleTypeKind:
|
||||
return llvm.ConstFloat(typ, 0.0)
|
||||
case llvm.IntegerTypeKind:
|
||||
return llvm.ConstInt(typ, 0, false)
|
||||
case llvm.PointerTypeKind:
|
||||
return llvm.ConstPointerNull(typ)
|
||||
case llvm.StructTypeKind:
|
||||
types := typ.StructElementTypes()
|
||||
vals := make([]llvm.Value, len(types))
|
||||
for i, subTyp := range types {
|
||||
val := getZeroValue(subTyp)
|
||||
vals[i] = val
|
||||
}
|
||||
if typ.StructName() != "" {
|
||||
return llvm.ConstNamedStruct(typ, vals)
|
||||
} else {
|
||||
return typ.Context().ConstStruct(vals, false)
|
||||
}
|
||||
case llvm.VectorTypeKind:
|
||||
zero := getZeroValue(typ.ElementType())
|
||||
vals := make([]llvm.Value, typ.VectorSize())
|
||||
for i := range vals {
|
||||
vals[i] = zero
|
||||
}
|
||||
return llvm.ConstVector(vals, false)
|
||||
default:
|
||||
panic("interp: unknown LLVM type: " + typ.String())
|
||||
}
|
||||
}
|
||||
|
||||
// getStringBytes loads the byte slice of a Go string represented as a
|
||||
// {ptr, len} pair.
|
||||
func getStringBytes(strPtr Value, strLen llvm.Value) []byte {
|
||||
|
||||
+29
-9
@@ -98,13 +98,33 @@ func (v *LocalValue) GetElementPtr(indices []uint32) Value {
|
||||
gep := llvm.ConstGEP(v.Underlying, getLLVMIndices(int32Type, indices))
|
||||
return &LocalValue{v.Eval, gep}
|
||||
}
|
||||
switch v.Underlying.Opcode() {
|
||||
case llvm.GetElementPtr, llvm.IntToPtr:
|
||||
int32Type := v.Underlying.Type().Context().Int32Type()
|
||||
llvmIndices := getLLVMIndices(int32Type, indices)
|
||||
return &LocalValue{v.Eval, llvm.ConstGEP(v.Underlying, llvmIndices)}
|
||||
default:
|
||||
panic("interp: GEP on a constant")
|
||||
if !v.Underlying.IsAConstantExpr().IsNil() {
|
||||
switch v.Underlying.Opcode() {
|
||||
case llvm.GetElementPtr, llvm.IntToPtr, llvm.BitCast:
|
||||
int32Type := v.Underlying.Type().Context().Int32Type()
|
||||
llvmIndices := getLLVMIndices(int32Type, indices)
|
||||
return &LocalValue{v.Eval, llvm.ConstGEP(v.Underlying, llvmIndices)}
|
||||
}
|
||||
}
|
||||
panic("interp: unknown GEP")
|
||||
}
|
||||
|
||||
// stripPointerCasts removes all const bitcasts from pointer values, if there
|
||||
// are any.
|
||||
func (v *LocalValue) stripPointerCasts() *LocalValue {
|
||||
value := v.Underlying
|
||||
for {
|
||||
if !value.IsAConstantExpr().IsNil() {
|
||||
switch value.Opcode() {
|
||||
case llvm.BitCast:
|
||||
value = value.Operand(0)
|
||||
continue
|
||||
}
|
||||
}
|
||||
return &LocalValue{
|
||||
Eval: v.Eval,
|
||||
Underlying: value,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -162,7 +182,7 @@ func (v *MapValue) newBucket() llvm.Value {
|
||||
llvm.ArrayType(v.KeyType, 8), // key type
|
||||
llvm.ArrayType(v.ValueType, 8), // value type
|
||||
}, false)
|
||||
bucketValue := getZeroValue(bucketType)
|
||||
bucketValue := llvm.ConstNull(bucketType)
|
||||
bucket := llvm.AddGlobal(v.Eval.Mod, bucketType, v.PkgName+"$mapbucket")
|
||||
bucket.SetInitializer(bucketValue)
|
||||
bucket.SetLinkage(llvm.InternalLinkage)
|
||||
@@ -311,7 +331,7 @@ func (v *MapValue) PutString(keyBuf, keyLen, valPtr *LocalValue) {
|
||||
|
||||
keyType := v.Eval.Mod.GetTypeByName("runtime._string")
|
||||
v.KeyType = keyType
|
||||
key := getZeroValue(keyType)
|
||||
key := llvm.ConstNull(keyType)
|
||||
key = llvm.ConstInsertValue(key, keyBuf.Value(), []uint32{0})
|
||||
key = llvm.ConstInsertValue(key, keyLen.Value(), []uint32{1})
|
||||
|
||||
|
||||
@@ -28,6 +28,7 @@ type Program struct {
|
||||
type Function struct {
|
||||
*ssa.Function
|
||||
LLVMFn llvm.Value
|
||||
module string // go:wasm-module
|
||||
linkName string // go:linkname, go:export, go:interrupt
|
||||
exported bool // go:export
|
||||
nobounds bool // go:nobounds
|
||||
@@ -177,6 +178,9 @@ func (p *Program) AddPackage(pkg *ssa.Package) {
|
||||
}
|
||||
|
||||
func (p *Program) addFunction(ssaFn *ssa.Function) {
|
||||
if _, ok := p.functionMap[ssaFn]; ok {
|
||||
return
|
||||
}
|
||||
f := &Function{Function: ssaFn}
|
||||
f.parsePragmas()
|
||||
p.Functions = append(p.Functions, f)
|
||||
@@ -229,6 +233,12 @@ func (f *Function) parsePragmas() {
|
||||
}
|
||||
f.linkName = parts[1]
|
||||
f.exported = true
|
||||
case "//go:wasm-module":
|
||||
// Alternative comment for setting the import module.
|
||||
if len(parts) != 2 {
|
||||
continue
|
||||
}
|
||||
f.module = parts[1]
|
||||
case "//go:inline":
|
||||
f.inline = InlineHint
|
||||
case "//go:noinline":
|
||||
@@ -291,6 +301,11 @@ func (f *Function) Inline() InlineType {
|
||||
return f.inline
|
||||
}
|
||||
|
||||
// Return the module name if not the default.
|
||||
func (f *Function) Module() string {
|
||||
return f.module
|
||||
}
|
||||
|
||||
// Return the link name for this function.
|
||||
func (f *Function) LinkName() string {
|
||||
if f.linkName != "" {
|
||||
|
||||
+1
-1
Submodule lib/cmsis-svd updated: 5910f3d115...7d9b416701
+3
-1
@@ -9,6 +9,8 @@ import (
|
||||
"os"
|
||||
"os/exec"
|
||||
"unsafe"
|
||||
|
||||
"github.com/tinygo-org/tinygo/goenv"
|
||||
)
|
||||
|
||||
/*
|
||||
@@ -63,7 +65,7 @@ func Link(linker string, flags ...string) error {
|
||||
cmd := exec.Command(linker, flags...)
|
||||
cmd.Stdout = os.Stdout
|
||||
cmd.Stderr = os.Stderr
|
||||
cmd.Dir = sourceDir()
|
||||
cmd.Dir = goenv.Get("TINYGOROOT")
|
||||
return cmd.Run()
|
||||
}
|
||||
}
|
||||
|
||||
+3
-1
@@ -8,6 +8,8 @@ package main
|
||||
import (
|
||||
"os"
|
||||
"os/exec"
|
||||
|
||||
"github.com/tinygo-org/tinygo/goenv"
|
||||
)
|
||||
|
||||
// Link invokes a linker with the given name and arguments.
|
||||
@@ -20,6 +22,6 @@ func Link(linker string, flags ...string) error {
|
||||
cmd := exec.Command(linker, flags...)
|
||||
cmd.Stdout = os.Stdout
|
||||
cmd.Stderr = os.Stderr
|
||||
cmd.Dir = sourceDir()
|
||||
cmd.Dir = goenv.Get("TINYGOROOT")
|
||||
return cmd.Run()
|
||||
}
|
||||
|
||||
+3
-3
@@ -317,9 +317,9 @@ func (p *Program) parseFile(path string, mode parser.Mode) (*ast.File, error) {
|
||||
defer rd.Close()
|
||||
relpath := path
|
||||
if filepath.IsAbs(path) {
|
||||
relpath, err = filepath.Rel(p.Dir, path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
rp, err := filepath.Rel(p.Dir, path)
|
||||
if err == nil {
|
||||
relpath = rp
|
||||
}
|
||||
}
|
||||
return parser.ParseFile(p.fset, relpath, rd, mode)
|
||||
|
||||
@@ -15,10 +15,14 @@ import (
|
||||
"strconv"
|
||||
"strings"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"github.com/tinygo-org/tinygo/compiler"
|
||||
"github.com/tinygo-org/tinygo/goenv"
|
||||
"github.com/tinygo-org/tinygo/interp"
|
||||
"github.com/tinygo-org/tinygo/loader"
|
||||
|
||||
serial "go.bug.st/serial.v1"
|
||||
)
|
||||
|
||||
// commandError is an error type to wrap os/exec.Command errors. This provides
|
||||
@@ -47,8 +51,10 @@ type BuildConfig struct {
|
||||
opt string
|
||||
gc string
|
||||
panicStrategy string
|
||||
scheduler string
|
||||
printIR bool
|
||||
dumpSSA bool
|
||||
verifyIR bool
|
||||
debug bool
|
||||
printSizes string
|
||||
cFlags []string
|
||||
@@ -65,7 +71,7 @@ func Compile(pkgName, outpath string, spec *TargetSpec, config *BuildConfig, act
|
||||
config.gc = spec.GC
|
||||
}
|
||||
|
||||
root := sourceDir()
|
||||
root := goenv.Get("TINYGOROOT")
|
||||
|
||||
// Merge and adjust CFlags.
|
||||
cflags := append([]string{}, config.cFlags...)
|
||||
@@ -79,7 +85,7 @@ func Compile(pkgName, outpath string, spec *TargetSpec, config *BuildConfig, act
|
||||
ldflags = append(ldflags, strings.Replace(flag, "{root}", root, -1))
|
||||
}
|
||||
|
||||
goroot := getGoroot()
|
||||
goroot := goenv.Get("GOROOT")
|
||||
if goroot == "" {
|
||||
return errors.New("cannot locate $GOROOT, please set it manually")
|
||||
}
|
||||
@@ -88,8 +94,8 @@ func Compile(pkgName, outpath string, spec *TargetSpec, config *BuildConfig, act
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not read version from GOROOT (%v): %v", goroot, err)
|
||||
}
|
||||
if major != 1 {
|
||||
return fmt.Errorf("expected major version 1, got go%d.%d", major, minor)
|
||||
if major != 1 || (minor != 11 && minor != 12 && minor != 13) {
|
||||
return fmt.Errorf("requires go version 1.11, 1.12, or 1.13, got go%d.%d", major, minor)
|
||||
}
|
||||
for i := 1; i <= minor; i++ {
|
||||
tags = append(tags, fmt.Sprintf("go1.%d", i))
|
||||
@@ -97,6 +103,10 @@ func Compile(pkgName, outpath string, spec *TargetSpec, config *BuildConfig, act
|
||||
if extraTags := strings.Fields(config.tags); len(extraTags) != 0 {
|
||||
tags = append(tags, extraTags...)
|
||||
}
|
||||
scheduler := spec.Scheduler
|
||||
if config.scheduler != "" {
|
||||
scheduler = config.scheduler
|
||||
}
|
||||
compilerConfig := compiler.Config{
|
||||
Triple: spec.Triple,
|
||||
CPU: spec.CPU,
|
||||
@@ -105,14 +115,16 @@ func Compile(pkgName, outpath string, spec *TargetSpec, config *BuildConfig, act
|
||||
GOARCH: spec.GOARCH,
|
||||
GC: config.gc,
|
||||
PanicStrategy: config.panicStrategy,
|
||||
Scheduler: scheduler,
|
||||
CFlags: cflags,
|
||||
LDFlags: ldflags,
|
||||
ClangHeaders: getClangHeaderPath(root),
|
||||
Debug: config.debug,
|
||||
DumpSSA: config.dumpSSA,
|
||||
VerifyIR: config.verifyIR,
|
||||
TINYGOROOT: root,
|
||||
GOROOT: goroot,
|
||||
GOPATH: getGopath(),
|
||||
GOPATH: goenv.Get("GOPATH"),
|
||||
BuildTags: tags,
|
||||
TestConfig: config.testConfig,
|
||||
}
|
||||
@@ -137,7 +149,7 @@ func Compile(pkgName, outpath string, spec *TargetSpec, config *BuildConfig, act
|
||||
return errors.New("verification error after IR construction")
|
||||
}
|
||||
|
||||
err = interp.Run(c.Module(), c.TargetData(), config.dumpSSA)
|
||||
err = interp.Run(c.Module(), config.dumpSSA)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -148,9 +160,6 @@ func Compile(pkgName, outpath string, spec *TargetSpec, config *BuildConfig, act
|
||||
if spec.GOOS != "darwin" {
|
||||
c.ApplyFunctionSections() // -ffunction-sections
|
||||
}
|
||||
if err := c.Verify(); err != nil {
|
||||
return errors.New("verification error after applying function sections")
|
||||
}
|
||||
|
||||
// Browsers cannot handle external functions that have type i64 because it
|
||||
// cannot be represented exactly in JavaScript (JS only has doubles). To
|
||||
@@ -162,9 +171,6 @@ func Compile(pkgName, outpath string, spec *TargetSpec, config *BuildConfig, act
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := c.Verify(); err != nil {
|
||||
return errors.New("verification error after running the wasm i64 hack")
|
||||
}
|
||||
}
|
||||
|
||||
// Optimization levels here are roughly the same as Clang, but probably not
|
||||
@@ -310,7 +316,7 @@ func Compile(pkgName, outpath string, spec *TargetSpec, config *BuildConfig, act
|
||||
}
|
||||
|
||||
// Get an Intel .hex file or .bin file from the .elf file.
|
||||
if outext == ".hex" || outext == ".bin" {
|
||||
if outext == ".hex" || outext == ".bin" || outext == ".gba" {
|
||||
tmppath = filepath.Join(dir, "main"+outext)
|
||||
err := Objcopy(executable, tmppath)
|
||||
if err != nil {
|
||||
@@ -398,40 +404,97 @@ func Flash(pkgName, target, port string, config *BuildConfig) error {
|
||||
// determine the type of file to compile
|
||||
var fileExt string
|
||||
|
||||
switch {
|
||||
case strings.Contains(spec.Flasher, "{hex}"):
|
||||
switch spec.FlashMethod {
|
||||
case "command", "":
|
||||
switch {
|
||||
case strings.Contains(spec.FlashCommand, "{hex}"):
|
||||
fileExt = ".hex"
|
||||
case strings.Contains(spec.FlashCommand, "{elf}"):
|
||||
fileExt = ".elf"
|
||||
case strings.Contains(spec.FlashCommand, "{bin}"):
|
||||
fileExt = ".bin"
|
||||
case strings.Contains(spec.FlashCommand, "{uf2}"):
|
||||
fileExt = ".uf2"
|
||||
default:
|
||||
return errors.New("invalid target file - did you forget the {hex} token in the 'flash-command' section?")
|
||||
}
|
||||
case "msd":
|
||||
if spec.FlashFilename == "" {
|
||||
return errors.New("invalid target file: flash-method was set to \"msd\" but no msd-firmware-name was set")
|
||||
}
|
||||
fileExt = filepath.Ext(spec.FlashFilename)
|
||||
case "openocd":
|
||||
fileExt = ".hex"
|
||||
case strings.Contains(spec.Flasher, "{elf}"):
|
||||
fileExt = ".elf"
|
||||
case strings.Contains(spec.Flasher, "{bin}"):
|
||||
fileExt = ".bin"
|
||||
case strings.Contains(spec.Flasher, "{uf2}"):
|
||||
fileExt = ".uf2"
|
||||
case "native":
|
||||
return errors.New("unknown flash method \"native\" - did you miss a -target flag?")
|
||||
default:
|
||||
return errors.New("invalid target file - did you forget the {hex} token in the 'flash' section?")
|
||||
return errors.New("unknown flash method: " + spec.FlashMethod)
|
||||
}
|
||||
|
||||
return Compile(pkgName, fileExt, spec, config, func(tmppath string) error {
|
||||
if spec.Flasher == "" {
|
||||
return errors.New("no flash command specified - did you miss a -target flag?")
|
||||
// do we need port reset to put MCU into bootloader mode?
|
||||
if spec.PortReset == "true" {
|
||||
err := touchSerialPortAt1200bps(port)
|
||||
if err != nil {
|
||||
return &commandError{"failed to reset port", tmppath, err}
|
||||
}
|
||||
// give the target MCU a chance to restart into bootloader
|
||||
time.Sleep(3 * time.Second)
|
||||
}
|
||||
|
||||
// Create the command.
|
||||
flashCmd := spec.Flasher
|
||||
fileToken := "{" + fileExt[1:] + "}"
|
||||
flashCmd = strings.Replace(flashCmd, fileToken, tmppath, -1)
|
||||
flashCmd = strings.Replace(flashCmd, "{port}", port, -1)
|
||||
// this flashing method copies the binary data to a Mass Storage Device (msd)
|
||||
switch spec.FlashMethod {
|
||||
case "", "command":
|
||||
// Create the command.
|
||||
flashCmd := spec.FlashCommand
|
||||
fileToken := "{" + fileExt[1:] + "}"
|
||||
flashCmd = strings.Replace(flashCmd, fileToken, tmppath, -1)
|
||||
flashCmd = strings.Replace(flashCmd, "{port}", port, -1)
|
||||
|
||||
// Execute the command.
|
||||
cmd := exec.Command("/bin/sh", "-c", flashCmd)
|
||||
cmd.Stdout = os.Stdout
|
||||
cmd.Stderr = os.Stderr
|
||||
cmd.Dir = sourceDir()
|
||||
err := cmd.Run()
|
||||
if err != nil {
|
||||
return &commandError{"failed to flash", tmppath, err}
|
||||
// Execute the command.
|
||||
cmd := exec.Command("/bin/sh", "-c", flashCmd)
|
||||
cmd.Stdout = os.Stdout
|
||||
cmd.Stderr = os.Stderr
|
||||
cmd.Dir = goenv.Get("TINYGOROOT")
|
||||
err := cmd.Run()
|
||||
if err != nil {
|
||||
return &commandError{"failed to flash", tmppath, err}
|
||||
}
|
||||
return nil
|
||||
case "msd":
|
||||
switch fileExt {
|
||||
case ".uf2":
|
||||
err := flashUF2UsingMSD(spec.FlashVolume, tmppath)
|
||||
if err != nil {
|
||||
return &commandError{"failed to flash", tmppath, err}
|
||||
}
|
||||
return nil
|
||||
case ".hex":
|
||||
err := flashHexUsingMSD(spec.FlashVolume, tmppath)
|
||||
if err != nil {
|
||||
return &commandError{"failed to flash", tmppath, err}
|
||||
}
|
||||
return nil
|
||||
default:
|
||||
return errors.New("mass storage device flashing currently only supports uf2 and hex")
|
||||
}
|
||||
case "openocd":
|
||||
args, err := spec.OpenOCDConfiguration()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
args = append(args, "-c", "program "+tmppath+" reset exit")
|
||||
cmd := exec.Command("openocd", args...)
|
||||
cmd.Stdout = os.Stdout
|
||||
cmd.Stderr = os.Stderr
|
||||
err = cmd.Run()
|
||||
if err != nil {
|
||||
return &commandError{"failed to flash", tmppath, err}
|
||||
}
|
||||
return nil
|
||||
default:
|
||||
return fmt.Errorf("unknown flash method: %s", spec.FlashMethod)
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
@@ -450,9 +513,33 @@ func FlashGDB(pkgName, target, port string, ocdOutput bool, config *BuildConfig)
|
||||
}
|
||||
|
||||
return Compile(pkgName, "", spec, config, func(tmppath string) error {
|
||||
if len(spec.OCDDaemon) != 0 {
|
||||
// Find a good way to run GDB.
|
||||
gdbInterface := spec.FlashMethod
|
||||
switch gdbInterface {
|
||||
case "msd", "command", "":
|
||||
if gdbInterface == "" {
|
||||
gdbInterface = "command"
|
||||
}
|
||||
if spec.OpenOCDInterface != "" && spec.OpenOCDTarget != "" {
|
||||
gdbInterface = "openocd"
|
||||
}
|
||||
}
|
||||
|
||||
// Run the GDB server, if necessary.
|
||||
var gdbCommands []string
|
||||
switch gdbInterface {
|
||||
case "native":
|
||||
// Run GDB directly.
|
||||
gdbCommands = append(gdbCommands, "run")
|
||||
case "openocd":
|
||||
gdbCommands = append(gdbCommands, "target remote :3333", "monitor halt", "load", "monitor reset", "c")
|
||||
|
||||
// We need a separate debugging daemon for on-chip debugging.
|
||||
daemon := exec.Command(spec.OCDDaemon[0], spec.OCDDaemon[1:]...)
|
||||
args, err := spec.OpenOCDConfiguration()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
daemon := exec.Command("openocd", args...)
|
||||
if ocdOutput {
|
||||
// Make it clear which output is from the daemon.
|
||||
w := &ColorWriter{
|
||||
@@ -465,11 +552,7 @@ func FlashGDB(pkgName, target, port string, ocdOutput bool, config *BuildConfig)
|
||||
}
|
||||
// Make sure the daemon doesn't receive Ctrl-C that is intended for
|
||||
// GDB (to break the currently executing program).
|
||||
// https://stackoverflow.com/a/35435038/559350
|
||||
daemon.SysProcAttr = &syscall.SysProcAttr{
|
||||
Setpgid: true,
|
||||
Pgid: 0,
|
||||
}
|
||||
setCommandAsDaemon(daemon)
|
||||
// Start now, and kill it on exit.
|
||||
daemon.Start()
|
||||
defer func() {
|
||||
@@ -477,6 +560,10 @@ func FlashGDB(pkgName, target, port string, ocdOutput bool, config *BuildConfig)
|
||||
// Maybe we should send a .Kill() after x seconds?
|
||||
daemon.Wait()
|
||||
}()
|
||||
case "msd":
|
||||
return errors.New("gdb is not supported for drag-and-drop programmable devices")
|
||||
default:
|
||||
return fmt.Errorf("gdb is not supported with interface %#v", gdbInterface)
|
||||
}
|
||||
|
||||
// Ignore Ctrl-C, it must be passed on to GDB.
|
||||
@@ -491,7 +578,7 @@ func FlashGDB(pkgName, target, port string, ocdOutput bool, config *BuildConfig)
|
||||
// By default: gdb -ex run <binary>
|
||||
// Exit GDB with Ctrl-D.
|
||||
params := []string{tmppath}
|
||||
for _, cmd := range spec.GDBCmds {
|
||||
for _, cmd := range gdbCommands {
|
||||
params = append(params, "-ex", cmd)
|
||||
}
|
||||
cmd := exec.Command(spec.GDB, params...)
|
||||
@@ -547,6 +634,54 @@ func Run(pkgName, target string, config *BuildConfig) error {
|
||||
})
|
||||
}
|
||||
|
||||
func touchSerialPortAt1200bps(port string) error {
|
||||
// Open port
|
||||
p, err := serial.Open(port, &serial.Mode{BaudRate: 1200})
|
||||
if err != nil {
|
||||
return fmt.Errorf("opening port: %s", err)
|
||||
}
|
||||
defer p.Close()
|
||||
|
||||
p.SetDTR(false)
|
||||
return nil
|
||||
}
|
||||
|
||||
func flashUF2UsingMSD(volume, tmppath string) error {
|
||||
// find standard UF2 info path
|
||||
infoPath := "/media/*/" + volume + "/INFO_UF2.TXT"
|
||||
if runtime.GOOS == "darwin" {
|
||||
infoPath = "/Volumes/" + volume + "/INFO_UF2.TXT"
|
||||
}
|
||||
|
||||
d, err := filepath.Glob(infoPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if d == nil {
|
||||
return errors.New("unable to locate UF2 device: " + volume)
|
||||
}
|
||||
|
||||
return moveFile(tmppath, filepath.Dir(d[0])+"/flash.uf2")
|
||||
}
|
||||
|
||||
func flashHexUsingMSD(volume, tmppath string) error {
|
||||
// find expected volume path
|
||||
destPath := "/media/*/" + volume
|
||||
if runtime.GOOS == "darwin" {
|
||||
destPath = "/Volumes/" + volume
|
||||
}
|
||||
|
||||
d, err := filepath.Glob(destPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if d == nil {
|
||||
return errors.New("unable to locate device: " + volume)
|
||||
}
|
||||
|
||||
return moveFile(tmppath, d[0]+"/flash.hex")
|
||||
}
|
||||
|
||||
// parseSize converts a human-readable size (with k/m/g suffix) into a plain
|
||||
// number.
|
||||
func parseSize(s string) (int64, error) {
|
||||
@@ -581,7 +716,8 @@ func usage() {
|
||||
fmt.Fprintln(os.Stderr, " test: test packages")
|
||||
fmt.Fprintln(os.Stderr, " flash: compile and flash to the device")
|
||||
fmt.Fprintln(os.Stderr, " gdb: run/flash and immediately enter GDB")
|
||||
fmt.Fprintln(os.Stderr, " clean: empty cache directory ("+cacheDir()+")")
|
||||
fmt.Fprintln(os.Stderr, " env: list environment variables used during build")
|
||||
fmt.Fprintln(os.Stderr, " clean: empty cache directory ("+goenv.Get("GOCACHE")+")")
|
||||
fmt.Fprintln(os.Stderr, " help: print this help text")
|
||||
fmt.Fprintln(os.Stderr, "\nflags:")
|
||||
flag.PrintDefaults()
|
||||
@@ -617,9 +753,11 @@ func main() {
|
||||
outpath := flag.String("o", "", "output filename")
|
||||
opt := flag.String("opt", "z", "optimization level: 0, 1, 2, s, z")
|
||||
gc := flag.String("gc", "", "garbage collector to use (none, leaking, conservative)")
|
||||
panicStrategy := flag.String("panic", "print", "panic strategy (abort, trap)")
|
||||
panicStrategy := flag.String("panic", "print", "panic strategy (print, trap)")
|
||||
scheduler := flag.String("scheduler", "", "which scheduler to use (coroutines, tasks)")
|
||||
printIR := flag.Bool("printir", false, "print LLVM IR")
|
||||
dumpSSA := flag.Bool("dumpssa", false, "dump internal Go SSA")
|
||||
verifyIR := flag.Bool("verifyir", false, "run extra verification steps on LLVM IR")
|
||||
tags := flag.String("tags", "", "a space-separated list of extra build tags")
|
||||
target := flag.String("target", "", "LLVM target | .json file with TargetSpec")
|
||||
printSize := flag.String("size", "", "print sizes (none, short, full)")
|
||||
@@ -643,8 +781,10 @@ func main() {
|
||||
opt: *opt,
|
||||
gc: *gc,
|
||||
panicStrategy: *panicStrategy,
|
||||
scheduler: *scheduler,
|
||||
printIR: *printIR,
|
||||
dumpSSA: *dumpSSA,
|
||||
verifyIR: *verifyIR,
|
||||
debug: !*nodebug,
|
||||
printSizes: *printSize,
|
||||
tags: *tags,
|
||||
@@ -748,8 +888,7 @@ func main() {
|
||||
handleCompilerError(err)
|
||||
case "clean":
|
||||
// remove cache directory
|
||||
dir := cacheDir()
|
||||
err := os.RemoveAll(dir)
|
||||
err := os.RemoveAll(goenv.Get("GOCACHE"))
|
||||
if err != nil {
|
||||
fmt.Fprintln(os.Stderr, "cannot clean cache:", err)
|
||||
os.Exit(1)
|
||||
@@ -757,7 +896,23 @@ func main() {
|
||||
case "help":
|
||||
usage()
|
||||
case "version":
|
||||
fmt.Printf("tinygo version %s %s/%s\n", version, runtime.GOOS, runtime.GOARCH)
|
||||
goversion := "<unknown>"
|
||||
if s, err := getGorootVersionString(goenv.Get("GOROOT")); err == nil {
|
||||
goversion = s
|
||||
}
|
||||
fmt.Printf("tinygo version %s %s/%s (using go version %s)\n", version, runtime.GOOS, runtime.GOARCH, goversion)
|
||||
case "env":
|
||||
if flag.NArg() == 0 {
|
||||
// Show all environment variables.
|
||||
for _, key := range goenv.Keys {
|
||||
fmt.Printf("%s=%#v\n", key, goenv.Get(key))
|
||||
}
|
||||
} else {
|
||||
// Show only one (or a few) environment variables.
|
||||
for i := 0; i < flag.NArg(); i++ {
|
||||
fmt.Println(goenv.Get(flag.Arg(i)))
|
||||
}
|
||||
}
|
||||
default:
|
||||
fmt.Fprintln(os.Stderr, "Unknown command:", command)
|
||||
usage()
|
||||
|
||||
+9
-5
@@ -45,11 +45,13 @@ func TestCompiler(t *testing.T) {
|
||||
}
|
||||
defer os.RemoveAll(tmpdir)
|
||||
|
||||
t.Log("running tests on host...")
|
||||
for _, path := range matches {
|
||||
t.Run(path, func(t *testing.T) {
|
||||
runTest(path, tmpdir, "", t)
|
||||
})
|
||||
if runtime.GOOS != "windows" {
|
||||
t.Log("running tests on host...")
|
||||
for _, path := range matches {
|
||||
t.Run(path, func(t *testing.T) {
|
||||
runTest(path, tmpdir, "", t)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
if testing.Short() {
|
||||
@@ -116,6 +118,7 @@ func runTest(path, tmpdir string, target string, t *testing.T) {
|
||||
opt: "z",
|
||||
printIR: false,
|
||||
dumpSSA: false,
|
||||
verifyIR: true,
|
||||
debug: false,
|
||||
printSizes: "",
|
||||
wasmAbi: "js",
|
||||
@@ -161,6 +164,7 @@ func runTest(path, tmpdir string, target string, t *testing.T) {
|
||||
|
||||
// putchar() prints CRLF, convert it to LF.
|
||||
actual := bytes.Replace(stdout.Bytes(), []byte{'\r', '\n'}, []byte{'\n'}, -1)
|
||||
expected = bytes.Replace(expected, []byte{'\r', '\n'}, []byte{'\n'}, -1) // for Windows
|
||||
|
||||
// Check whether the command ran successfully.
|
||||
fail := false
|
||||
|
||||
@@ -108,6 +108,10 @@ func Objcopy(infile, outfile string) error {
|
||||
|
||||
// Write to the file, in the correct format.
|
||||
switch filepath.Ext(outfile) {
|
||||
case ".gba":
|
||||
// The address is not stored in a .gba file.
|
||||
_, err := f.Write(data)
|
||||
return err
|
||||
case ".bin":
|
||||
// The address is not stored in a .bin file (therefore you
|
||||
// should use .hex files in most cases).
|
||||
|
||||
@@ -73,8 +73,32 @@ func SVCall4(num uintptr, a1, a2, a3, a4 interface{}) uintptr
|
||||
const (
|
||||
SCS_BASE = 0xE000E000
|
||||
NVIC_BASE = SCS_BASE + 0x0100
|
||||
SCB_BASE = SCS_BASE + 0x0D00
|
||||
)
|
||||
|
||||
const (
|
||||
SCB_AIRCR_VECTKEY_Pos = 16
|
||||
SCB_AIRCR_SYSRESETREQ_Pos = 2
|
||||
SCB_AIRCR_SYSRESETREQ_Msk = 1 << SCB_AIRCR_SYSRESETREQ_Pos
|
||||
)
|
||||
|
||||
// System Control Block (SCB)
|
||||
//
|
||||
// SCB_Type provides the definitions for the System Control Block Registers.
|
||||
type SCB_Type struct {
|
||||
CPUID volatile.Register32 // CPUID Base Register
|
||||
ICSR volatile.Register32 // Interrupt Control and State Register
|
||||
VTOR volatile.Register32 // Vector Table Offset Register
|
||||
AIRCR volatile.Register32 // Application Interrupt and Reset Control Register
|
||||
SCR volatile.Register32 // System Control Register
|
||||
CCR volatile.Register32 // Configuration Control Register
|
||||
_ volatile.Register32 // RESERVED1;
|
||||
SHP [2]volatile.Register32 // System Handlers Priority Registers. [0] is RESERVED
|
||||
SHCSR volatile.Register32 // System Handler Control and State Register
|
||||
}
|
||||
|
||||
var SCB = (*SCB_Type)(unsafe.Pointer(uintptr(SCB_BASE)))
|
||||
|
||||
// Nested Vectored Interrupt Controller (NVIC).
|
||||
//
|
||||
// Source:
|
||||
@@ -134,3 +158,14 @@ func DisableInterrupts() uintptr {
|
||||
func EnableInterrupts(mask uintptr) {
|
||||
Asm("cpsie if")
|
||||
}
|
||||
|
||||
// SystemReset performs a hard system reset.
|
||||
func SystemReset() {
|
||||
// SCB->AIRCR = ((0x5FA << SCB_AIRCR_VECTKEY_Pos) |
|
||||
// SCB_AIRCR_SYSRESETREQ_Msk);
|
||||
SCB.AIRCR.Set((0x5FA << SCB_AIRCR_VECTKEY_Pos) | SCB_AIRCR_SYSRESETREQ_Msk)
|
||||
|
||||
for {
|
||||
Asm("wfi")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
package main
|
||||
|
||||
// Draw a red square on the GameBoy Advance screen.
|
||||
|
||||
import (
|
||||
"image/color"
|
||||
"machine"
|
||||
)
|
||||
|
||||
var display = machine.Display
|
||||
|
||||
func main() {
|
||||
display.Configure()
|
||||
|
||||
for x := int16(30); x < 50; x++ {
|
||||
for y := int16(80); y < 100; y++ {
|
||||
display.SetPixel(x, y, color.RGBA{255, 0, 0, 255})
|
||||
}
|
||||
}
|
||||
display.Display()
|
||||
}
|
||||
@@ -1,3 +1,8 @@
|
||||
invoke: clean wasm_exec
|
||||
tinygo build -o ./html/wasm.wasm -target wasm -no-debug ./invoke/wasm.go
|
||||
cp ./invoke/wasm.js ./html/
|
||||
cp ./invoke/index.html ./html/
|
||||
|
||||
export: clean wasm_exec
|
||||
tinygo build -o ./html/wasm.wasm -target wasm -no-debug ./export/wasm.go
|
||||
cp ./export/wasm.js ./html/
|
||||
@@ -8,6 +13,12 @@ callback: clean wasm_exec
|
||||
cp ./callback/wasm.js ./html/
|
||||
cp ./callback/index.html ./html/
|
||||
|
||||
slices: clean wasm_exec
|
||||
tinygo build -o ./html/wasm.wasm -target wasm -no-debug ./slices/wasm.go
|
||||
cp ./slices/wasm.js ./html/
|
||||
cp ./slices/index.html ./html/
|
||||
|
||||
|
||||
main: clean wasm_exec
|
||||
tinygo build -o ./html/wasm.wasm -target wasm -no-debug ./main/main.go
|
||||
cp ./main/index.html ./html/
|
||||
|
||||
@@ -3,7 +3,9 @@
|
||||
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.
|
||||
[the export folder](./export) for an example of this. Additionally, the Wasm
|
||||
module (which has a default value of `env`) can be specified using
|
||||
`//go:wasm-module <module>`.
|
||||
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.
|
||||
@@ -34,7 +36,7 @@ $ make main
|
||||
Start the local web server:
|
||||
|
||||
```bash
|
||||
$ go run main.go
|
||||
$ go run server.go
|
||||
Serving ./html on http://localhost:8080
|
||||
```
|
||||
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
<!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>Edit on either side to mimic values, using WebAssembly:</p>
|
||||
<input type="text" id="a" value=""/>==<input type="text" id="b" value=""/>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
@@ -0,0 +1,15 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"syscall/js"
|
||||
)
|
||||
|
||||
func runner(this js.Value, args []js.Value) interface{} {
|
||||
return args[0].Invoke(args[1]).String()
|
||||
}
|
||||
|
||||
func main() {
|
||||
wait := make(chan struct{}, 0)
|
||||
js.Global().Set("runner", js.FuncOf(runner))
|
||||
<-wait
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
'use strict';
|
||||
|
||||
const WASM_URL = 'wasm.wasm';
|
||||
|
||||
var wasm;
|
||||
|
||||
function updateRight() {
|
||||
const value = document.getElementById("a").value;
|
||||
window.runner(function (value) {
|
||||
document.getElementById("b").value = value;
|
||||
}, value);
|
||||
}
|
||||
|
||||
function updateLeft() {
|
||||
const value = document.getElementById("b").value;
|
||||
window.runner(function (value) {
|
||||
document.getElementById("a").value = value;
|
||||
}, value);
|
||||
}
|
||||
|
||||
function init() {
|
||||
document.querySelector('#a').oninput = updateRight;
|
||||
document.querySelector('#b').oninput = updateLeft;
|
||||
|
||||
const go = new Go();
|
||||
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);
|
||||
})
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
init();
|
||||
@@ -0,0 +1,19 @@
|
||||
<!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>type values separated by comma, using WebAssembly:</p>
|
||||
<input type="text" id="a" value=""/>==<div id="b"></div>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
@@ -0,0 +1,23 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"syscall/js"
|
||||
)
|
||||
|
||||
func splitter(this js.Value, args []js.Value) interface{} {
|
||||
values := strings.Split(args[0].String(), ",")
|
||||
|
||||
result := make([]interface{}, 0)
|
||||
for _, each := range values {
|
||||
result = append(result, each)
|
||||
}
|
||||
|
||||
return js.ValueOf(result)
|
||||
}
|
||||
|
||||
func main() {
|
||||
wait := make(chan struct{}, 0)
|
||||
js.Global().Set("splitter", js.FuncOf(splitter))
|
||||
<-wait
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
'use strict';
|
||||
|
||||
const WASM_URL = 'wasm.wasm';
|
||||
|
||||
var wasm;
|
||||
|
||||
function update() {
|
||||
const value = document.getElementById("a").value;
|
||||
document.getElementById("b").innerHTML = JSON.stringify(window.splitter(value));
|
||||
}
|
||||
|
||||
function init() {
|
||||
document.querySelector('#a').oninput = update;
|
||||
|
||||
const go = new Go();
|
||||
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);
|
||||
})
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
init();
|
||||
@@ -0,0 +1,51 @@
|
||||
package reflectlite
|
||||
|
||||
import "reflect"
|
||||
|
||||
func Swapper(slice interface{}) func(i, j int) {
|
||||
return reflect.Swapper(slice)
|
||||
}
|
||||
|
||||
type Kind = reflect.Kind
|
||||
type Type = reflect.Type
|
||||
type Value = reflect.Value
|
||||
|
||||
const (
|
||||
Invalid Kind = reflect.Invalid
|
||||
Bool Kind = reflect.Bool
|
||||
Int Kind = reflect.Int
|
||||
Int8 Kind = reflect.Int8
|
||||
Int16 Kind = reflect.Int16
|
||||
Int32 Kind = reflect.Int32
|
||||
Int64 Kind = reflect.Int64
|
||||
Uint Kind = reflect.Uint
|
||||
Uint8 Kind = reflect.Uint8
|
||||
Uint16 Kind = reflect.Uint16
|
||||
Uint32 Kind = reflect.Uint32
|
||||
Uint64 Kind = reflect.Uint64
|
||||
Uintptr Kind = reflect.Uintptr
|
||||
Float32 Kind = reflect.Float32
|
||||
Float64 Kind = reflect.Float64
|
||||
Complex64 Kind = reflect.Complex64
|
||||
Complex128 Kind = reflect.Complex128
|
||||
Array Kind = reflect.Array
|
||||
Chan Kind = reflect.Chan
|
||||
Func Kind = reflect.Func
|
||||
Interface Kind = reflect.Interface
|
||||
Map Kind = reflect.Map
|
||||
Ptr Kind = reflect.Ptr
|
||||
Slice Kind = reflect.Slice
|
||||
String Kind = reflect.String
|
||||
Struct Kind = reflect.Struct
|
||||
UnsafePointer Kind = reflect.UnsafePointer
|
||||
)
|
||||
|
||||
func ValueOf(i interface{}) reflect.Value {
|
||||
return reflect.ValueOf(i)
|
||||
}
|
||||
|
||||
func TypeOf(i interface{}) reflect.Type {
|
||||
return reflect.TypeOf(i)
|
||||
}
|
||||
|
||||
type ValueError = reflect.ValueError
|
||||
@@ -8,6 +8,9 @@ package machine
|
||||
|
||||
import "device/sam"
|
||||
|
||||
// used to reset into bootloader
|
||||
const RESET_MAGIC_VALUE = 0x07738135
|
||||
|
||||
// GPIO Pins
|
||||
const (
|
||||
RX0 Pin = PB23 // UART2 RX
|
||||
@@ -34,7 +37,7 @@ const (
|
||||
A0 Pin = PA02 // ADC/AIN[0]
|
||||
A1 Pin = PB02 // ADC/AIN[10]
|
||||
A2 Pin = PA11 // ADC/AIN[19]
|
||||
A3 Pin = PA10 // ADC/AIN[18]
|
||||
A3 Pin = PA10 // ADC/AIN[18],
|
||||
A4 Pin = PB08 // ADC/AIN[2], SCL: SERCOM2/PAD[1]
|
||||
A5 Pin = PB09 // ADC/AIN[3], SDA: SERCOM2/PAD[1]
|
||||
A6 Pin = PA09 // ADC/AIN[17]
|
||||
@@ -65,10 +68,10 @@ const (
|
||||
|
||||
// UART1 on the Arduino Nano 33 connects to the onboard NINA-W102 WiFi chip.
|
||||
var (
|
||||
UART1 = UART{Bus: sam.SERCOM5_USART,
|
||||
UART1 = UART{
|
||||
Buffer: NewRingBuffer(),
|
||||
Mode: PinSERCOMAlt,
|
||||
IRQVal: sam.IRQ_SERCOM5,
|
||||
Bus: sam.SERCOM5_USART,
|
||||
SERCOM: 5,
|
||||
}
|
||||
)
|
||||
|
||||
@@ -85,10 +88,10 @@ func handleUART1() {
|
||||
|
||||
// UART2 on the Arduino Nano 33 connects to the normal TX/RX pins.
|
||||
var (
|
||||
UART2 = UART{Bus: sam.SERCOM3_USART,
|
||||
UART2 = UART{
|
||||
Buffer: NewRingBuffer(),
|
||||
Mode: PinSERCOMAlt,
|
||||
IRQVal: sam.IRQ_SERCOM3,
|
||||
Bus: sam.SERCOM3_USART,
|
||||
SERCOM: 3,
|
||||
}
|
||||
)
|
||||
|
||||
@@ -107,22 +110,25 @@ const (
|
||||
|
||||
// I2C on the Arduino Nano 33.
|
||||
var (
|
||||
I2C0 = I2C{Bus: sam.SERCOM4_I2CM,
|
||||
SDA: SDA_PIN,
|
||||
SCL: SCL_PIN,
|
||||
PinMode: PinSERCOMAlt}
|
||||
I2C0 = I2C{
|
||||
Bus: sam.SERCOM4_I2CM,
|
||||
SERCOM: 4,
|
||||
}
|
||||
)
|
||||
|
||||
// SPI pins
|
||||
const (
|
||||
SPI0_SCK_PIN Pin = PB11 // SCK: SERCOM4/PAD[3]
|
||||
SPI0_MOSI_PIN Pin = PB10 // MOSI: SERCOM4/PAD[2]
|
||||
SPI0_MISO_PIN Pin = PA12 // MISO: SERCOM4/PAD[0]
|
||||
SPI0_SCK_PIN Pin = A2 // SCK: SERCOM0/PAD[3]
|
||||
SPI0_MOSI_PIN Pin = A3 // MOSI: SERCOM0/PAD[2]
|
||||
SPI0_MISO_PIN Pin = A6 // MISO: SERCOM0/PAD[1]
|
||||
)
|
||||
|
||||
// SPI on the Arduino Nano 33.
|
||||
var (
|
||||
SPI0 = SPI{Bus: sam.SERCOM1_SPI}
|
||||
SPI0 = SPI{
|
||||
Bus: sam.SERCOM0_SPI,
|
||||
SERCOM: 0,
|
||||
}
|
||||
)
|
||||
|
||||
// I2S pins
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
|
||||
package machine
|
||||
|
||||
import "device/stm32"
|
||||
|
||||
// https://wiki.stm32duino.com/index.php?title=File:Bluepillpinout.gif
|
||||
const (
|
||||
PA0 = portA + 0
|
||||
@@ -47,10 +49,28 @@ const (
|
||||
|
||||
// UART pins
|
||||
const (
|
||||
UART_TX_PIN = PA9
|
||||
UART_RX_PIN = PA10
|
||||
UART_TX_PIN = PA9
|
||||
UART_RX_PIN = PA10
|
||||
UART_ALT_TX_PIN = PB6
|
||||
UART_ALT_RX_PIN = PB7
|
||||
)
|
||||
|
||||
var (
|
||||
// USART1 is the first hardware serial port on the STM32.
|
||||
// Both UART0 and UART1 refer to USART1.
|
||||
UART0 = UART{
|
||||
Buffer: NewRingBuffer(),
|
||||
Bus: stm32.USART1,
|
||||
IRQVal: stm32.IRQ_USART1,
|
||||
}
|
||||
UART1 = &UART0
|
||||
)
|
||||
|
||||
//go:export USART1_IRQHandler
|
||||
func handleUART1() {
|
||||
UART1.Receive(byte((UART1.Bus.DR.Get() & 0xFF)))
|
||||
}
|
||||
|
||||
// SPI pins
|
||||
const (
|
||||
SPI0_SCK_PIN = PA5
|
||||
|
||||
@@ -4,6 +4,9 @@ package machine
|
||||
|
||||
import "device/sam"
|
||||
|
||||
// used to reset into bootloader
|
||||
const RESET_MAGIC_VALUE = 0xf01669ef
|
||||
|
||||
// GPIO Pins
|
||||
const (
|
||||
D0 = PB09
|
||||
@@ -67,10 +70,10 @@ const (
|
||||
|
||||
// UART1 on the Circuit Playground Express.
|
||||
var (
|
||||
UART1 = UART{Bus: sam.SERCOM1_USART,
|
||||
UART1 = UART{
|
||||
Buffer: NewRingBuffer(),
|
||||
Mode: PinSERCOM,
|
||||
IRQVal: sam.IRQ_SERCOM1,
|
||||
Bus: sam.SERCOM4_USART,
|
||||
SERCOM: 4,
|
||||
}
|
||||
)
|
||||
|
||||
@@ -91,15 +94,15 @@ const (
|
||||
// I2C on the Circuit Playground Express.
|
||||
var (
|
||||
// external device
|
||||
I2C0 = I2C{Bus: sam.SERCOM5_I2CM,
|
||||
SDA: SDA_PIN,
|
||||
SCL: SCL_PIN,
|
||||
PinMode: PinSERCOM}
|
||||
I2C0 = I2C{
|
||||
Bus: sam.SERCOM5_I2CM,
|
||||
SERCOM: 5,
|
||||
}
|
||||
// internal device
|
||||
I2C1 = I2C{Bus: sam.SERCOM1_I2CM,
|
||||
SDA: SDA1_PIN,
|
||||
SCL: SCL1_PIN,
|
||||
PinMode: PinSERCOMAlt}
|
||||
I2C1 = I2C{
|
||||
Bus: sam.SERCOM1_I2CM,
|
||||
SERCOM: 1,
|
||||
}
|
||||
)
|
||||
|
||||
// SPI pins (internal flash)
|
||||
@@ -111,7 +114,10 @@ const (
|
||||
|
||||
// SPI on the Circuit Playground Express.
|
||||
var (
|
||||
SPI0 = SPI{Bus: sam.SERCOM3_SPI}
|
||||
SPI0 = SPI{
|
||||
Bus: sam.SERCOM3_SPI,
|
||||
SERCOM: 3,
|
||||
}
|
||||
)
|
||||
|
||||
// I2S pins
|
||||
|
||||
@@ -4,6 +4,9 @@ package machine
|
||||
|
||||
import "device/sam"
|
||||
|
||||
// used to reset into bootloader
|
||||
const RESET_MAGIC_VALUE = 0xf01669ef
|
||||
|
||||
// GPIO Pins
|
||||
const (
|
||||
D0 = PA11 // UART0 RX
|
||||
@@ -50,10 +53,10 @@ const (
|
||||
|
||||
// UART1 on the Feather M0.
|
||||
var (
|
||||
UART1 = UART{Bus: sam.SERCOM1_USART,
|
||||
UART1 = UART{
|
||||
Buffer: NewRingBuffer(),
|
||||
Mode: PinSERCOM,
|
||||
IRQVal: sam.IRQ_SERCOM1,
|
||||
Bus: sam.SERCOM1_USART,
|
||||
SERCOM: 1,
|
||||
}
|
||||
)
|
||||
|
||||
@@ -70,10 +73,10 @@ const (
|
||||
|
||||
// I2C on the Feather M0.
|
||||
var (
|
||||
I2C0 = I2C{Bus: sam.SERCOM3_I2CM,
|
||||
SDA: SDA_PIN,
|
||||
SCL: SCL_PIN,
|
||||
PinMode: PinSERCOM}
|
||||
I2C0 = I2C{
|
||||
Bus: sam.SERCOM3_I2CM,
|
||||
SERCOM: 3,
|
||||
}
|
||||
)
|
||||
|
||||
// SPI pins
|
||||
@@ -85,7 +88,10 @@ const (
|
||||
|
||||
// SPI on the Feather M0.
|
||||
var (
|
||||
SPI0 = SPI{Bus: sam.SERCOM4_SPI}
|
||||
SPI0 = SPI{
|
||||
Bus: sam.SERCOM4_SPI,
|
||||
SERCOM: 4,
|
||||
}
|
||||
)
|
||||
|
||||
// I2S pins
|
||||
|
||||
@@ -2,6 +2,29 @@
|
||||
|
||||
package machine
|
||||
|
||||
const (
|
||||
D0 = P16
|
||||
D1 = P17
|
||||
D2 = P18
|
||||
D3 = P19 // Green LED/PWM
|
||||
D4 = P20 // PWM
|
||||
D5 = P21 // Blue LED/PWM
|
||||
D6 = P22 // Red LED/PWM
|
||||
D7 = P16
|
||||
D8 = NoPin // PWM?
|
||||
D9 = P01
|
||||
D10 = P02
|
||||
D11 = P03
|
||||
D12 = P04
|
||||
D13 = P05
|
||||
D14 = NoPin // not connected
|
||||
D15 = P09 // does not seem to work?
|
||||
D16 = P10 // PWM
|
||||
D17 = P11 // PWM
|
||||
D18 = P12 // SDA/PWM
|
||||
D19 = P13 // SDL/PWM
|
||||
)
|
||||
|
||||
const (
|
||||
LED = LED1
|
||||
LED1 = LED_RED
|
||||
|
||||
@@ -2,7 +2,12 @@
|
||||
|
||||
package machine
|
||||
|
||||
import "device/sam"
|
||||
import (
|
||||
"device/sam"
|
||||
)
|
||||
|
||||
// used to reset into bootloader
|
||||
const RESET_MAGIC_VALUE = 0xf01669ef
|
||||
|
||||
// GPIO Pins
|
||||
const (
|
||||
@@ -50,10 +55,10 @@ const (
|
||||
|
||||
// UART1 on the ItsyBitsy M0.
|
||||
var (
|
||||
UART1 = UART{Bus: sam.SERCOM1_USART,
|
||||
UART1 = UART{
|
||||
Buffer: NewRingBuffer(),
|
||||
Mode: PinSERCOM,
|
||||
IRQVal: sam.IRQ_SERCOM1,
|
||||
Bus: sam.SERCOM1_USART,
|
||||
SERCOM: 1,
|
||||
}
|
||||
)
|
||||
|
||||
@@ -70,10 +75,10 @@ const (
|
||||
|
||||
// I2C on the ItsyBitsy M0.
|
||||
var (
|
||||
I2C0 = I2C{Bus: sam.SERCOM3_I2CM,
|
||||
SDA: SDA_PIN,
|
||||
SCL: SCL_PIN,
|
||||
PinMode: PinSERCOM}
|
||||
I2C0 = I2C{
|
||||
Bus: sam.SERCOM3_I2CM,
|
||||
SERCOM: 3,
|
||||
}
|
||||
)
|
||||
|
||||
// SPI pins
|
||||
@@ -85,7 +90,26 @@ const (
|
||||
|
||||
// SPI on the ItsyBitsy M0.
|
||||
var (
|
||||
SPI0 = SPI{Bus: sam.SERCOM4_SPI}
|
||||
SPI0 = SPI{
|
||||
Bus: sam.SERCOM4_SPI,
|
||||
SERCOM: 4,
|
||||
}
|
||||
)
|
||||
|
||||
// "Internal" SPI pins; SPI flash is attached to these on ItsyBitsy M0
|
||||
const (
|
||||
SPI1_CS_PIN = PA27
|
||||
SPI1_SCK_PIN = PB23
|
||||
SPI1_MOSI_PIN = PB22
|
||||
SPI1_MISO_PIN = PB03
|
||||
)
|
||||
|
||||
// "Internal" SPI on Sercom 5
|
||||
var (
|
||||
SPI1 = SPI{
|
||||
Bus: sam.SERCOM5_SPI,
|
||||
SERCOM: 5,
|
||||
}
|
||||
)
|
||||
|
||||
// I2S pins
|
||||
|
||||
@@ -0,0 +1,83 @@
|
||||
// +build sam,atsamd51,itsybitsy_m4
|
||||
|
||||
package machine
|
||||
|
||||
import "device/sam"
|
||||
|
||||
// used to reset into bootloader
|
||||
const RESET_MAGIC_VALUE = 0xf01669ef
|
||||
|
||||
// GPIO Pins
|
||||
const (
|
||||
D0 = PA16 // UART0 RX/PWM available
|
||||
D1 = PA17 // UART0 TX/PWM available
|
||||
D2 = PA07
|
||||
D3 = PB22
|
||||
D4 = PA14 // PWM available
|
||||
D5 = PA15 // PWM available
|
||||
D6 = PB02 // dotStar clock
|
||||
D7 = PA18 // PWM available
|
||||
D8 = PB03 // dotStar data
|
||||
D9 = PA19 // PWM available
|
||||
D10 = PA20 // can be used for PWM or UART1 TX
|
||||
D11 = PA21 // can be used for PWM or UART1 RX
|
||||
D12 = PA23 // PWM available
|
||||
D13 = PA22 // PWM available
|
||||
)
|
||||
|
||||
// Analog pins
|
||||
const (
|
||||
A0 = PA02 // ADC/AIN[0]
|
||||
A1 = PB05 // ADC/AIN[2]
|
||||
A2 = PB08 // ADC/AIN[3]
|
||||
A3 = PB09 // ADC/AIN[4]
|
||||
A4 = PA04 // ADC/AIN[5]
|
||||
A5 = PA06 // ADC/AIN[10]
|
||||
)
|
||||
|
||||
const (
|
||||
LED = D13
|
||||
)
|
||||
|
||||
// UART0 aka USBCDC pins
|
||||
const (
|
||||
USBCDC_DM_PIN = PA24
|
||||
USBCDC_DP_PIN = PA25
|
||||
)
|
||||
|
||||
// UART1 pins
|
||||
const (
|
||||
UART_TX_PIN = D1
|
||||
UART_RX_PIN = D0
|
||||
)
|
||||
|
||||
// I2C pins
|
||||
const (
|
||||
SDA_PIN = PA12 // SDA: SERCOM3/PAD[0]
|
||||
SCL_PIN = PA13 // SCL: SERCOM3/PAD[1]
|
||||
)
|
||||
|
||||
// I2C on the ItsyBitsy M4.
|
||||
var (
|
||||
I2C0 = I2C{Bus: sam.SERCOM2_I2CM,
|
||||
SDA: SDA_PIN,
|
||||
SCL: SCL_PIN,
|
||||
PinMode: PinSERCOM}
|
||||
)
|
||||
|
||||
// SPI pins
|
||||
const (
|
||||
SPI0_SCK_PIN = PA01 // SCK: SERCOM1/PAD[1]
|
||||
SPI0_MOSI_PIN = PA00 // MOSI: SERCOM1/PAD[0]
|
||||
SPI0_MISO_PIN = PB23 // MISO: SERCOM1/PAD[3]
|
||||
)
|
||||
|
||||
// SPI on the ItsyBitsy M4.
|
||||
var (
|
||||
SPI0 = SPI{Bus: sam.SERCOM1_SPIM,
|
||||
SCK: SPI0_SCK_PIN,
|
||||
MOSI: SPI0_MOSI_PIN,
|
||||
MISO: SPI0_MISO_PIN,
|
||||
DOpad: spiTXPad2SCK3,
|
||||
DIpad: sercomRXPad0}
|
||||
)
|
||||
@@ -0,0 +1,124 @@
|
||||
// +build nucleof103rb
|
||||
|
||||
package machine
|
||||
|
||||
import "device/stm32"
|
||||
|
||||
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
|
||||
)
|
||||
|
||||
const (
|
||||
LED = LED_BUILTIN
|
||||
LED_BUILTIN = LED_GREEN
|
||||
LED_GREEN = PA5
|
||||
)
|
||||
|
||||
const (
|
||||
BUTTON = BUTTON_USER
|
||||
BUTTON_USER = PC13
|
||||
)
|
||||
|
||||
// UART pins
|
||||
const (
|
||||
UART_TX_PIN = PA2
|
||||
UART_RX_PIN = PA3
|
||||
UART_ALT_TX_PIN = PD5
|
||||
UART_ALT_RX_PIN = PD6
|
||||
)
|
||||
|
||||
var (
|
||||
// USART2 is the hardware serial port connected to the onboard ST-LINK
|
||||
// debugger to be exposed as virtual COM port over USB on Nucleo boards.
|
||||
// Both UART0 and UART1 refer to USART2.
|
||||
UART0 = UART{
|
||||
Buffer: NewRingBuffer(),
|
||||
Bus: stm32.USART2,
|
||||
IRQVal: stm32.IRQ_USART2,
|
||||
}
|
||||
UART2 = &UART0
|
||||
)
|
||||
|
||||
//go:export USART2_IRQHandler
|
||||
func handleUART2() {
|
||||
UART2.Receive(byte((UART2.Bus.DR.Get() & 0xFF)))
|
||||
}
|
||||
|
||||
// SPI pins
|
||||
const (
|
||||
SPI0_SCK_PIN = PA5
|
||||
SPI0_MISO_PIN = PA6
|
||||
SPI0_MOSI_PIN = PA7
|
||||
)
|
||||
|
||||
// I2C pins
|
||||
const (
|
||||
SCL_PIN = PB6
|
||||
SDA_PIN = PB7
|
||||
)
|
||||
@@ -1,4 +1,4 @@
|
||||
// +build bluepill stm32f4disco
|
||||
// +build bluepill nucleof103rb stm32f4disco
|
||||
|
||||
package machine
|
||||
|
||||
|
||||
@@ -4,6 +4,9 @@ package machine
|
||||
|
||||
import "device/sam"
|
||||
|
||||
// used to reset into bootloader
|
||||
const RESET_MAGIC_VALUE = 0xf01669ef
|
||||
|
||||
// GPIO Pins
|
||||
const (
|
||||
D0 = PA08 // PWM available
|
||||
@@ -41,10 +44,10 @@ const (
|
||||
|
||||
// UART1 on the Trinket M0.
|
||||
var (
|
||||
UART1 = UART{Bus: sam.SERCOM1_USART,
|
||||
UART1 = UART{
|
||||
Buffer: NewRingBuffer(),
|
||||
Mode: PinSERCOM,
|
||||
IRQVal: sam.IRQ_SERCOM1,
|
||||
Bus: sam.SERCOM0_USART,
|
||||
SERCOM: 0,
|
||||
}
|
||||
)
|
||||
|
||||
@@ -62,7 +65,10 @@ const (
|
||||
|
||||
// SPI on the Trinket M0.
|
||||
var (
|
||||
SPI0 = SPI{Bus: sam.SERCOM0_SPI}
|
||||
SPI0 = SPI{
|
||||
Bus: sam.SERCOM0_SPI,
|
||||
SERCOM: 0,
|
||||
}
|
||||
)
|
||||
|
||||
// I2C pins
|
||||
@@ -73,10 +79,10 @@ const (
|
||||
|
||||
// I2C on the Trinket M0.
|
||||
var (
|
||||
I2C0 = I2C{Bus: sam.SERCOM2_I2CM,
|
||||
SDA: SDA_PIN,
|
||||
SCL: SCL_PIN,
|
||||
PinMode: PinSERCOMAlt}
|
||||
I2C0 = I2C{
|
||||
Bus: sam.SERCOM2_I2CM,
|
||||
SERCOM: 2,
|
||||
}
|
||||
)
|
||||
|
||||
// I2S pins
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
// +build !stm32f4disco,!hifive1b
|
||||
// +build avr nrf sam stm32,!stm32f4disco
|
||||
|
||||
package machine
|
||||
|
||||
|
||||
@@ -1,5 +1,14 @@
|
||||
package machine
|
||||
|
||||
import "errors"
|
||||
|
||||
var (
|
||||
ErrInvalidInputPin = errors.New("machine: invalid input pin")
|
||||
ErrInvalidOutputPin = errors.New("machine: invalid output pin")
|
||||
ErrInvalidClockPin = errors.New("machine: invalid clock pin")
|
||||
ErrInvalidDataPin = errors.New("machine: invalid data pin")
|
||||
)
|
||||
|
||||
type PinConfig struct {
|
||||
Mode PinMode
|
||||
}
|
||||
|
||||
+298
-133
@@ -8,10 +8,8 @@
|
||||
package machine
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"device/arm"
|
||||
"device/sam"
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"unsafe"
|
||||
)
|
||||
@@ -105,6 +103,117 @@ const (
|
||||
PB31 Pin = 63
|
||||
)
|
||||
|
||||
const (
|
||||
pinPadMapSERCOM0Pad0 byte = (0x10 << 1) | 0x00
|
||||
pinPadMapSERCOM1Pad0 byte = (0x20 << 1) | 0x00
|
||||
pinPadMapSERCOM2Pad0 byte = (0x30 << 1) | 0x00
|
||||
pinPadMapSERCOM3Pad0 byte = (0x40 << 1) | 0x00
|
||||
pinPadMapSERCOM4Pad0 byte = (0x50 << 1) | 0x00
|
||||
pinPadMapSERCOM5Pad0 byte = (0x60 << 1) | 0x00
|
||||
pinPadMapSERCOM0Pad2 byte = (0x10 << 1) | 0x10
|
||||
pinPadMapSERCOM1Pad2 byte = (0x20 << 1) | 0x10
|
||||
pinPadMapSERCOM2Pad2 byte = (0x30 << 1) | 0x10
|
||||
pinPadMapSERCOM3Pad2 byte = (0x40 << 1) | 0x10
|
||||
pinPadMapSERCOM4Pad2 byte = (0x50 << 1) | 0x10
|
||||
pinPadMapSERCOM5Pad2 byte = (0x60 << 1) | 0x10
|
||||
|
||||
pinPadMapSERCOM0AltPad0 byte = (0x01 << 1) | 0x00
|
||||
pinPadMapSERCOM1AltPad0 byte = (0x02 << 1) | 0x00
|
||||
pinPadMapSERCOM2AltPad0 byte = (0x03 << 1) | 0x00
|
||||
pinPadMapSERCOM3AltPad0 byte = (0x04 << 1) | 0x00
|
||||
pinPadMapSERCOM4AltPad0 byte = (0x05 << 1) | 0x00
|
||||
pinPadMapSERCOM5AltPad0 byte = (0x06 << 1) | 0x00
|
||||
pinPadMapSERCOM0AltPad2 byte = (0x01 << 1) | 0x01
|
||||
pinPadMapSERCOM1AltPad2 byte = (0x02 << 1) | 0x01
|
||||
pinPadMapSERCOM2AltPad2 byte = (0x03 << 1) | 0x01
|
||||
pinPadMapSERCOM3AltPad2 byte = (0x04 << 1) | 0x01
|
||||
pinPadMapSERCOM4AltPad2 byte = (0x05 << 1) | 0x01
|
||||
pinPadMapSERCOM5AltPad2 byte = (0x06 << 1) | 0x01
|
||||
)
|
||||
|
||||
// pinPadMapping lists which pins have which SERCOMs attached to them.
|
||||
// The encoding is rather dense, with each byte encoding two pins and both
|
||||
// SERCOM and SERCOM-ALT.
|
||||
//
|
||||
// Observations:
|
||||
// * There are six SERCOMs. Those SERCOM numbers can be encoded in 3 bits.
|
||||
// * Even pad numbers are always on even pins, and odd pad numbers are always on
|
||||
// odd pins.
|
||||
// * Pin pads come in pairs. If PA00 has pad 0, then PA01 has pad 1.
|
||||
// With this information, we can encode SERCOM pin/pad numbers much more
|
||||
// efficiently. First of all, due to pads coming in pairs, we can ignore half
|
||||
// the pins: the information for an odd pin can be calculated easily from the
|
||||
// preceding even pin. And second, if odd pads are always on odd pins and even
|
||||
// pads on even pins, we can drop a single bit from the pad number.
|
||||
//
|
||||
// Each byte below is split in two nibbles. The 4 high bits are for SERCOM and
|
||||
// the 4 low bits are for SERCOM-ALT. Of each nibble, the 3 high bits encode the
|
||||
// SERCOM + 1 while the low bit encodes whether this is PAD0 or PAD2 (0 means
|
||||
// PAD0, 1 means PAD2). It encodes SERCOM + 1 instead of just the SERCOM number,
|
||||
// to make it easy to check whether a nibble is set at all.
|
||||
var pinPadMapping = [32]byte{
|
||||
// page 21
|
||||
PA00 / 2: 0 | pinPadMapSERCOM1AltPad0,
|
||||
PB08 / 2: 0 | pinPadMapSERCOM4AltPad0,
|
||||
PA04 / 2: 0 | pinPadMapSERCOM0AltPad0,
|
||||
PA06 / 2: 0 | pinPadMapSERCOM0AltPad2,
|
||||
PA08 / 2: pinPadMapSERCOM0Pad0 | pinPadMapSERCOM2AltPad0,
|
||||
PA10 / 2: pinPadMapSERCOM0Pad2 | pinPadMapSERCOM2AltPad2,
|
||||
|
||||
// page 22
|
||||
PB10 / 2: 0 | pinPadMapSERCOM4AltPad2,
|
||||
PB12 / 2: pinPadMapSERCOM4Pad0 | 0,
|
||||
PB14 / 2: pinPadMapSERCOM4Pad2 | 0,
|
||||
PA12 / 2: pinPadMapSERCOM2Pad0 | pinPadMapSERCOM4AltPad0,
|
||||
PA14 / 2: pinPadMapSERCOM2Pad2 | pinPadMapSERCOM4AltPad2,
|
||||
PA16 / 2: pinPadMapSERCOM1Pad0 | pinPadMapSERCOM3AltPad0,
|
||||
PA18 / 2: pinPadMapSERCOM1Pad2 | pinPadMapSERCOM3AltPad2,
|
||||
PB16 / 2: pinPadMapSERCOM5Pad0 | 0,
|
||||
PA20 / 2: pinPadMapSERCOM5Pad2 | pinPadMapSERCOM3AltPad2,
|
||||
PA22 / 2: pinPadMapSERCOM3Pad0 | pinPadMapSERCOM5AltPad0,
|
||||
PA24 / 2: pinPadMapSERCOM3Pad2 | pinPadMapSERCOM5AltPad2,
|
||||
|
||||
// page 23
|
||||
PB22 / 2: 0 | pinPadMapSERCOM5AltPad2,
|
||||
PA30 / 2: 0 | pinPadMapSERCOM1AltPad2,
|
||||
PB30 / 2: 0 | pinPadMapSERCOM5AltPad0,
|
||||
PB00 / 2: 0 | pinPadMapSERCOM5AltPad2,
|
||||
PB02 / 2: 0 | pinPadMapSERCOM5AltPad0,
|
||||
}
|
||||
|
||||
// findPinPadMapping looks up the pad number and the pinmode for a given pin,
|
||||
// given a SERCOM number. The result can either be SERCOM, SERCOM-ALT, or "not
|
||||
// found" (indicated by returning ok=false). The pad number is returned to
|
||||
// calculate the DOPO/DIPO bitfields of the various serial peripherals.
|
||||
func findPinPadMapping(sercom uint8, pin Pin) (pinMode PinMode, pad uint32, ok bool) {
|
||||
nibbles := pinPadMapping[pin/2]
|
||||
upper := nibbles >> 4
|
||||
lower := nibbles & 0xf
|
||||
|
||||
if upper != 0 {
|
||||
// SERCOM
|
||||
if (upper>>1)-1 == sercom {
|
||||
pinMode = PinSERCOM
|
||||
pad |= uint32((upper & 1) << 1)
|
||||
ok = true
|
||||
}
|
||||
}
|
||||
if lower != 0 {
|
||||
// SERCOM-ALT
|
||||
if (lower>>1)-1 == sercom {
|
||||
pinMode = PinSERCOMAlt
|
||||
pad |= uint32((lower & 1) << 1)
|
||||
ok = true
|
||||
}
|
||||
}
|
||||
|
||||
if ok {
|
||||
// The lower bit of the pad is the same as the lower bit of the pin number.
|
||||
pad |= uint32(pin & 1)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// InitADC initializes the ADC.
|
||||
func InitADC() {
|
||||
// ADC Bias Calibration
|
||||
@@ -192,6 +301,11 @@ func (a ADC) Get() uint16 {
|
||||
sam.ADC.SWTRIG.SetBits(sam.ADC_SWTRIG_START)
|
||||
waitADCSync()
|
||||
|
||||
// wait for first conversion to finish to fix same issue as
|
||||
// https://github.com/arduino/ArduinoCore-samd/issues/446
|
||||
for !sam.ADC.INTFLAG.HasBits(sam.ADC_INTFLAG_RESRDY) {
|
||||
}
|
||||
|
||||
// Clear the Data Ready flag
|
||||
sam.ADC.INTFLAG.SetBits(sam.ADC_INTFLAG_RESRDY)
|
||||
waitADCSync()
|
||||
@@ -250,8 +364,7 @@ func waitADCSync() {
|
||||
type UART struct {
|
||||
Buffer *RingBuffer
|
||||
Bus *sam.SERCOM_USART_Type
|
||||
Mode PinMode
|
||||
IRQVal uint32
|
||||
SERCOM uint8
|
||||
}
|
||||
|
||||
var (
|
||||
@@ -260,71 +373,54 @@ var (
|
||||
)
|
||||
|
||||
const (
|
||||
sampleRate16X = 16
|
||||
lsbFirst = 1
|
||||
sercomRXPad0 = 0
|
||||
sercomRXPad1 = 1
|
||||
sercomRXPad2 = 2
|
||||
sercomRXPad3 = 3
|
||||
sercomTXPad0 = 0 // Only for UART
|
||||
sercomTXPad2 = 1 // Only for UART
|
||||
sercomTXPad023 = 2 // Only for UART with TX on PAD0, RTS on PAD2 and CTS on PAD3
|
||||
|
||||
spiTXPad0SCK1 = 0
|
||||
spiTXPad2SCK3 = 1
|
||||
spiTXPad3SCK1 = 2
|
||||
spiTXPad0SCK3 = 3
|
||||
sampleRate16X = 16
|
||||
lsbFirst = 1
|
||||
)
|
||||
|
||||
// Configure the UART.
|
||||
func (uart UART) Configure(config UARTConfig) {
|
||||
func (uart UART) Configure(config UARTConfig) error {
|
||||
// Default baud rate to 115200.
|
||||
if config.BaudRate == 0 {
|
||||
config.BaudRate = 115200
|
||||
}
|
||||
|
||||
// determine pins
|
||||
if config.TX == 0 {
|
||||
// Use default pins if pins are not set.
|
||||
if config.TX == 0 && config.RX == 0 {
|
||||
// use default pins
|
||||
config.TX = UART_TX_PIN
|
||||
config.RX = UART_RX_PIN
|
||||
}
|
||||
|
||||
// determine pads
|
||||
var txpad, rxpad int
|
||||
switch config.TX {
|
||||
case PA10:
|
||||
txpad = sercomTXPad2
|
||||
case PA18:
|
||||
txpad = sercomTXPad2
|
||||
case PA16:
|
||||
txpad = sercomTXPad0
|
||||
case PA22:
|
||||
txpad = sercomTXPad0
|
||||
// Determine transmit pinout.
|
||||
txPinMode, txPad, ok := findPinPadMapping(uart.SERCOM, config.TX)
|
||||
if !ok {
|
||||
return ErrInvalidOutputPin
|
||||
}
|
||||
var txPinOut uint32
|
||||
// See table 25-9 of the datasheet (page 459) for how pads are mapped to
|
||||
// pinout values.
|
||||
switch txPad {
|
||||
case 0:
|
||||
txPinOut = 0
|
||||
case 2:
|
||||
txPinOut = 1
|
||||
default:
|
||||
panic("Invalid TX pin for UART")
|
||||
// TODO: flow control (RTS/CTS)
|
||||
return ErrInvalidOutputPin
|
||||
}
|
||||
|
||||
switch config.RX {
|
||||
case PA11:
|
||||
rxpad = sercomRXPad3
|
||||
case PA18:
|
||||
rxpad = sercomRXPad2
|
||||
case PA16:
|
||||
rxpad = sercomRXPad0
|
||||
case PA19:
|
||||
rxpad = sercomRXPad3
|
||||
case PA17:
|
||||
rxpad = sercomRXPad1
|
||||
case PA23:
|
||||
rxpad = sercomRXPad1
|
||||
default:
|
||||
panic("Invalid RX pin for UART")
|
||||
// Determine receive pinout.
|
||||
rxPinMode, rxPad, ok := findPinPadMapping(uart.SERCOM, config.RX)
|
||||
if !ok {
|
||||
return ErrInvalidInputPin
|
||||
}
|
||||
// As you can see in table 25-8 on page 459 of the datasheet, input pins
|
||||
// are mapped directly.
|
||||
rxPinOut := rxPad
|
||||
|
||||
// configure pins
|
||||
config.TX.Configure(PinConfig{Mode: uart.Mode})
|
||||
config.RX.Configure(PinConfig{Mode: uart.Mode})
|
||||
config.TX.Configure(PinConfig{Mode: txPinMode})
|
||||
config.RX.Configure(PinConfig{Mode: rxPinMode})
|
||||
|
||||
// reset SERCOM0
|
||||
uart.Bus.CTRLA.SetBits(sam.SERCOM_USART_CTRLA_SWRST)
|
||||
@@ -358,8 +454,8 @@ func (uart UART) Configure(config UARTConfig) {
|
||||
// set UART pads. This is not same as pins...
|
||||
// SERCOM_USART_CTRLA_TXPO(txPad) |
|
||||
// SERCOM_USART_CTRLA_RXPO(rxPad);
|
||||
uart.Bus.CTRLA.SetBits(uint32((txpad << sam.SERCOM_USART_CTRLA_TXPO_Pos) |
|
||||
(rxpad << sam.SERCOM_USART_CTRLA_RXPO_Pos)))
|
||||
uart.Bus.CTRLA.SetBits((txPinOut << sam.SERCOM_USART_CTRLA_TXPO_Pos) |
|
||||
(rxPinOut << sam.SERCOM_USART_CTRLA_RXPO_Pos))
|
||||
|
||||
// Enable Transceiver and Receiver
|
||||
//sercom->USART.CTRLB.reg |= SERCOM_USART_CTRLB_TXEN | SERCOM_USART_CTRLB_RXEN ;
|
||||
@@ -375,7 +471,12 @@ func (uart UART) Configure(config UARTConfig) {
|
||||
uart.Bus.INTENSET.Set(sam.SERCOM_USART_INTENSET_RXC)
|
||||
|
||||
// Enable RX IRQ.
|
||||
arm.EnableIRQ(uart.IRQVal)
|
||||
// IRQ lines are in the same order as SERCOM instance numbers on SAMD21
|
||||
// chips, so the IRQ number can be trivially determined from the SERCOM
|
||||
// number.
|
||||
arm.EnableIRQ(sam.IRQ_SERCOM0 + uint32(uart.SERCOM))
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// SetBaudRate sets the communication speed for the UART.
|
||||
@@ -410,10 +511,8 @@ func defaultUART1Handler() {
|
||||
|
||||
// I2C on the SAMD21.
|
||||
type I2C struct {
|
||||
Bus *sam.SERCOM_I2CM_Type
|
||||
SCL Pin
|
||||
SDA Pin
|
||||
PinMode PinMode
|
||||
Bus *sam.SERCOM_I2CM_Type
|
||||
SERCOM uint8
|
||||
}
|
||||
|
||||
// I2CConfig is used to store config info for I2C.
|
||||
@@ -443,11 +542,30 @@ const (
|
||||
const i2cTimeout = 1000
|
||||
|
||||
// Configure is intended to setup the I2C interface.
|
||||
func (i2c I2C) Configure(config I2CConfig) {
|
||||
func (i2c I2C) Configure(config I2CConfig) error {
|
||||
// Default I2C bus speed is 100 kHz.
|
||||
if config.Frequency == 0 {
|
||||
config.Frequency = TWI_FREQ_100KHZ
|
||||
}
|
||||
if config.SDA == 0 && config.SCL == 0 {
|
||||
config.SDA = SDA_PIN
|
||||
config.SCL = SCL_PIN
|
||||
}
|
||||
|
||||
sclPinMode, sclPad, ok := findPinPadMapping(i2c.SERCOM, config.SCL)
|
||||
if !ok || sclPad != 1 {
|
||||
// SCL must be on pad 1, according to section 27.5 of the datasheet.
|
||||
// Note: this is not an exhaustive test for I2C support on the pin: not
|
||||
// all pins support I2C.
|
||||
return ErrInvalidClockPin
|
||||
}
|
||||
sdaPinMode, sdaPad, ok := findPinPadMapping(i2c.SERCOM, config.SDA)
|
||||
if !ok || sdaPad != 0 {
|
||||
// SDA must be on pad 0, according to section 27.5 of the datasheet.
|
||||
// Note: this is not an exhaustive test for I2C support on the pin: not
|
||||
// all pins support I2C.
|
||||
return ErrInvalidDataPin
|
||||
}
|
||||
|
||||
// reset SERCOM
|
||||
i2c.Bus.CTRLA.SetBits(sam.SERCOM_I2CM_CTRLA_SWRST)
|
||||
@@ -473,8 +591,10 @@ func (i2c I2C) Configure(config I2CConfig) {
|
||||
}
|
||||
|
||||
// enable pins
|
||||
i2c.SDA.Configure(PinConfig{Mode: i2c.PinMode})
|
||||
i2c.SCL.Configure(PinConfig{Mode: i2c.PinMode})
|
||||
config.SDA.Configure(PinConfig{Mode: sdaPinMode})
|
||||
config.SCL.Configure(PinConfig{Mode: sclPinMode})
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// SetBaudRate sets the communication speed for the I2C.
|
||||
@@ -871,7 +991,8 @@ func waitForSync() {
|
||||
|
||||
// SPI
|
||||
type SPI struct {
|
||||
Bus *sam.SERCOM_SPI_Type
|
||||
Bus *sam.SERCOM_SPI_Type
|
||||
SERCOM uint8
|
||||
}
|
||||
|
||||
// SPIConfig is used to store config info for SPI.
|
||||
@@ -885,28 +1006,69 @@ type SPIConfig struct {
|
||||
}
|
||||
|
||||
// Configure is intended to setup the SPI interface.
|
||||
func (spi SPI) Configure(config SPIConfig) {
|
||||
config.SCK = SPI0_SCK_PIN
|
||||
config.MOSI = SPI0_MOSI_PIN
|
||||
config.MISO = SPI0_MISO_PIN
|
||||
|
||||
doPad := spiTXPad2SCK3
|
||||
diPad := sercomRXPad0
|
||||
func (spi SPI) Configure(config SPIConfig) error {
|
||||
// Use default pins if not set.
|
||||
if config.SCK == 0 && config.MOSI == 0 && config.MISO == 0 {
|
||||
config.SCK = SPI0_SCK_PIN
|
||||
config.MOSI = SPI0_MOSI_PIN
|
||||
config.MISO = SPI0_MISO_PIN
|
||||
}
|
||||
|
||||
// set default frequency
|
||||
if config.Frequency == 0 {
|
||||
config.Frequency = 4000000
|
||||
}
|
||||
|
||||
// Determine the input pinout (for MISO).
|
||||
misoPinMode, misoPad, ok := findPinPadMapping(spi.SERCOM, config.MISO)
|
||||
if !ok {
|
||||
return ErrInvalidInputPin
|
||||
}
|
||||
dataInPinout := misoPad // mapped directly
|
||||
|
||||
// Determine the output pinout (for MOSI/SCK).
|
||||
// See table 26-7 on page 494 of the datasheet.
|
||||
var dataOutPinout uint32
|
||||
sckPinMode, sckPad, ok := findPinPadMapping(spi.SERCOM, config.SCK)
|
||||
if !ok {
|
||||
return ErrInvalidOutputPin
|
||||
}
|
||||
mosiPinMode, mosiPad, ok := findPinPadMapping(spi.SERCOM, config.MOSI)
|
||||
if !ok {
|
||||
return ErrInvalidOutputPin
|
||||
}
|
||||
switch sckPad {
|
||||
case 1:
|
||||
switch mosiPad {
|
||||
case 0:
|
||||
dataOutPinout = 0x0
|
||||
case 3:
|
||||
dataOutPinout = 0x2
|
||||
default:
|
||||
return ErrInvalidOutputPin
|
||||
}
|
||||
case 3:
|
||||
switch mosiPad {
|
||||
case 2:
|
||||
dataOutPinout = 0x1
|
||||
case 0:
|
||||
dataOutPinout = 0x3
|
||||
default:
|
||||
return ErrInvalidOutputPin
|
||||
}
|
||||
default:
|
||||
return ErrInvalidOutputPin
|
||||
}
|
||||
|
||||
// Disable SPI port.
|
||||
spi.Bus.CTRLA.ClearBits(sam.SERCOM_SPI_CTRLA_ENABLE)
|
||||
for spi.Bus.SYNCBUSY.HasBits(sam.SERCOM_SPI_SYNCBUSY_ENABLE) {
|
||||
}
|
||||
|
||||
// enable pins
|
||||
config.SCK.Configure(PinConfig{Mode: PinSERCOMAlt})
|
||||
config.MOSI.Configure(PinConfig{Mode: PinSERCOMAlt})
|
||||
config.MISO.Configure(PinConfig{Mode: PinSERCOMAlt})
|
||||
config.SCK.Configure(PinConfig{Mode: sckPinMode})
|
||||
config.MOSI.Configure(PinConfig{Mode: mosiPinMode})
|
||||
config.MISO.Configure(PinConfig{Mode: misoPinMode})
|
||||
|
||||
// reset SERCOM
|
||||
spi.Bus.CTRLA.SetBits(sam.SERCOM_SPI_CTRLA_SWRST)
|
||||
@@ -915,16 +1077,16 @@ func (spi SPI) Configure(config SPIConfig) {
|
||||
}
|
||||
|
||||
// set bit transfer order
|
||||
dataOrder := 0
|
||||
dataOrder := uint32(0)
|
||||
if config.LSBFirst {
|
||||
dataOrder = 1
|
||||
}
|
||||
|
||||
// Set SPI master
|
||||
spi.Bus.CTRLA.Set(uint32((sam.SERCOM_SPI_CTRLA_MODE_SPI_MASTER << sam.SERCOM_SPI_CTRLA_MODE_Pos) |
|
||||
(doPad << sam.SERCOM_SPI_CTRLA_DOPO_Pos) |
|
||||
(diPad << sam.SERCOM_SPI_CTRLA_DIPO_Pos) |
|
||||
(dataOrder << sam.SERCOM_SPI_CTRLA_DORD_Pos)))
|
||||
spi.Bus.CTRLA.Set((sam.SERCOM_SPI_CTRLA_MODE_SPI_MASTER << sam.SERCOM_SPI_CTRLA_MODE_Pos) |
|
||||
(dataOutPinout << sam.SERCOM_SPI_CTRLA_DOPO_Pos) |
|
||||
(dataInPinout << sam.SERCOM_SPI_CTRLA_DIPO_Pos) |
|
||||
(dataOrder << sam.SERCOM_SPI_CTRLA_DORD_Pos))
|
||||
|
||||
spi.Bus.CTRLB.SetBits((0 << sam.SERCOM_SPI_CTRLB_CHSIZE_Pos) | // 8bit char size
|
||||
sam.SERCOM_SPI_CTRLB_RXEN) // receive enable
|
||||
@@ -958,6 +1120,8 @@ func (spi SPI) Configure(config SPIConfig) {
|
||||
spi.Bus.CTRLA.SetBits(sam.SERCOM_SPI_CTRLA_ENABLE)
|
||||
for spi.Bus.SYNCBUSY.HasBits(sam.SERCOM_SPI_SYNCBUSY_ENABLE) {
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Transfer writes/reads a single byte using the SPI interface.
|
||||
@@ -1390,18 +1554,14 @@ func handleUSB() {
|
||||
for i = 1; i < uint32(len(endPoints)); i++ {
|
||||
// Check if endpoint has a pending interrupt
|
||||
epFlags := getEPINTFLAG(i)
|
||||
if epFlags > 0 {
|
||||
if (epFlags&sam.USB_DEVICE_EPINTFLAG_TRCPT0) > 0 ||
|
||||
(epFlags&sam.USB_DEVICE_EPINTFLAG_TRCPT1) > 0 {
|
||||
switch i {
|
||||
case usb_CDC_ENDPOINT_OUT:
|
||||
if (epFlags & sam.USB_DEVICE_EPINTFLAG_TRCPT0) > 0 {
|
||||
handleEndpoint(i)
|
||||
}
|
||||
handleEndpoint(i)
|
||||
setEPINTFLAG(i, epFlags)
|
||||
case usb_CDC_ENDPOINT_IN, usb_CDC_ENDPOINT_ACM:
|
||||
// set bank ready
|
||||
setEPSTATUSCLR(i, sam.USB_DEVICE_EPSTATUSCLR_BK1RDY)
|
||||
|
||||
// ack transfer complete
|
||||
setEPINTFLAG(i, sam.USB_DEVICE_EPINTFLAG_TRCPT1)
|
||||
}
|
||||
}
|
||||
@@ -1598,24 +1758,27 @@ func handleStandardSetup(setup usbSetup) bool {
|
||||
func cdcSetup(setup usbSetup) bool {
|
||||
if setup.bmRequestType == usb_REQUEST_DEVICETOHOST_CLASS_INTERFACE {
|
||||
if setup.bRequest == usb_CDC_GET_LINE_CODING {
|
||||
buf := bytes.NewBuffer(make([]byte, 0, 7))
|
||||
binary.Write(buf, binary.LittleEndian, usbLineInfo.dwDTERate)
|
||||
binary.Write(buf, binary.LittleEndian, usbLineInfo.bCharFormat)
|
||||
binary.Write(buf, binary.LittleEndian, usbLineInfo.bParityType)
|
||||
binary.Write(buf, binary.LittleEndian, usbLineInfo.bDataBits)
|
||||
b := make([]byte, 7)
|
||||
b[0] = byte(usbLineInfo.dwDTERate)
|
||||
b[1] = byte(usbLineInfo.dwDTERate >> 8)
|
||||
b[2] = byte(usbLineInfo.dwDTERate >> 16)
|
||||
b[3] = byte(usbLineInfo.dwDTERate >> 24)
|
||||
b[4] = byte(usbLineInfo.bCharFormat)
|
||||
b[5] = byte(usbLineInfo.bParityType)
|
||||
b[6] = byte(usbLineInfo.bDataBits)
|
||||
|
||||
sendUSBPacket(0, buf.Bytes())
|
||||
sendUSBPacket(0, b)
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
if setup.bmRequestType == usb_REQUEST_HOSTTODEVICE_CLASS_INTERFACE {
|
||||
if setup.bRequest == usb_CDC_SET_LINE_CODING {
|
||||
buf := bytes.NewBuffer(receiveUSBControlPacket())
|
||||
binary.Read(buf, binary.LittleEndian, &(usbLineInfo.dwDTERate))
|
||||
binary.Read(buf, binary.LittleEndian, &(usbLineInfo.bCharFormat))
|
||||
binary.Read(buf, binary.LittleEndian, &(usbLineInfo.bParityType))
|
||||
binary.Read(buf, binary.LittleEndian, &(usbLineInfo.bDataBits))
|
||||
b := receiveUSBControlPacket()
|
||||
usbLineInfo.dwDTERate = uint32(b[0]) | uint32(b[1])<<8 | uint32(b[2])<<16 | uint32(b[3])<<24
|
||||
usbLineInfo.bCharFormat = b[4]
|
||||
usbLineInfo.bParityType = b[5]
|
||||
usbLineInfo.bDataBits = b[6]
|
||||
}
|
||||
|
||||
if setup.bRequest == usb_CDC_SET_CONTROL_LINE_STATE {
|
||||
@@ -1624,17 +1787,19 @@ func cdcSetup(setup usbSetup) bool {
|
||||
|
||||
if setup.bRequest == usb_CDC_SET_LINE_CODING || setup.bRequest == usb_CDC_SET_CONTROL_LINE_STATE {
|
||||
// auto-reset into the bootloader
|
||||
if usbLineInfo.dwDTERate == 1200 && (usbLineInfo.lineState&0x01) == 0 {
|
||||
// TODO: system reset
|
||||
if usbLineInfo.dwDTERate == 1200 && usbLineInfo.lineState&usb_CDC_LINESTATE_DTR == 0 {
|
||||
ResetProcessor()
|
||||
} else {
|
||||
// TODO: cancel any reset
|
||||
}
|
||||
sendZlp(0)
|
||||
}
|
||||
|
||||
if setup.bRequest == usb_CDC_SEND_BREAK {
|
||||
// TODO: something with this value?
|
||||
// breakValue = ((uint16_t)setup.wValueH << 8) | setup.wValueL;
|
||||
// return false;
|
||||
sendZlp(0)
|
||||
}
|
||||
return true
|
||||
}
|
||||
@@ -1655,53 +1820,41 @@ func sendUSBPacket(ep uint32, data []byte) {
|
||||
}
|
||||
|
||||
func receiveUSBControlPacket() []byte {
|
||||
// set ready to receive data
|
||||
// address
|
||||
usbEndpointDescriptors[0].DeviceDescBank[0].ADDR.Set(uint32(uintptr(unsafe.Pointer(&udd_ep_out_cache_buffer[0]))))
|
||||
|
||||
// set byte count to zero
|
||||
usbEndpointDescriptors[0].DeviceDescBank[0].PCKSIZE.ClearBits(usb_DEVICE_PCKSIZE_BYTE_COUNT_Mask << usb_DEVICE_PCKSIZE_BYTE_COUNT_Pos)
|
||||
|
||||
// set ready for next data
|
||||
setEPSTATUSCLR(0, sam.USB_DEVICE_EPSTATUSCLR_BK0RDY)
|
||||
|
||||
// read the data
|
||||
bytesread := armRecvCtrlOUT(0)
|
||||
|
||||
// return the data
|
||||
data := make([]byte, 0, bytesread)
|
||||
copy(data, udd_ep_out_cache_buffer[0][:bytesread])
|
||||
return data
|
||||
}
|
||||
|
||||
func armRecvCtrlOUT(ep uint32) uint32 {
|
||||
// Set output address to receive data
|
||||
usbEndpointDescriptors[ep].DeviceDescBank[0].ADDR.Set(uint32(uintptr(unsafe.Pointer(&udd_ep_out_cache_buffer[ep]))))
|
||||
|
||||
// set multi-packet size which is total expected number of bytes to receive.
|
||||
usbEndpointDescriptors[ep].DeviceDescBank[0].PCKSIZE.SetBits((8 << usb_DEVICE_PCKSIZE_MULTI_PACKET_SIZE_Pos) |
|
||||
uint32(epPacketSize(64)<<usb_DEVICE_PCKSIZE_SIZE_Pos))
|
||||
|
||||
// clear byte count of bytes received so far.
|
||||
usbEndpointDescriptors[ep].DeviceDescBank[0].PCKSIZE.ClearBits(usb_DEVICE_PCKSIZE_BYTE_COUNT_Mask << usb_DEVICE_PCKSIZE_BYTE_COUNT_Pos)
|
||||
|
||||
// clear ready state to start transfer
|
||||
setEPSTATUSCLR(ep, sam.USB_DEVICE_EPSTATUSCLR_BK0RDY)
|
||||
|
||||
// Wait until OUT transfer is ready.
|
||||
timeout := 3000
|
||||
for (getEPSTATUS(ep) & sam.USB_DEVICE_EPSTATUS_BK0RDY) == 0 {
|
||||
timeout := 300000
|
||||
for (getEPSTATUS(0) & sam.USB_DEVICE_EPSTATUS_BK0RDY) == 0 {
|
||||
timeout--
|
||||
if timeout == 0 {
|
||||
return 0
|
||||
return []byte{}
|
||||
}
|
||||
}
|
||||
|
||||
// Wait until OUT transfer is completed.
|
||||
timeout = 3000
|
||||
for (getEPINTFLAG(ep) & sam.USB_DEVICE_EPINTFLAG_TRCPT0) == 0 {
|
||||
timeout = 300000
|
||||
for (getEPINTFLAG(0) & sam.USB_DEVICE_EPINTFLAG_TRCPT0) == 0 {
|
||||
timeout--
|
||||
if timeout == 0 {
|
||||
return 0
|
||||
return []byte{}
|
||||
}
|
||||
}
|
||||
|
||||
// return number of bytes received
|
||||
return (usbEndpointDescriptors[ep].DeviceDescBank[0].PCKSIZE.Get() >>
|
||||
usb_DEVICE_PCKSIZE_BYTE_COUNT_Pos) & usb_DEVICE_PCKSIZE_BYTE_COUNT_Mask
|
||||
// get data
|
||||
bytesread := uint32((usbEndpointDescriptors[0].DeviceDescBank[0].PCKSIZE.Get() >>
|
||||
usb_DEVICE_PCKSIZE_BYTE_COUNT_Pos) & usb_DEVICE_PCKSIZE_BYTE_COUNT_Mask)
|
||||
|
||||
data := make([]byte, bytesread)
|
||||
copy(data, udd_ep_out_cache_buffer[0][:])
|
||||
|
||||
return data
|
||||
}
|
||||
|
||||
// sendDescriptor creates and sends the various USB descriptor types that
|
||||
@@ -1797,9 +1950,9 @@ func sendConfiguration(setup usbSetup) {
|
||||
|
||||
dif := NewInterfaceDescriptor(usb_CDC_DATA_INTERFACE, 2, usb_CDC_DATA_INTERFACE_CLASS, 0, 0)
|
||||
|
||||
in := NewEndpointDescriptor((usb_CDC_ENDPOINT_OUT | usbEndpointOut), usb_ENDPOINT_TYPE_BULK, usbEndpointPacketSize, 0)
|
||||
out := NewEndpointDescriptor((usb_CDC_ENDPOINT_OUT | usbEndpointOut), usb_ENDPOINT_TYPE_BULK, usbEndpointPacketSize, 0)
|
||||
|
||||
out := NewEndpointDescriptor((usb_CDC_ENDPOINT_IN | usbEndpointIn), usb_ENDPOINT_TYPE_BULK, usbEndpointPacketSize, 0)
|
||||
in := NewEndpointDescriptor((usb_CDC_ENDPOINT_IN | usbEndpointIn), usb_ENDPOINT_TYPE_BULK, usbEndpointPacketSize, 0)
|
||||
|
||||
cdc := NewCDCDescriptor(iad,
|
||||
cif,
|
||||
@@ -1809,8 +1962,8 @@ func sendConfiguration(setup usbSetup) {
|
||||
callManagement,
|
||||
cifin,
|
||||
dif,
|
||||
in,
|
||||
out)
|
||||
out,
|
||||
in)
|
||||
|
||||
sz := uint16(configDescriptorSize + cdcSize)
|
||||
config := NewConfigDescriptor(sz, 2)
|
||||
@@ -2076,3 +2229,15 @@ func setEPINTENSET(ep uint32, val uint8) {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// ResetProcessor should perform a system reset in preperation
|
||||
// to switch to the bootloader to flash new firmware.
|
||||
func ResetProcessor() {
|
||||
arm.DisableInterrupts()
|
||||
|
||||
// Perform magic reset into bootloader, as mentioned in
|
||||
// https://github.com/arduino/ArduinoCore-samd/issues/197
|
||||
*(*uint32)(unsafe.Pointer(uintptr(0x20007FFC))) = RESET_MAGIC_VALUE
|
||||
|
||||
arm.SystemReset()
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,201 @@
|
||||
// +build sam,atsamd51,atsamd51g19
|
||||
|
||||
// Peripheral abstraction layer for the atsamd51.
|
||||
//
|
||||
// Datasheet:
|
||||
// http://ww1.microchip.com/downloads/en/DeviceDoc/60001507C.pdf
|
||||
//
|
||||
package machine
|
||||
|
||||
import (
|
||||
"device/sam"
|
||||
)
|
||||
|
||||
// Return the register and mask to enable a given GPIO pin. This can be used to
|
||||
// implement bit-banged drivers.
|
||||
func (p Pin) PortMaskSet() (*uint32, uint32) {
|
||||
if p < 32 {
|
||||
return &sam.PORT.GROUP[0].OUTSET.Reg, 1 << uint8(p)
|
||||
} else {
|
||||
return &sam.PORT.GROUP[1].OUTSET.Reg, 1 << uint8(p-32)
|
||||
}
|
||||
}
|
||||
|
||||
// Return the register and mask to disable a given port. This can be used to
|
||||
// implement bit-banged drivers.
|
||||
func (p Pin) PortMaskClear() (*uint32, uint32) {
|
||||
if p < 32 {
|
||||
return &sam.PORT.GROUP[0].OUTCLR.Reg, 1 << uint8(p)
|
||||
} else {
|
||||
return &sam.PORT.GROUP[1].OUTCLR.Reg, 1 << uint8(p-32)
|
||||
}
|
||||
}
|
||||
|
||||
// Set the pin to high or low.
|
||||
// Warning: only use this on an output pin!
|
||||
func (p Pin) Set(high bool) {
|
||||
if p < 32 {
|
||||
if high {
|
||||
sam.PORT.GROUP[0].OUTSET.Set(1 << uint8(p))
|
||||
} else {
|
||||
sam.PORT.GROUP[0].OUTCLR.Set(1 << uint8(p))
|
||||
}
|
||||
} else {
|
||||
if high {
|
||||
sam.PORT.GROUP[1].OUTSET.Set(1 << uint8(p-32))
|
||||
} else {
|
||||
sam.PORT.GROUP[1].OUTCLR.Set(1 << uint8(p-32))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Get returns the current value of a GPIO pin.
|
||||
func (p Pin) Get() bool {
|
||||
if p < 32 {
|
||||
return (sam.PORT.GROUP[0].IN.Get()>>uint8(p))&1 > 0
|
||||
} else {
|
||||
return (sam.PORT.GROUP[1].IN.Get()>>(uint8(p)-32))&1 > 0
|
||||
}
|
||||
}
|
||||
|
||||
// Configure this pin with the given configuration.
|
||||
func (p Pin) Configure(config PinConfig) {
|
||||
switch config.Mode {
|
||||
case PinOutput:
|
||||
if p < 32 {
|
||||
sam.PORT.GROUP[0].DIRSET.Set(1 << uint8(p))
|
||||
// output is also set to input enable so pin can read back its own value
|
||||
p.setPinCfg(sam.PORT_GROUP_PINCFG_INEN)
|
||||
} else {
|
||||
sam.PORT.GROUP[1].DIRSET.Set(1 << uint8(p-32))
|
||||
// output is also set to input enable so pin can read back its own value
|
||||
p.setPinCfg(sam.PORT_GROUP_PINCFG_INEN)
|
||||
}
|
||||
|
||||
case PinInput:
|
||||
if p < 32 {
|
||||
sam.PORT.GROUP[0].DIRCLR.Set(1 << uint8(p))
|
||||
p.setPinCfg(sam.PORT_GROUP_PINCFG_INEN)
|
||||
} else {
|
||||
sam.PORT.GROUP[1].DIRCLR.Set(1<<uint8(p) - 32)
|
||||
p.setPinCfg(sam.PORT_GROUP_PINCFG_INEN)
|
||||
}
|
||||
|
||||
case PinInputPulldown:
|
||||
if p < 32 {
|
||||
sam.PORT.GROUP[0].DIRCLR.Set(1 << uint8(p))
|
||||
sam.PORT.GROUP[0].OUTCLR.Set(1 << uint8(p))
|
||||
p.setPinCfg(sam.PORT_GROUP_PINCFG_INEN | sam.PORT_GROUP_PINCFG_PULLEN)
|
||||
} else {
|
||||
sam.PORT.GROUP[1].DIRCLR.Set(1<<uint8(p) - 32)
|
||||
sam.PORT.GROUP[1].OUTCLR.Set(1<<uint8(p) - 32)
|
||||
p.setPinCfg(sam.PORT_GROUP_PINCFG_INEN | sam.PORT_GROUP_PINCFG_PULLEN)
|
||||
}
|
||||
|
||||
case PinInputPullup:
|
||||
if p < 32 {
|
||||
sam.PORT.GROUP[0].DIRCLR.Set(1 << uint8(p))
|
||||
sam.PORT.GROUP[0].OUTSET.Set(1 << uint8(p))
|
||||
p.setPinCfg(sam.PORT_GROUP_PINCFG_INEN | sam.PORT_GROUP_PINCFG_PULLEN)
|
||||
} else {
|
||||
sam.PORT.GROUP[1].DIRCLR.Set(1<<uint8(p) - 32)
|
||||
sam.PORT.GROUP[1].OUTSET.Set(1<<uint8(p) - 32)
|
||||
p.setPinCfg(sam.PORT_GROUP_PINCFG_INEN | sam.PORT_GROUP_PINCFG_PULLEN)
|
||||
}
|
||||
|
||||
case PinSERCOM:
|
||||
if p&1 > 0 {
|
||||
// odd pin, so save the even pins
|
||||
val := p.getPMux() & sam.PORT_GROUP_PMUX_PMUXE_Msk
|
||||
p.setPMux(val | (uint8(PinSERCOM) << sam.PORT_GROUP_PMUX_PMUXO_Pos))
|
||||
} else {
|
||||
// even pin, so save the odd pins
|
||||
val := p.getPMux() & sam.PORT_GROUP_PMUX_PMUXO_Msk
|
||||
p.setPMux(val | (uint8(PinSERCOM) << sam.PORT_GROUP_PMUX_PMUXE_Pos))
|
||||
}
|
||||
// enable port config
|
||||
p.setPinCfg(sam.PORT_GROUP_PINCFG_PMUXEN | sam.PORT_GROUP_PINCFG_DRVSTR | sam.PORT_GROUP_PINCFG_INEN)
|
||||
|
||||
case PinSERCOMAlt:
|
||||
if p&1 > 0 {
|
||||
// odd pin, so save the even pins
|
||||
val := p.getPMux() & sam.PORT_GROUP_PMUX_PMUXE_Msk
|
||||
p.setPMux(val | (uint8(PinSERCOMAlt) << sam.PORT_GROUP_PMUX_PMUXO_Pos))
|
||||
} else {
|
||||
// even pin, so save the odd pins
|
||||
val := p.getPMux() & sam.PORT_GROUP_PMUX_PMUXO_Msk
|
||||
p.setPMux(val | (uint8(PinSERCOMAlt) << sam.PORT_GROUP_PMUX_PMUXE_Pos))
|
||||
}
|
||||
// enable port config
|
||||
p.setPinCfg(sam.PORT_GROUP_PINCFG_PMUXEN | sam.PORT_GROUP_PINCFG_DRVSTR)
|
||||
|
||||
case PinCom:
|
||||
if p&1 > 0 {
|
||||
// odd pin, so save the even pins
|
||||
val := p.getPMux() & sam.PORT_GROUP_PMUX_PMUXE_Msk
|
||||
p.setPMux(val | (uint8(PinCom) << sam.PORT_GROUP_PMUX_PMUXO_Pos))
|
||||
} else {
|
||||
// even pin, so save the odd pins
|
||||
val := p.getPMux() & sam.PORT_GROUP_PMUX_PMUXO_Msk
|
||||
p.setPMux(val | (uint8(PinCom) << sam.PORT_GROUP_PMUX_PMUXE_Pos))
|
||||
}
|
||||
// enable port config
|
||||
p.setPinCfg(sam.PORT_GROUP_PINCFG_PMUXEN)
|
||||
case PinAnalog:
|
||||
if p&1 > 0 {
|
||||
// odd pin, so save the even pins
|
||||
val := p.getPMux() & sam.PORT_GROUP_PMUX_PMUXE_Msk
|
||||
p.setPMux(val | (uint8(PinAnalog) << sam.PORT_GROUP_PMUX_PMUXO_Pos))
|
||||
} else {
|
||||
// even pin, so save the odd pins
|
||||
val := p.getPMux() & sam.PORT_GROUP_PMUX_PMUXO_Msk
|
||||
p.setPMux(val | (uint8(PinAnalog) << sam.PORT_GROUP_PMUX_PMUXE_Pos))
|
||||
}
|
||||
// enable port config
|
||||
p.setPinCfg(sam.PORT_GROUP_PINCFG_PMUXEN | sam.PORT_GROUP_PINCFG_DRVSTR)
|
||||
}
|
||||
}
|
||||
|
||||
// getPMux returns the value for the correct PMUX register for this pin.
|
||||
func (p Pin) getPMux() uint8 {
|
||||
switch {
|
||||
case p < 32:
|
||||
return sam.PORT.GROUP[0].PMUX[uint8(p)>>1].Get()
|
||||
case p >= 32 && p < 64:
|
||||
return sam.PORT.GROUP[1].PMUX[uint8(p-32)>>1].Get()
|
||||
default:
|
||||
return 0
|
||||
}
|
||||
}
|
||||
|
||||
// setPMux sets the value for the correct PMUX register for this pin.
|
||||
func (p Pin) setPMux(val uint8) {
|
||||
switch {
|
||||
case p < 32:
|
||||
sam.PORT.GROUP[0].PMUX[uint8(p)>>1].Set(val)
|
||||
case p >= 32 && p < 64:
|
||||
sam.PORT.GROUP[1].PMUX[uint8(p-32)>>1].Set(val)
|
||||
}
|
||||
}
|
||||
|
||||
// getPinCfg returns the value for the correct PINCFG register for this pin.
|
||||
func (p Pin) getPinCfg() uint8 {
|
||||
switch {
|
||||
case p < 32:
|
||||
return sam.PORT.GROUP[0].PINCFG[p].Get()
|
||||
case p >= 32 && p <= 64:
|
||||
return sam.PORT.GROUP[1].PINCFG[p-32].Get()
|
||||
default:
|
||||
return 0
|
||||
}
|
||||
}
|
||||
|
||||
// setPinCfg sets the value for the correct PINCFG register for this pin.
|
||||
func (p Pin) setPinCfg(val uint8) {
|
||||
switch {
|
||||
case p < 32:
|
||||
sam.PORT.GROUP[0].PINCFG[p].Set(val)
|
||||
case p >= 32 && p <= 64:
|
||||
sam.PORT.GROUP[1].PINCFG[p-32].Set(val)
|
||||
}
|
||||
}
|
||||
@@ -30,6 +30,13 @@ func (p Pin) Set(high bool) {
|
||||
}
|
||||
}
|
||||
|
||||
// Get returns the current value of a GPIO pin.
|
||||
func (p Pin) Get() bool {
|
||||
val := sifive.GPIO0.VALUE.Get() & (1 << uint8(p))
|
||||
println(sifive.GPIO0.VALUE.Get())
|
||||
return (val > 0)
|
||||
}
|
||||
|
||||
type UART struct {
|
||||
Bus *sifive.UART_Type
|
||||
Buffer *RingBuffer
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
// +build gameboyadvance
|
||||
|
||||
package machine
|
||||
|
||||
import (
|
||||
"image/color"
|
||||
"runtime/volatile"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
// Make it easier to directly write to I/O RAM.
|
||||
var ioram = (*[0x400]volatile.Register8)(unsafe.Pointer(uintptr(0x04000000)))
|
||||
|
||||
type PinMode uint8
|
||||
|
||||
// Set has not been implemented.
|
||||
func (p Pin) Set(value bool) {
|
||||
// do nothing
|
||||
}
|
||||
|
||||
var Display = FramebufDisplay{(*[160][240]volatile.Register16)(unsafe.Pointer(uintptr(0x06000000)))}
|
||||
|
||||
type FramebufDisplay struct {
|
||||
port *[160][240]volatile.Register16
|
||||
}
|
||||
|
||||
func (d FramebufDisplay) Configure() {
|
||||
// Write into the I/O registers, setting video display parameters.
|
||||
ioram[0].Set(0x03) // Use video mode 3 (in BG2, a 16bpp bitmap in VRAM)
|
||||
ioram[1].Set(0x04) // Enable BG2 (BG0 = 1, BG1 = 2, BG2 = 4, ...)
|
||||
}
|
||||
|
||||
func (d FramebufDisplay) Size() (x, y int16) {
|
||||
return 240, 160
|
||||
}
|
||||
|
||||
func (d FramebufDisplay) SetPixel(x, y int16, c color.RGBA) {
|
||||
d.port[y][x].Set(uint16(c.R)&0x1f | uint16(c.G)&0x1f<<5 | uint16(c.B)&0x1f<<10)
|
||||
}
|
||||
|
||||
func (d FramebufDisplay) Display() error {
|
||||
// Nothing to do here.
|
||||
return nil
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
// +build !avr,!nrf,!sam,!sifive,!stm32
|
||||
// +build !baremetal
|
||||
|
||||
package machine
|
||||
|
||||
|
||||
@@ -5,6 +5,11 @@ package machine
|
||||
import (
|
||||
"device/arm"
|
||||
"device/nrf"
|
||||
"errors"
|
||||
)
|
||||
|
||||
var (
|
||||
ErrTxInvalidSliceSize = errors.New("SPI write and read slices must be same size")
|
||||
)
|
||||
|
||||
type PinMode uint8
|
||||
@@ -322,3 +327,66 @@ func (spi SPI) Transfer(w byte) (byte, error) {
|
||||
// TODO: handle SPI errors
|
||||
return byte(r), nil
|
||||
}
|
||||
|
||||
// Tx handles read/write operation for SPI interface. Since SPI is a syncronous write/read
|
||||
// interface, there must always be the same number of bytes written as bytes read.
|
||||
// The Tx method knows about this, and offers a few different ways of calling it.
|
||||
//
|
||||
// This form sends the bytes in tx buffer, putting the resulting bytes read into the rx buffer.
|
||||
// Note that the tx and rx buffers must be the same size:
|
||||
//
|
||||
// spi.Tx(tx, rx)
|
||||
//
|
||||
// This form sends the tx buffer, ignoring the result. Useful for sending "commands" that return zeros
|
||||
// until all the bytes in the command packet have been received:
|
||||
//
|
||||
// spi.Tx(tx, nil)
|
||||
//
|
||||
// This form sends zeros, putting the result into the rx buffer. Good for reading a "result packet":
|
||||
//
|
||||
// spi.Tx(nil, rx)
|
||||
//
|
||||
func (spi SPI) Tx(w, r []byte) error {
|
||||
var err error
|
||||
|
||||
switch {
|
||||
case len(w) == 0:
|
||||
// read only, so write zero and read a result.
|
||||
for i := range r {
|
||||
r[i], err = spi.Transfer(0)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
case len(r) == 0:
|
||||
// write only
|
||||
spi.Bus.TXD.Set(uint32(w[0]))
|
||||
w = w[1:]
|
||||
for _, b := range w {
|
||||
spi.Bus.TXD.Set(uint32(b))
|
||||
for spi.Bus.EVENTS_READY.Get() == 0 {
|
||||
}
|
||||
_ = spi.Bus.RXD.Get()
|
||||
spi.Bus.EVENTS_READY.Set(0)
|
||||
}
|
||||
for spi.Bus.EVENTS_READY.Get() == 0 {
|
||||
}
|
||||
_ = spi.Bus.RXD.Get()
|
||||
spi.Bus.EVENTS_READY.Set(0)
|
||||
|
||||
default:
|
||||
// write/read
|
||||
if len(w) != len(r) {
|
||||
return ErrTxInvalidSliceSize
|
||||
}
|
||||
|
||||
for i, b := range w {
|
||||
r[i], err = spi.Transfer(b)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -99,18 +99,21 @@ func (p Pin) Set(high bool) {
|
||||
}
|
||||
}
|
||||
|
||||
// Get returns the current value of a GPIO pin.
|
||||
func (p Pin) Get() bool {
|
||||
port := p.getPort()
|
||||
pin := uint8(p) % 16
|
||||
val := port.IDR.Get() & (1 << pin)
|
||||
return (val > 0)
|
||||
}
|
||||
|
||||
// UART
|
||||
type UART struct {
|
||||
Buffer *RingBuffer
|
||||
Bus *stm32.USART_Type
|
||||
IRQVal uint32
|
||||
}
|
||||
|
||||
var (
|
||||
// USART1 is the first hardware serial port on the STM32.
|
||||
// Both UART0 and UART1 refer to USART1.
|
||||
UART0 = UART{Buffer: NewRingBuffer()}
|
||||
UART1 = &UART0
|
||||
)
|
||||
|
||||
// Configure the UART.
|
||||
func (uart UART) Configure(config UARTConfig) {
|
||||
// Default baud rate to 115200.
|
||||
@@ -120,53 +123,63 @@ func (uart UART) Configure(config UARTConfig) {
|
||||
|
||||
// pins
|
||||
switch config.TX {
|
||||
case PB6:
|
||||
// use alternate TX/RX pins PB6/PB7 via AFIO mapping
|
||||
case UART_ALT_TX_PIN:
|
||||
// use alternate TX/RX pins via AFIO mapping
|
||||
stm32.RCC.APB2ENR.SetBits(stm32.RCC_APB2ENR_AFIOEN)
|
||||
stm32.AFIO.MAPR.SetBits(stm32.AFIO_MAPR_USART1_REMAP)
|
||||
PB6.Configure(PinConfig{Mode: PinOutput50MHz + PinOutputModeAltPushPull})
|
||||
PB7.Configure(PinConfig{Mode: PinInputModeFloating})
|
||||
if uart.Bus == stm32.USART1 {
|
||||
stm32.AFIO.MAPR.SetBits(stm32.AFIO_MAPR_USART1_REMAP)
|
||||
} else if uart.Bus == stm32.USART2 {
|
||||
stm32.AFIO.MAPR.SetBits(stm32.AFIO_MAPR_USART2_REMAP)
|
||||
}
|
||||
UART_ALT_TX_PIN.Configure(PinConfig{Mode: PinOutput50MHz + PinOutputModeAltPushPull})
|
||||
UART_ALT_RX_PIN.Configure(PinConfig{Mode: PinInputModeFloating})
|
||||
default:
|
||||
// use standard TX/RX pins PA9 and PA10
|
||||
UART_TX_PIN.Configure(PinConfig{Mode: PinOutput50MHz + PinOutputModeAltPushPull})
|
||||
UART_RX_PIN.Configure(PinConfig{Mode: PinInputModeFloating})
|
||||
}
|
||||
|
||||
// Enable USART1 clock
|
||||
stm32.RCC.APB2ENR.SetBits(stm32.RCC_APB2ENR_USART1EN)
|
||||
// Enable USART clock
|
||||
if uart.Bus == stm32.USART1 {
|
||||
stm32.RCC.APB2ENR.SetBits(stm32.RCC_APB2ENR_USART1EN)
|
||||
} else if uart.Bus == stm32.USART2 {
|
||||
stm32.RCC.APB1ENR.SetBits(stm32.RCC_APB1ENR_USART2EN)
|
||||
}
|
||||
|
||||
// Set baud rate
|
||||
uart.SetBaudRate(config.BaudRate)
|
||||
|
||||
// Enable USART1 port.
|
||||
stm32.USART1.CR1.Set(stm32.USART_CR1_TE | stm32.USART_CR1_RE | stm32.USART_CR1_RXNEIE | stm32.USART_CR1_UE)
|
||||
// Enable USART port
|
||||
uart.Bus.CR1.Set(stm32.USART_CR1_TE | stm32.USART_CR1_RE | stm32.USART_CR1_RXNEIE | stm32.USART_CR1_UE)
|
||||
|
||||
// Enable RX IRQ.
|
||||
arm.SetPriority(stm32.IRQ_USART1, 0xc0)
|
||||
arm.EnableIRQ(stm32.IRQ_USART1)
|
||||
// Enable RX IRQ
|
||||
arm.SetPriority(uart.IRQVal, 0xc0)
|
||||
arm.EnableIRQ(uart.IRQVal)
|
||||
}
|
||||
|
||||
// SetBaudRate sets the communication speed for the UART.
|
||||
func (uart UART) SetBaudRate(br uint32) {
|
||||
// first divide by PCLK2 prescaler (div 1) and then desired baudrate
|
||||
divider := CPU_FREQUENCY / br
|
||||
stm32.USART1.BRR.Set(divider)
|
||||
// Note: PCLK2 (from APB2) used for USART1 and PCLK1 for USART2, 3, 4, 5
|
||||
var divider uint32
|
||||
if uart.Bus == stm32.USART1 {
|
||||
// first divide by PCLK2 prescaler (div 1) and then desired baudrate
|
||||
divider = CPU_FREQUENCY / br
|
||||
} else {
|
||||
// first divide by PCLK1 prescaler (div 2) and then desired baudrate
|
||||
divider = CPU_FREQUENCY / 2 / br
|
||||
}
|
||||
uart.Bus.BRR.Set(divider)
|
||||
}
|
||||
|
||||
// WriteByte writes a byte of data to the UART.
|
||||
func (uart UART) WriteByte(c byte) error {
|
||||
stm32.USART1.DR.Set(uint32(c))
|
||||
uart.Bus.DR.Set(uint32(c))
|
||||
|
||||
for !stm32.USART1.SR.HasBits(stm32.USART_SR_TXE) {
|
||||
for !uart.Bus.SR.HasBits(stm32.USART_SR_TXE) {
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
//go:export USART1_IRQHandler
|
||||
func handleUART1() {
|
||||
UART1.Receive(byte((stm32.USART1.DR.Get() & 0xFF)))
|
||||
}
|
||||
|
||||
// SPI on the STM32.
|
||||
type SPI struct {
|
||||
Bus *stm32.SPI_Type
|
||||
@@ -202,24 +215,25 @@ func (spi SPI) Configure(config SPIConfig) {
|
||||
|
||||
var conf uint32
|
||||
|
||||
// set frequency
|
||||
// set frequency dependent on PCLK2 prescaler (div 1)
|
||||
switch config.Frequency {
|
||||
case 125000:
|
||||
conf |= stm32.SPI_BaudRatePrescaler_128
|
||||
// Note: impossible to achieve lower frequency with current PCLK2!
|
||||
conf |= stm32.SPI_BaudRatePrescaler_256
|
||||
case 250000:
|
||||
conf |= stm32.SPI_BaudRatePrescaler_64
|
||||
conf |= stm32.SPI_BaudRatePrescaler_256
|
||||
case 500000:
|
||||
conf |= stm32.SPI_BaudRatePrescaler_32
|
||||
case 1000000:
|
||||
conf |= stm32.SPI_BaudRatePrescaler_16
|
||||
case 2000000:
|
||||
conf |= stm32.SPI_BaudRatePrescaler_8
|
||||
case 4000000:
|
||||
conf |= stm32.SPI_BaudRatePrescaler_4
|
||||
case 8000000:
|
||||
conf |= stm32.SPI_BaudRatePrescaler_2
|
||||
default:
|
||||
conf |= stm32.SPI_BaudRatePrescaler_128
|
||||
case 1000000:
|
||||
conf |= stm32.SPI_BaudRatePrescaler_64
|
||||
case 2000000:
|
||||
conf |= stm32.SPI_BaudRatePrescaler_32
|
||||
case 4000000:
|
||||
conf |= stm32.SPI_BaudRatePrescaler_16
|
||||
case 8000000:
|
||||
conf |= stm32.SPI_BaudRatePrescaler_8
|
||||
default:
|
||||
conf |= stm32.SPI_BaudRatePrescaler_256
|
||||
}
|
||||
|
||||
// set bit transfer order
|
||||
@@ -345,11 +359,11 @@ func (i2c I2C) Configure(config I2CConfig) {
|
||||
// Disable the selected I2C peripheral to configure
|
||||
i2c.Bus.CR1.ClearBits(stm32.I2C_CR1_PE)
|
||||
|
||||
// pclk1 clock speed is main frequency divided by PCK1 prescaler (div 2)
|
||||
// pclk1 clock speed is main frequency divided by PCLK1 prescaler (div 2)
|
||||
pclk1 := uint32(CPU_FREQUENCY / 2)
|
||||
|
||||
// set freqency range to pclk1 clock speed in Mhz.
|
||||
// aka setting the value 36 means to use 36MhZ clock.
|
||||
// set freqency range to PCLK1 clock speed in MHz
|
||||
// aka setting the value 36 means to use 36 MHz clock
|
||||
pclk1Mhz := pclk1 / 1000000
|
||||
i2c.Bus.CR2.SetBits(pclk1Mhz)
|
||||
|
||||
|
||||
+1
-6
@@ -1,11 +1,10 @@
|
||||
// +build !stm32f407,!avr,!hifive1b
|
||||
// +build sam stm32,!stm32f407
|
||||
|
||||
package machine
|
||||
|
||||
import "errors"
|
||||
|
||||
var (
|
||||
ErrTxSlicesRequired = errors.New("SPI Tx requires a write or read slice, or both")
|
||||
ErrTxInvalidSliceSize = errors.New("SPI write and read slices must be same size")
|
||||
)
|
||||
|
||||
@@ -28,10 +27,6 @@ var (
|
||||
// spi.Tx(nil, rx)
|
||||
//
|
||||
func (spi SPI) Tx(w, r []byte) error {
|
||||
if w == nil && r == nil {
|
||||
return ErrTxSlicesRequired
|
||||
}
|
||||
|
||||
var err error
|
||||
|
||||
switch {
|
||||
|
||||
+91
-87
@@ -4,7 +4,6 @@ package machine
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"runtime/volatile"
|
||||
)
|
||||
@@ -41,22 +40,26 @@ func NewDeviceDescriptor(class, subClass, proto, packetSize0 uint8, vid, pid, ve
|
||||
|
||||
// Bytes returns DeviceDescriptor data
|
||||
func (d DeviceDescriptor) Bytes() []byte {
|
||||
buf := bytes.NewBuffer(make([]byte, 0, deviceDescriptorSize))
|
||||
binary.Write(buf, binary.LittleEndian, d.bLength)
|
||||
binary.Write(buf, binary.LittleEndian, d.bDescriptorType)
|
||||
binary.Write(buf, binary.LittleEndian, d.bcdUSB)
|
||||
binary.Write(buf, binary.LittleEndian, d.bDeviceClass)
|
||||
binary.Write(buf, binary.LittleEndian, d.bDeviceSubClass)
|
||||
binary.Write(buf, binary.LittleEndian, d.bDeviceProtocol)
|
||||
binary.Write(buf, binary.LittleEndian, d.bMaxPacketSize0)
|
||||
binary.Write(buf, binary.LittleEndian, d.idVendor)
|
||||
binary.Write(buf, binary.LittleEndian, d.idProduct)
|
||||
binary.Write(buf, binary.LittleEndian, d.bcdDevice)
|
||||
binary.Write(buf, binary.LittleEndian, d.iManufacturer)
|
||||
binary.Write(buf, binary.LittleEndian, d.iProduct)
|
||||
binary.Write(buf, binary.LittleEndian, d.iSerialNumber)
|
||||
binary.Write(buf, binary.LittleEndian, d.bNumConfigurations)
|
||||
return buf.Bytes()
|
||||
b := make([]byte, deviceDescriptorSize)
|
||||
b[0] = byte(d.bLength)
|
||||
b[1] = byte(d.bDescriptorType)
|
||||
b[2] = byte(d.bcdUSB)
|
||||
b[3] = byte(d.bcdUSB >> 8)
|
||||
b[4] = byte(d.bDeviceClass)
|
||||
b[5] = byte(d.bDeviceSubClass)
|
||||
b[6] = byte(d.bDeviceProtocol)
|
||||
b[7] = byte(d.bMaxPacketSize0)
|
||||
b[8] = byte(d.idVendor)
|
||||
b[9] = byte(d.idVendor >> 8)
|
||||
b[10] = byte(d.idProduct)
|
||||
b[11] = byte(d.idProduct >> 8)
|
||||
b[12] = byte(d.bcdDevice)
|
||||
b[13] = byte(d.bcdDevice >> 8)
|
||||
b[14] = byte(d.iManufacturer)
|
||||
b[15] = byte(d.iProduct)
|
||||
b[16] = byte(d.iSerialNumber)
|
||||
b[17] = byte(d.bNumConfigurations)
|
||||
return b
|
||||
}
|
||||
|
||||
const configDescriptorSize = 9
|
||||
@@ -85,16 +88,17 @@ func NewConfigDescriptor(totalLength uint16, interfaces uint8) ConfigDescriptor
|
||||
|
||||
// Bytes returns ConfigDescriptor data.
|
||||
func (d ConfigDescriptor) Bytes() []byte {
|
||||
buf := bytes.NewBuffer(make([]byte, 0, configDescriptorSize))
|
||||
binary.Write(buf, binary.LittleEndian, d.bLength)
|
||||
binary.Write(buf, binary.LittleEndian, d.bDescriptorType)
|
||||
binary.Write(buf, binary.LittleEndian, d.wTotalLength)
|
||||
binary.Write(buf, binary.LittleEndian, d.bNumInterfaces)
|
||||
binary.Write(buf, binary.LittleEndian, d.bConfigurationValue)
|
||||
binary.Write(buf, binary.LittleEndian, d.iConfiguration)
|
||||
binary.Write(buf, binary.LittleEndian, d.bmAttributes)
|
||||
binary.Write(buf, binary.LittleEndian, d.bMaxPower)
|
||||
return buf.Bytes()
|
||||
b := make([]byte, configDescriptorSize)
|
||||
b[0] = byte(d.bLength)
|
||||
b[1] = byte(d.bDescriptorType)
|
||||
b[2] = byte(d.wTotalLength)
|
||||
b[3] = byte(d.wTotalLength >> 8)
|
||||
b[4] = byte(d.bNumInterfaces)
|
||||
b[5] = byte(d.bConfigurationValue)
|
||||
b[6] = byte(d.iConfiguration)
|
||||
b[7] = byte(d.bmAttributes)
|
||||
b[8] = byte(d.bMaxPower)
|
||||
return b
|
||||
}
|
||||
|
||||
const interfaceDescriptorSize = 9
|
||||
@@ -124,17 +128,17 @@ func NewInterfaceDescriptor(n, numEndpoints, class, subClass, protocol uint8) In
|
||||
|
||||
// Bytes returns InterfaceDescriptor data.
|
||||
func (d InterfaceDescriptor) Bytes() []byte {
|
||||
buf := bytes.NewBuffer(make([]byte, 0, interfaceDescriptorSize))
|
||||
binary.Write(buf, binary.LittleEndian, d.bLength)
|
||||
binary.Write(buf, binary.LittleEndian, d.bDescriptorType)
|
||||
binary.Write(buf, binary.LittleEndian, d.bInterfaceNumber)
|
||||
binary.Write(buf, binary.LittleEndian, d.bAlternateSetting)
|
||||
binary.Write(buf, binary.LittleEndian, d.bNumEndpoints)
|
||||
binary.Write(buf, binary.LittleEndian, d.bInterfaceClass)
|
||||
binary.Write(buf, binary.LittleEndian, d.bInterfaceSubClass)
|
||||
binary.Write(buf, binary.LittleEndian, d.bInterfaceProtocol)
|
||||
binary.Write(buf, binary.LittleEndian, d.iInterface)
|
||||
return buf.Bytes()
|
||||
b := make([]byte, interfaceDescriptorSize)
|
||||
b[0] = byte(d.bLength)
|
||||
b[1] = byte(d.bDescriptorType)
|
||||
b[2] = byte(d.bInterfaceNumber)
|
||||
b[3] = byte(d.bAlternateSetting)
|
||||
b[4] = byte(d.bNumEndpoints)
|
||||
b[5] = byte(d.bInterfaceClass)
|
||||
b[6] = byte(d.bInterfaceSubClass)
|
||||
b[7] = byte(d.bInterfaceProtocol)
|
||||
b[8] = byte(d.iInterface)
|
||||
return b
|
||||
}
|
||||
|
||||
const endpointDescriptorSize = 7
|
||||
@@ -160,14 +164,15 @@ func NewEndpointDescriptor(addr, attr uint8, packetSize uint16, interval uint8)
|
||||
|
||||
// Bytes returns EndpointDescriptor data.
|
||||
func (d EndpointDescriptor) Bytes() []byte {
|
||||
buf := bytes.NewBuffer(make([]byte, 0, endpointDescriptorSize))
|
||||
binary.Write(buf, binary.LittleEndian, d.bLength)
|
||||
binary.Write(buf, binary.LittleEndian, d.bDescriptorType)
|
||||
binary.Write(buf, binary.LittleEndian, d.bEndpointAddress)
|
||||
binary.Write(buf, binary.LittleEndian, d.bmAttributes)
|
||||
binary.Write(buf, binary.LittleEndian, d.wMaxPacketSize)
|
||||
binary.Write(buf, binary.LittleEndian, d.bInterval)
|
||||
return buf.Bytes()
|
||||
b := make([]byte, endpointDescriptorSize)
|
||||
b[0] = byte(d.bLength)
|
||||
b[1] = byte(d.bDescriptorType)
|
||||
b[2] = byte(d.bEndpointAddress)
|
||||
b[3] = byte(d.bmAttributes)
|
||||
b[4] = byte(d.wMaxPacketSize)
|
||||
b[5] = byte(d.wMaxPacketSize >> 8)
|
||||
b[6] = byte(d.bInterval)
|
||||
return b
|
||||
}
|
||||
|
||||
const iadDescriptorSize = 8
|
||||
@@ -197,16 +202,16 @@ func NewIADDescriptor(firstInterface, count, class, subClass, protocol uint8) IA
|
||||
|
||||
// Bytes returns IADDescriptor data.
|
||||
func (d IADDescriptor) Bytes() []byte {
|
||||
buf := bytes.NewBuffer(make([]byte, 0, iadDescriptorSize))
|
||||
binary.Write(buf, binary.LittleEndian, d.bLength)
|
||||
binary.Write(buf, binary.LittleEndian, d.bDescriptorType)
|
||||
binary.Write(buf, binary.LittleEndian, d.bFirstInterface)
|
||||
binary.Write(buf, binary.LittleEndian, d.bInterfaceCount)
|
||||
binary.Write(buf, binary.LittleEndian, d.bFunctionClass)
|
||||
binary.Write(buf, binary.LittleEndian, d.bFunctionSubClass)
|
||||
binary.Write(buf, binary.LittleEndian, d.bFunctionProtocol)
|
||||
binary.Write(buf, binary.LittleEndian, d.iFunction)
|
||||
return buf.Bytes()
|
||||
b := make([]byte, iadDescriptorSize)
|
||||
b[0] = byte(d.bLength)
|
||||
b[1] = byte(d.bDescriptorType)
|
||||
b[2] = byte(d.bFirstInterface)
|
||||
b[3] = byte(d.bInterfaceCount)
|
||||
b[4] = byte(d.bFunctionClass)
|
||||
b[5] = byte(d.bFunctionSubClass)
|
||||
b[6] = byte(d.bFunctionProtocol)
|
||||
b[7] = byte(d.iFunction)
|
||||
return b
|
||||
}
|
||||
|
||||
const cdcCSInterfaceDescriptorSize = 5
|
||||
@@ -227,13 +232,13 @@ func NewCDCCSInterfaceDescriptor(subtype, d0, d1 uint8) CDCCSInterfaceDescriptor
|
||||
|
||||
// Bytes returns CDCCSInterfaceDescriptor data.
|
||||
func (d CDCCSInterfaceDescriptor) Bytes() []byte {
|
||||
buf := bytes.NewBuffer(make([]byte, 0, cdcCSInterfaceDescriptorSize))
|
||||
binary.Write(buf, binary.LittleEndian, d.len)
|
||||
binary.Write(buf, binary.LittleEndian, d.dtype)
|
||||
binary.Write(buf, binary.LittleEndian, d.subtype)
|
||||
binary.Write(buf, binary.LittleEndian, d.d0)
|
||||
binary.Write(buf, binary.LittleEndian, d.d1)
|
||||
return buf.Bytes()
|
||||
b := make([]byte, cdcCSInterfaceDescriptorSize)
|
||||
b[0] = byte(d.len)
|
||||
b[1] = byte(d.dtype)
|
||||
b[2] = byte(d.subtype)
|
||||
b[3] = byte(d.d0)
|
||||
b[4] = byte(d.d1)
|
||||
return b
|
||||
}
|
||||
|
||||
const cmFunctionalDescriptorSize = 5
|
||||
@@ -254,13 +259,13 @@ func NewCMFunctionalDescriptor(subtype, d0, d1 uint8) CMFunctionalDescriptor {
|
||||
|
||||
// Bytes returns the CMFunctionalDescriptor data.
|
||||
func (d CMFunctionalDescriptor) Bytes() []byte {
|
||||
buf := bytes.NewBuffer(make([]byte, 0, cmFunctionalDescriptorSize))
|
||||
binary.Write(buf, binary.LittleEndian, d.bFunctionLength)
|
||||
binary.Write(buf, binary.LittleEndian, d.bDescriptorType)
|
||||
binary.Write(buf, binary.LittleEndian, d.bDescriptorSubtype)
|
||||
binary.Write(buf, binary.LittleEndian, d.bmCapabilities)
|
||||
binary.Write(buf, binary.LittleEndian, d.bDataInterface)
|
||||
return buf.Bytes()
|
||||
b := make([]byte, cmFunctionalDescriptorSize)
|
||||
b[0] = byte(d.bFunctionLength)
|
||||
b[1] = byte(d.bDescriptorType)
|
||||
b[2] = byte(d.bDescriptorSubtype)
|
||||
b[3] = byte(d.bmCapabilities)
|
||||
b[4] = byte(d.bDescriptorSubtype)
|
||||
return b
|
||||
}
|
||||
|
||||
const acmFunctionalDescriptorSize = 4
|
||||
@@ -280,12 +285,12 @@ func NewACMFunctionalDescriptor(subtype, d0 uint8) ACMFunctionalDescriptor {
|
||||
|
||||
// Bytes returns the ACMFunctionalDescriptor data.
|
||||
func (d ACMFunctionalDescriptor) Bytes() []byte {
|
||||
buf := bytes.NewBuffer(make([]byte, 0, acmFunctionalDescriptorSize))
|
||||
binary.Write(buf, binary.LittleEndian, d.len)
|
||||
binary.Write(buf, binary.LittleEndian, d.dtype)
|
||||
binary.Write(buf, binary.LittleEndian, d.subtype)
|
||||
binary.Write(buf, binary.LittleEndian, d.bmCapabilities)
|
||||
return buf.Bytes()
|
||||
b := make([]byte, acmFunctionalDescriptorSize)
|
||||
b[0] = byte(d.len)
|
||||
b[1] = byte(d.dtype)
|
||||
b[2] = byte(d.subtype)
|
||||
b[3] = byte(d.bmCapabilities)
|
||||
return b
|
||||
}
|
||||
|
||||
// CDCDescriptor is the Communication Device Class (CDC) descriptor.
|
||||
@@ -316,8 +321,8 @@ func NewCDCDescriptor(i IADDescriptor, c InterfaceDescriptor,
|
||||
callm CMFunctionalDescriptor,
|
||||
ci EndpointDescriptor,
|
||||
di InterfaceDescriptor,
|
||||
inp EndpointDescriptor,
|
||||
outp EndpointDescriptor) CDCDescriptor {
|
||||
outp EndpointDescriptor,
|
||||
inp EndpointDescriptor) CDCDescriptor {
|
||||
return CDCDescriptor{iad: i,
|
||||
cif: c,
|
||||
header: h,
|
||||
@@ -343,7 +348,7 @@ const cdcSize = iadDescriptorSize +
|
||||
|
||||
// Bytes returns CDCDescriptor data.
|
||||
func (d CDCDescriptor) Bytes() []byte {
|
||||
buf := bytes.NewBuffer(make([]byte, 0, cdcSize))
|
||||
buf := bytes.NewBuffer(make([]byte, 0))
|
||||
buf.Write(d.iad.Bytes())
|
||||
buf.Write(d.cif.Bytes())
|
||||
buf.Write(d.header.Bytes())
|
||||
@@ -352,8 +357,8 @@ func (d CDCDescriptor) Bytes() []byte {
|
||||
buf.Write(d.callManagement.Bytes())
|
||||
buf.Write(d.cifin.Bytes())
|
||||
buf.Write(d.dif.Bytes())
|
||||
buf.Write(d.in.Bytes())
|
||||
buf.Write(d.out.Bytes())
|
||||
buf.Write(d.in.Bytes())
|
||||
return buf.Bytes()
|
||||
}
|
||||
|
||||
@@ -523,14 +528,13 @@ type usbSetup struct {
|
||||
}
|
||||
|
||||
func newUSBSetup(data []byte) usbSetup {
|
||||
buf := bytes.NewBuffer(data)
|
||||
u := usbSetup{}
|
||||
binary.Read(buf, binary.LittleEndian, &(u.bmRequestType))
|
||||
binary.Read(buf, binary.LittleEndian, &(u.bRequest))
|
||||
binary.Read(buf, binary.LittleEndian, &(u.wValueL))
|
||||
binary.Read(buf, binary.LittleEndian, &(u.wValueH))
|
||||
binary.Read(buf, binary.LittleEndian, &(u.wIndex))
|
||||
binary.Read(buf, binary.LittleEndian, &(u.wLength))
|
||||
u.bmRequestType = uint8(data[0])
|
||||
u.bRequest = uint8(data[1])
|
||||
u.wValueL = uint8(data[2])
|
||||
u.wValueH = uint8(data[3])
|
||||
u.wIndex = uint16(data[4]) | uint16(data[5]<<8)
|
||||
u.wLength = uint16(data[6]) | uint16(data[7]<<8)
|
||||
return u
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// +build avr cortexm tinygo.riscv wasm
|
||||
// +build baremetal wasm
|
||||
|
||||
package os
|
||||
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
// +build darwin linux,!avr,!cortexm,!tinygo.riscv
|
||||
// +build darwin linux,!baremetal
|
||||
|
||||
package os
|
||||
|
||||
|
||||
@@ -0,0 +1,60 @@
|
||||
package reflect
|
||||
|
||||
import (
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
// This stores a varint for each named type. Named types are identified by their
|
||||
// name instead of by their type. The named types stored in this struct are
|
||||
// non-basic types: pointer, struct, and channel.
|
||||
//go:extern reflect.namedNonBasicTypesSidetable
|
||||
var namedNonBasicTypesSidetable uintptr
|
||||
|
||||
//go:extern reflect.structTypesSidetable
|
||||
var structTypesSidetable byte
|
||||
|
||||
//go:extern reflect.structNamesSidetable
|
||||
var structNamesSidetable byte
|
||||
|
||||
//go:extern reflect.arrayTypesSidetable
|
||||
var arrayTypesSidetable byte
|
||||
|
||||
// readStringSidetable reads a string from the given table (like
|
||||
// structNamesSidetable) and returns this string. No heap allocation is
|
||||
// necessary because it makes the string point directly to the raw bytes of the
|
||||
// table.
|
||||
func readStringSidetable(table unsafe.Pointer, index uintptr) string {
|
||||
nameLen, namePtr := readVarint(unsafe.Pointer(uintptr(table) + index))
|
||||
return *(*string)(unsafe.Pointer(&StringHeader{
|
||||
Data: uintptr(namePtr),
|
||||
Len: nameLen,
|
||||
}))
|
||||
}
|
||||
|
||||
// readVarint decodes a varint as used in the encoding/binary package.
|
||||
// It has an input pointer and returns the read varint and the pointer
|
||||
// incremented to the next field in the data structure, just after the varint.
|
||||
//
|
||||
// Details:
|
||||
// https://github.com/golang/go/blob/e37a1b1c/src/encoding/binary/varint.go#L7-L25
|
||||
func readVarint(buf unsafe.Pointer) (uintptr, unsafe.Pointer) {
|
||||
var n uintptr
|
||||
shift := uintptr(0)
|
||||
for {
|
||||
// Read the next byte in the buffer.
|
||||
c := *(*byte)(buf)
|
||||
|
||||
// Decode the bits from this byte and add them to the output number.
|
||||
n |= uintptr(c&0x7f) << shift
|
||||
shift += 7
|
||||
|
||||
// Increment the buf pointer (pointer arithmetic!).
|
||||
buf = unsafe.Pointer(uintptr(buf) + 1)
|
||||
|
||||
// Check whether this is the last byte of this varint. The upper bit
|
||||
// (msb) indicates whether any bytes follow.
|
||||
if c>>7 == 0 {
|
||||
return n, buf
|
||||
}
|
||||
}
|
||||
}
|
||||
+276
-12
@@ -142,34 +142,153 @@ func (t Type) Kind() Kind {
|
||||
}
|
||||
}
|
||||
|
||||
// Elem returns the element type for channel, slice and array types, the
|
||||
// pointed-to value for pointer types, and the key type for map types.
|
||||
func (t Type) Elem() Type {
|
||||
switch t.Kind() {
|
||||
case Chan, Ptr, Slice:
|
||||
if (t>>4)%2 != 0 {
|
||||
panic("unimplemented: (reflect.Type).Elem() for named types")
|
||||
}
|
||||
return t >> 5
|
||||
default: // not implemented: Array, Map
|
||||
return t.stripPrefix()
|
||||
case Array:
|
||||
index := t.stripPrefix()
|
||||
elem, _ := readVarint(unsafe.Pointer(uintptr(unsafe.Pointer(&arrayTypesSidetable)) + uintptr(index)))
|
||||
return Type(elem)
|
||||
default: // not implemented: Map
|
||||
panic("unimplemented: (reflect.Type).Elem()")
|
||||
}
|
||||
}
|
||||
|
||||
// stripPrefix removes the "prefix" (the first 5 bytes of the type code) from
|
||||
// the type code. If this is a named type, it will resolve the underlying type
|
||||
// (which is the data for this named type). If it is not, the lower bits are
|
||||
// simply shifted off.
|
||||
//
|
||||
// The behavior is only defined for non-basic types.
|
||||
func (t Type) stripPrefix() Type {
|
||||
// Look at the 'n' bit in the type code (see the top of this file) to see
|
||||
// whether this is a named type.
|
||||
if (t>>4)%2 != 0 {
|
||||
// This is a named type. The data is stored in a sidetable.
|
||||
namedTypeNum := t >> 5
|
||||
n := *(*uintptr)(unsafe.Pointer(uintptr(unsafe.Pointer(&namedNonBasicTypesSidetable)) + uintptr(namedTypeNum)*unsafe.Sizeof(uintptr(0))))
|
||||
return Type(n)
|
||||
}
|
||||
// Not a named type, so the value is stored directly in the type code.
|
||||
return t >> 5
|
||||
}
|
||||
|
||||
// Field returns the type of the i'th field of this struct type. It panics if t
|
||||
// is not a struct type.
|
||||
func (t Type) Field(i int) StructField {
|
||||
panic("unimplemented: (reflect.Type).Field()")
|
||||
if t.Kind() != Struct {
|
||||
panic(&TypeError{"Field"})
|
||||
}
|
||||
structIdentifier := t.stripPrefix()
|
||||
|
||||
numField, p := readVarint(unsafe.Pointer(uintptr(unsafe.Pointer(&structTypesSidetable)) + uintptr(structIdentifier)))
|
||||
if uint(i) >= uint(numField) {
|
||||
panic("reflect: field index out of range")
|
||||
}
|
||||
|
||||
// Iterate over every field in the struct and update the StructField each
|
||||
// time, until the target field has been reached. This is very much not
|
||||
// efficient, but it is easy to implement.
|
||||
// Adding a jump table at the start to jump to the field directly would
|
||||
// make this much faster, but that would also impact code size.
|
||||
field := StructField{}
|
||||
offset := uintptr(0)
|
||||
for fieldNum := 0; fieldNum <= i; fieldNum++ {
|
||||
// Read some flags of this field, like whether the field is an
|
||||
// embedded field.
|
||||
flagsByte := *(*uint8)(p)
|
||||
p = unsafe.Pointer(uintptr(p) + 1)
|
||||
|
||||
// Read the type of this struct field.
|
||||
var fieldType uintptr
|
||||
fieldType, p = readVarint(p)
|
||||
field.Type = Type(fieldType)
|
||||
|
||||
// Move Offset forward to align it to this field's alignment.
|
||||
// Assume alignment is a power of two.
|
||||
offset = align(offset, uintptr(field.Type.Align()))
|
||||
field.Offset = offset
|
||||
offset += field.Type.Size() // starting (unaligned) offset for next field
|
||||
|
||||
// Read the field name.
|
||||
var nameNum uintptr
|
||||
nameNum, p = readVarint(p)
|
||||
field.Name = readStringSidetable(unsafe.Pointer(&structNamesSidetable), nameNum)
|
||||
|
||||
// The first bit in the flagsByte indicates whether this is an embedded
|
||||
// field.
|
||||
field.Anonymous = flagsByte&1 != 0
|
||||
|
||||
// The second bit indicates whether there is a tag.
|
||||
if flagsByte&2 != 0 {
|
||||
// There is a tag.
|
||||
var tagNum uintptr
|
||||
tagNum, p = readVarint(p)
|
||||
field.Tag = readStringSidetable(unsafe.Pointer(&structNamesSidetable), tagNum)
|
||||
} else {
|
||||
// There is no tag.
|
||||
field.Tag = ""
|
||||
}
|
||||
|
||||
// The third bit indicates whether this field is exported.
|
||||
if flagsByte&4 != 0 {
|
||||
// This field is exported.
|
||||
field.PkgPath = ""
|
||||
} else {
|
||||
// This field is unexported.
|
||||
// TODO: list the real package path here. Storing it should not
|
||||
// significantly impact binary size as there is only a limited
|
||||
// number of packages in any program.
|
||||
field.PkgPath = "<unimplemented>"
|
||||
}
|
||||
}
|
||||
|
||||
return field
|
||||
}
|
||||
|
||||
// Bits returns the number of bits that this type uses. It is only valid for
|
||||
// arithmetic types (integers, floats, and complex numbers). For other types, it
|
||||
// will panic.
|
||||
func (t Type) Bits() int {
|
||||
panic("unimplemented: (reflect.Type).Bits()")
|
||||
kind := t.Kind()
|
||||
if kind >= Int && kind <= Complex128 {
|
||||
return int(t.Size()) * 8
|
||||
}
|
||||
panic(TypeError{"Bits"})
|
||||
}
|
||||
|
||||
// Len returns the number of elements in this array. It panics of the type kind
|
||||
// is not Array.
|
||||
func (t Type) Len() int {
|
||||
panic("unimplemented: (reflect.Type).Len()")
|
||||
if t.Kind() != Array {
|
||||
panic(TypeError{"Len"})
|
||||
}
|
||||
|
||||
// skip past the element type
|
||||
arrayIdentifier := t.stripPrefix()
|
||||
_, p := readVarint(unsafe.Pointer(uintptr(unsafe.Pointer(&arrayTypesSidetable)) + uintptr(arrayIdentifier)))
|
||||
|
||||
// Read the array length.
|
||||
arrayLen, _ := readVarint(p)
|
||||
return int(arrayLen)
|
||||
}
|
||||
|
||||
// NumField returns the number of fields of a struct type. It panics for other
|
||||
// type kinds.
|
||||
func (t Type) NumField() int {
|
||||
panic("unimplemented: (reflect.Type).NumField()")
|
||||
if t.Kind() != Struct {
|
||||
panic(&TypeError{"NumField"})
|
||||
}
|
||||
structIdentifier := t.stripPrefix()
|
||||
n, _ := readVarint(unsafe.Pointer(uintptr(unsafe.Pointer(&structTypesSidetable)) + uintptr(structIdentifier)))
|
||||
return int(n)
|
||||
}
|
||||
|
||||
// Size returns the size in bytes of a given type. It is similar to
|
||||
// unsafe.Sizeof.
|
||||
func (t Type) Size() uintptr {
|
||||
switch t.Kind() {
|
||||
case Bool, Int8, Uint8:
|
||||
@@ -198,12 +317,157 @@ func (t Type) Size() uintptr {
|
||||
return unsafe.Sizeof(uintptr(0))
|
||||
case Slice:
|
||||
return unsafe.Sizeof(SliceHeader{})
|
||||
case Interface:
|
||||
return unsafe.Sizeof(interface{}(nil))
|
||||
case Array:
|
||||
return t.Elem().Size() * uintptr(t.Len())
|
||||
case Struct:
|
||||
numField := t.NumField()
|
||||
if numField == 0 {
|
||||
return 0
|
||||
}
|
||||
lastField := t.Field(numField - 1)
|
||||
return lastField.Offset + lastField.Type.Size()
|
||||
default:
|
||||
panic("unimplemented: size of type")
|
||||
}
|
||||
}
|
||||
|
||||
type StructField struct {
|
||||
Name string
|
||||
Type Type
|
||||
// Align returns the alignment of this type. It is similar to calling
|
||||
// unsafe.Alignof.
|
||||
func (t Type) Align() int {
|
||||
switch t.Kind() {
|
||||
case Bool, Int8, Uint8:
|
||||
return int(unsafe.Alignof(int8(0)))
|
||||
case Int16, Uint16:
|
||||
return int(unsafe.Alignof(int16(0)))
|
||||
case Int32, Uint32:
|
||||
return int(unsafe.Alignof(int32(0)))
|
||||
case Int64, Uint64:
|
||||
return int(unsafe.Alignof(int64(0)))
|
||||
case Int, Uint:
|
||||
return int(unsafe.Alignof(int(0)))
|
||||
case Uintptr:
|
||||
return int(unsafe.Alignof(uintptr(0)))
|
||||
case Float32:
|
||||
return int(unsafe.Alignof(float32(0)))
|
||||
case Float64:
|
||||
return int(unsafe.Alignof(float64(0)))
|
||||
case Complex64:
|
||||
return int(unsafe.Alignof(complex64(0)))
|
||||
case Complex128:
|
||||
return int(unsafe.Alignof(complex128(0)))
|
||||
case String:
|
||||
return int(unsafe.Alignof(StringHeader{}))
|
||||
case UnsafePointer, Chan, Map, Ptr:
|
||||
return int(unsafe.Alignof(uintptr(0)))
|
||||
case Slice:
|
||||
return int(unsafe.Alignof(SliceHeader{}))
|
||||
case Interface:
|
||||
return int(unsafe.Alignof(interface{}(nil)))
|
||||
case Struct:
|
||||
numField := t.NumField()
|
||||
alignment := 1
|
||||
for i := 0; i < numField; i++ {
|
||||
fieldAlignment := t.Field(i).Type.Align()
|
||||
if fieldAlignment > alignment {
|
||||
alignment = fieldAlignment
|
||||
}
|
||||
}
|
||||
return alignment
|
||||
default:
|
||||
panic("unimplemented: alignment of type")
|
||||
}
|
||||
}
|
||||
|
||||
// FieldAlign returns the alignment if this type is used in a struct field. It
|
||||
// is currently an alias for Align() but this might change in the future.
|
||||
func (t Type) FieldAlign() int {
|
||||
return t.Align()
|
||||
}
|
||||
|
||||
// AssignableTo returns whether a value of type u can be assigned to a variable
|
||||
// of type t.
|
||||
func (t Type) AssignableTo(u Type) bool {
|
||||
if t == u {
|
||||
return true
|
||||
}
|
||||
if t.Kind() == Interface {
|
||||
panic("reflect: unimplemented: assigning to interface of different type")
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (t Type) Implements(u Type) bool {
|
||||
if t.Kind() != Interface {
|
||||
panic("reflect: non-interface type passed to Type.Implements")
|
||||
}
|
||||
return u.AssignableTo(t)
|
||||
}
|
||||
|
||||
// Comparable returns whether values of this type can be compared to each other.
|
||||
func (t Type) Comparable() bool {
|
||||
switch t.Kind() {
|
||||
case Bool, Int, Int8, Int16, Int32, Int64, Uint, Uint8, Uint16, Uint32, Uint64, Uintptr:
|
||||
return true
|
||||
case Float32, Float64, Complex64, Complex128:
|
||||
return true
|
||||
case String:
|
||||
return true
|
||||
case UnsafePointer:
|
||||
return true
|
||||
case Chan:
|
||||
return true
|
||||
case Interface:
|
||||
return true
|
||||
case Ptr:
|
||||
return true
|
||||
case Slice:
|
||||
return false
|
||||
case Array:
|
||||
return t.Elem().Comparable()
|
||||
case Func:
|
||||
return false
|
||||
case Map:
|
||||
return false
|
||||
case Struct:
|
||||
numField := t.NumField()
|
||||
for i := 0; i < numField; i++ {
|
||||
if !t.Field(i).Type.Comparable() {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
default:
|
||||
panic(TypeError{"Comparable"})
|
||||
}
|
||||
}
|
||||
|
||||
// A StructField describes a single field in a struct.
|
||||
type StructField struct {
|
||||
// Name indicates the field name.
|
||||
Name string
|
||||
|
||||
// PkgPath is the package path where the struct containing this field is
|
||||
// declared for unexported fields, or the empty string for exported fields.
|
||||
PkgPath string
|
||||
|
||||
Type Type
|
||||
Tag string
|
||||
Anonymous bool
|
||||
Offset uintptr
|
||||
}
|
||||
|
||||
// TypeError is the error that is used in a panic when invoking a method on a
|
||||
// type that is not applicable to that type.
|
||||
type TypeError struct {
|
||||
Method string
|
||||
}
|
||||
|
||||
func (e *TypeError) Error() string {
|
||||
return "reflect: call of reflect.Type." + e.Method + " on invalid type"
|
||||
}
|
||||
|
||||
func align(offset uintptr, alignment uintptr) uintptr {
|
||||
return (offset + alignment - 1) &^ (alignment - 1)
|
||||
}
|
||||
|
||||
+208
-70
@@ -4,10 +4,28 @@ import (
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
type valueFlags uint8
|
||||
|
||||
// Flags list some useful flags that contain some extra information not
|
||||
// contained in an interface{} directly, like whether this value was exported at
|
||||
// all (it is possible to read unexported fields using reflection, but it is not
|
||||
// possible to modify them).
|
||||
const (
|
||||
valueFlagIndirect valueFlags = 1 << iota
|
||||
valueFlagExported
|
||||
)
|
||||
|
||||
type Value struct {
|
||||
typecode Type
|
||||
value unsafe.Pointer
|
||||
indirect bool
|
||||
flags valueFlags
|
||||
}
|
||||
|
||||
// isIndirect returns whether the value pointer in this Value is always a
|
||||
// pointer to the value. If it is false, it is only a pointer to the value if
|
||||
// the value is bigger than a pointer.
|
||||
func (v Value) isIndirect() bool {
|
||||
return v.flags&valueFlagIndirect != 0
|
||||
}
|
||||
|
||||
func Indirect(v Value) Value {
|
||||
@@ -17,29 +35,32 @@ func Indirect(v Value) Value {
|
||||
return v.Elem()
|
||||
}
|
||||
|
||||
//go:linkname composeInterface runtime.composeInterface
|
||||
func composeInterface(Type, unsafe.Pointer) interface{}
|
||||
|
||||
//go:linkname decomposeInterface runtime.decomposeInterface
|
||||
func decomposeInterface(i interface{}) (Type, unsafe.Pointer)
|
||||
|
||||
func ValueOf(i interface{}) Value {
|
||||
v := (*interfaceHeader)(unsafe.Pointer(&i))
|
||||
typecode, value := decomposeInterface(i)
|
||||
return Value{
|
||||
typecode: v.typecode,
|
||||
value: v.value,
|
||||
typecode: typecode,
|
||||
value: value,
|
||||
flags: valueFlagExported,
|
||||
}
|
||||
}
|
||||
|
||||
func (v Value) Interface() interface{} {
|
||||
i := interfaceHeader{
|
||||
typecode: v.typecode,
|
||||
value: v.value,
|
||||
}
|
||||
if v.indirect && v.Type().Size() <= unsafe.Sizeof(uintptr(0)) {
|
||||
if v.isIndirect() && v.Type().Size() <= unsafe.Sizeof(uintptr(0)) {
|
||||
// Value was indirect but must be put back directly in the interface
|
||||
// value.
|
||||
var value uintptr
|
||||
for j := v.Type().Size(); j != 0; j-- {
|
||||
value = (value << 8) | uintptr(*(*uint8)(unsafe.Pointer(uintptr(v.value) + j - 1)))
|
||||
}
|
||||
i.value = unsafe.Pointer(value)
|
||||
v.value = unsafe.Pointer(value)
|
||||
}
|
||||
return *(*interface{})(unsafe.Pointer(&i))
|
||||
return composeInterface(v.typecode, v.value)
|
||||
}
|
||||
|
||||
func (v Value) Type() Type {
|
||||
@@ -50,9 +71,14 @@ func (v Value) Kind() Kind {
|
||||
return v.Type().Kind()
|
||||
}
|
||||
|
||||
// IsNil returns whether the value is the nil value. It panics if the value Kind
|
||||
// is not a channel, map, pointer, function, slice, or interface.
|
||||
func (v Value) IsNil() bool {
|
||||
switch v.Kind() {
|
||||
case Chan, Map, Ptr:
|
||||
if v.isIndirect() {
|
||||
return *(*uintptr)(v.value) == 0
|
||||
}
|
||||
return v.value == nil
|
||||
case Func:
|
||||
if v.value == nil {
|
||||
@@ -70,16 +96,21 @@ func (v Value) IsNil() bool {
|
||||
if v.value == nil {
|
||||
return true
|
||||
}
|
||||
itf := (*interfaceHeader)(v.value)
|
||||
return itf.value == nil
|
||||
_, val := decomposeInterface(*(*interface{})(v.value))
|
||||
return val == nil
|
||||
default:
|
||||
panic(&ValueError{"IsNil"})
|
||||
}
|
||||
}
|
||||
|
||||
// Pointer returns the underlying pointer of the given value for the following
|
||||
// types: chan, map, pointer, unsafe.Pointer, slice, func.
|
||||
func (v Value) Pointer() uintptr {
|
||||
switch v.Kind() {
|
||||
case Chan, Map, Ptr, UnsafePointer:
|
||||
if v.isIndirect() {
|
||||
return *(*uintptr)(v.value)
|
||||
}
|
||||
return uintptr(v.value)
|
||||
case Slice:
|
||||
slice := (*SliceHeader)(v.value)
|
||||
@@ -109,13 +140,13 @@ func (v Value) Addr() Value {
|
||||
}
|
||||
|
||||
func (v Value) CanSet() bool {
|
||||
return v.indirect
|
||||
return v.flags&(valueFlagExported|valueFlagIndirect) == valueFlagExported|valueFlagIndirect
|
||||
}
|
||||
|
||||
func (v Value) Bool() bool {
|
||||
switch v.Kind() {
|
||||
case Bool:
|
||||
if v.indirect {
|
||||
if v.isIndirect() {
|
||||
return *((*bool)(v.value))
|
||||
} else {
|
||||
return uintptr(v.value) != 0
|
||||
@@ -128,31 +159,31 @@ func (v Value) Bool() bool {
|
||||
func (v Value) Int() int64 {
|
||||
switch v.Kind() {
|
||||
case Int:
|
||||
if v.indirect || unsafe.Sizeof(int(0)) > unsafe.Sizeof(uintptr(0)) {
|
||||
if v.isIndirect() || unsafe.Sizeof(int(0)) > unsafe.Sizeof(uintptr(0)) {
|
||||
return int64(*(*int)(v.value))
|
||||
} else {
|
||||
return int64(int(uintptr(v.value)))
|
||||
}
|
||||
case Int8:
|
||||
if v.indirect {
|
||||
if v.isIndirect() {
|
||||
return int64(*(*int8)(v.value))
|
||||
} else {
|
||||
return int64(int8(uintptr(v.value)))
|
||||
}
|
||||
case Int16:
|
||||
if v.indirect {
|
||||
if v.isIndirect() {
|
||||
return int64(*(*int16)(v.value))
|
||||
} else {
|
||||
return int64(int16(uintptr(v.value)))
|
||||
}
|
||||
case Int32:
|
||||
if v.indirect || unsafe.Sizeof(int32(0)) > unsafe.Sizeof(uintptr(0)) {
|
||||
if v.isIndirect() || unsafe.Sizeof(int32(0)) > unsafe.Sizeof(uintptr(0)) {
|
||||
return int64(*(*int32)(v.value))
|
||||
} else {
|
||||
return int64(int32(uintptr(v.value)))
|
||||
}
|
||||
case Int64:
|
||||
if v.indirect || unsafe.Sizeof(int64(0)) > unsafe.Sizeof(uintptr(0)) {
|
||||
if v.isIndirect() || unsafe.Sizeof(int64(0)) > unsafe.Sizeof(uintptr(0)) {
|
||||
return int64(*(*int64)(v.value))
|
||||
} else {
|
||||
return int64(int64(uintptr(v.value)))
|
||||
@@ -165,37 +196,37 @@ func (v Value) Int() int64 {
|
||||
func (v Value) Uint() uint64 {
|
||||
switch v.Kind() {
|
||||
case Uintptr:
|
||||
if v.indirect {
|
||||
if v.isIndirect() {
|
||||
return uint64(*(*uintptr)(v.value))
|
||||
} else {
|
||||
return uint64(uintptr(v.value))
|
||||
}
|
||||
case Uint8:
|
||||
if v.indirect {
|
||||
if v.isIndirect() {
|
||||
return uint64(*(*uint8)(v.value))
|
||||
} else {
|
||||
return uint64(uintptr(v.value))
|
||||
}
|
||||
case Uint16:
|
||||
if v.indirect {
|
||||
if v.isIndirect() {
|
||||
return uint64(*(*uint16)(v.value))
|
||||
} else {
|
||||
return uint64(uintptr(v.value))
|
||||
}
|
||||
case Uint:
|
||||
if v.indirect || unsafe.Sizeof(uint(0)) > unsafe.Sizeof(uintptr(0)) {
|
||||
if v.isIndirect() || unsafe.Sizeof(uint(0)) > unsafe.Sizeof(uintptr(0)) {
|
||||
return uint64(*(*uint)(v.value))
|
||||
} else {
|
||||
return uint64(uintptr(v.value))
|
||||
}
|
||||
case Uint32:
|
||||
if v.indirect || unsafe.Sizeof(uint32(0)) > unsafe.Sizeof(uintptr(0)) {
|
||||
if v.isIndirect() || unsafe.Sizeof(uint32(0)) > unsafe.Sizeof(uintptr(0)) {
|
||||
return uint64(*(*uint32)(v.value))
|
||||
} else {
|
||||
return uint64(uintptr(v.value))
|
||||
}
|
||||
case Uint64:
|
||||
if v.indirect || unsafe.Sizeof(uint64(0)) > unsafe.Sizeof(uintptr(0)) {
|
||||
if v.isIndirect() || unsafe.Sizeof(uint64(0)) > unsafe.Sizeof(uintptr(0)) {
|
||||
return uint64(*(*uint64)(v.value))
|
||||
} else {
|
||||
return uint64(uintptr(v.value))
|
||||
@@ -208,7 +239,7 @@ func (v Value) Uint() uint64 {
|
||||
func (v Value) Float() float64 {
|
||||
switch v.Kind() {
|
||||
case Float32:
|
||||
if v.indirect || unsafe.Sizeof(float32(0)) > unsafe.Sizeof(uintptr(0)) {
|
||||
if v.isIndirect() || unsafe.Sizeof(float32(0)) > unsafe.Sizeof(uintptr(0)) {
|
||||
// The float is stored as an external value on systems with 16-bit
|
||||
// pointers.
|
||||
return float64(*(*float32)(v.value))
|
||||
@@ -218,7 +249,7 @@ func (v Value) Float() float64 {
|
||||
return float64(*(*float32)(unsafe.Pointer(&v.value)))
|
||||
}
|
||||
case Float64:
|
||||
if v.indirect || unsafe.Sizeof(float64(0)) > unsafe.Sizeof(uintptr(0)) {
|
||||
if v.isIndirect() || unsafe.Sizeof(float64(0)) > unsafe.Sizeof(uintptr(0)) {
|
||||
// For systems with 16-bit and 32-bit pointers.
|
||||
return *(*float64)(v.value)
|
||||
} else {
|
||||
@@ -234,7 +265,7 @@ func (v Value) Float() float64 {
|
||||
func (v Value) Complex() complex128 {
|
||||
switch v.Kind() {
|
||||
case Complex64:
|
||||
if v.indirect || unsafe.Sizeof(complex64(0)) > unsafe.Sizeof(uintptr(0)) {
|
||||
if v.isIndirect() || unsafe.Sizeof(complex64(0)) > unsafe.Sizeof(uintptr(0)) {
|
||||
// The complex number is stored as an external value on systems with
|
||||
// 16-bit and 32-bit pointers.
|
||||
return complex128(*(*complex64)(v.value))
|
||||
@@ -273,6 +304,8 @@ func (v Value) Slice(i, j int) Value {
|
||||
panic("unimplemented: (reflect.Value).Slice()")
|
||||
}
|
||||
|
||||
// Len returns the length of this value for slices, strings, arrays, channels,
|
||||
// and maps. For oter types, it panics.
|
||||
func (v Value) Len() int {
|
||||
t := v.Type()
|
||||
switch t.Kind() {
|
||||
@@ -280,7 +313,9 @@ func (v Value) Len() int {
|
||||
return int((*SliceHeader)(v.value).Len)
|
||||
case String:
|
||||
return int((*StringHeader)(v.value).Len)
|
||||
default: // Array, Chan, Map
|
||||
case Array:
|
||||
return v.Type().Len()
|
||||
default: // Chan, Map
|
||||
panic("unimplemented: (reflect.Value).Len()")
|
||||
}
|
||||
}
|
||||
@@ -295,15 +330,17 @@ func (v Value) Cap() int {
|
||||
}
|
||||
}
|
||||
|
||||
// NumField returns the number of fields of this struct. It panics for other
|
||||
// value types.
|
||||
func (v Value) NumField() int {
|
||||
panic("unimplemented: (reflect.Value).NumField()")
|
||||
return v.Type().NumField()
|
||||
}
|
||||
|
||||
func (v Value) Elem() Value {
|
||||
switch v.Kind() {
|
||||
case Ptr:
|
||||
ptr := v.value
|
||||
if v.indirect {
|
||||
if v.isIndirect() {
|
||||
ptr = *(*unsafe.Pointer)(ptr)
|
||||
}
|
||||
if ptr == nil {
|
||||
@@ -312,15 +349,71 @@ func (v Value) Elem() Value {
|
||||
return Value{
|
||||
typecode: v.Type().Elem(),
|
||||
value: ptr,
|
||||
indirect: true,
|
||||
flags: v.flags | valueFlagIndirect,
|
||||
}
|
||||
default: // not implemented: Interface
|
||||
panic(&ValueError{"Elem"})
|
||||
}
|
||||
}
|
||||
|
||||
// Field returns the value of the i'th field of this struct.
|
||||
func (v Value) Field(i int) Value {
|
||||
panic("unimplemented: (reflect.Value).Field()")
|
||||
structField := v.Type().Field(i)
|
||||
flags := v.flags
|
||||
if structField.PkgPath != "" {
|
||||
// The fact that PkgPath is present means that this field is not
|
||||
// exported.
|
||||
flags &^= valueFlagExported
|
||||
}
|
||||
|
||||
size := v.Type().Size()
|
||||
fieldSize := structField.Type.Size()
|
||||
if v.isIndirect() || fieldSize > unsafe.Sizeof(uintptr(0)) {
|
||||
// v.value was already a pointer to the value and it should stay that
|
||||
// way.
|
||||
return Value{
|
||||
flags: flags,
|
||||
typecode: structField.Type,
|
||||
value: unsafe.Pointer(uintptr(v.value) + structField.Offset),
|
||||
}
|
||||
}
|
||||
|
||||
// The fieldSize is smaller than uintptr, which means that the value will
|
||||
// have to be stored directly in the interface value.
|
||||
|
||||
if fieldSize == 0 {
|
||||
// The struct field is zero sized.
|
||||
// This is a rare situation, but because it's undefined behavior
|
||||
// to shift the size of the value (zeroing the value), handle this
|
||||
// situation explicitly.
|
||||
return Value{
|
||||
flags: flags,
|
||||
typecode: structField.Type,
|
||||
value: unsafe.Pointer(uintptr(0)),
|
||||
}
|
||||
}
|
||||
|
||||
if size > unsafe.Sizeof(uintptr(0)) {
|
||||
// The value was not stored in the interface before but will be
|
||||
// afterwards, so load the value (from the correct offset) and return
|
||||
// it.
|
||||
ptr := unsafe.Pointer(uintptr(v.value) + structField.Offset)
|
||||
value := unsafe.Pointer(loadValue(ptr, fieldSize))
|
||||
return Value{
|
||||
flags: 0,
|
||||
typecode: structField.Type,
|
||||
value: value,
|
||||
}
|
||||
}
|
||||
|
||||
// The value was already stored directly in the interface and it still
|
||||
// is. Cut out the part of the value that we need.
|
||||
value := maskAndShift(uintptr(v.value), structField.Offset, fieldSize)
|
||||
return Value{
|
||||
flags: flags,
|
||||
typecode: structField.Type,
|
||||
value: unsafe.Pointer(value),
|
||||
}
|
||||
}
|
||||
|
||||
func (v Value) Index(i int) Value {
|
||||
@@ -333,7 +426,7 @@ func (v Value) Index(i int) Value {
|
||||
}
|
||||
elem := Value{
|
||||
typecode: v.Type().Elem(),
|
||||
indirect: true,
|
||||
flags: v.flags | valueFlagIndirect,
|
||||
}
|
||||
addr := uintptr(slice.Data) + elem.Type().Size()*uintptr(i) // pointer to new value
|
||||
elem.value = unsafe.Pointer(addr)
|
||||
@@ -351,12 +444,75 @@ func (v Value) Index(i int) Value {
|
||||
value: unsafe.Pointer(uintptr(*(*uint8)(unsafe.Pointer(s.Data + uintptr(i))))),
|
||||
}
|
||||
case Array:
|
||||
panic("unimplemented: (reflect.Value).Index()")
|
||||
// Extract an element from the array.
|
||||
elemType := v.Type().Elem()
|
||||
elemSize := elemType.Size()
|
||||
size := v.Type().Size()
|
||||
if size == 0 {
|
||||
// The element size is 0 and/or the length of the array is 0.
|
||||
return Value{
|
||||
typecode: v.Type().Elem(),
|
||||
flags: v.flags,
|
||||
}
|
||||
}
|
||||
if elemSize > unsafe.Sizeof(uintptr(0)) {
|
||||
// The resulting value doesn't fit in a pointer so must be
|
||||
// indirect. Also, because size != 0 this implies that the array
|
||||
// length must be != 0, and thus that the total size is at least
|
||||
// elemSize.
|
||||
addr := uintptr(v.value) + elemSize*uintptr(i) // pointer to new value
|
||||
return Value{
|
||||
typecode: v.Type().Elem(),
|
||||
flags: v.flags,
|
||||
value: unsafe.Pointer(addr),
|
||||
}
|
||||
}
|
||||
|
||||
if size > unsafe.Sizeof(uintptr(0)) {
|
||||
// The element fits in a pointer, but the array does not.
|
||||
// Load the value from the pointer.
|
||||
addr := uintptr(v.value) + elemSize*uintptr(i) // pointer to new value
|
||||
return Value{
|
||||
typecode: v.Type().Elem(),
|
||||
flags: v.flags,
|
||||
value: unsafe.Pointer(loadValue(unsafe.Pointer(addr), elemSize)),
|
||||
}
|
||||
}
|
||||
|
||||
// The value fits in a pointer, so extract it with some shifting and
|
||||
// masking.
|
||||
offset := elemSize * uintptr(i)
|
||||
value := maskAndShift(uintptr(v.value), offset, elemSize)
|
||||
return Value{
|
||||
typecode: v.Type().Elem(),
|
||||
flags: v.flags,
|
||||
value: unsafe.Pointer(value),
|
||||
}
|
||||
default:
|
||||
panic(&ValueError{"Index"})
|
||||
}
|
||||
}
|
||||
|
||||
// loadValue loads a value that may or may not be word-aligned. The number of
|
||||
// bytes given in size are loaded. The biggest possible size it can load is that
|
||||
// of an uintptr.
|
||||
func loadValue(ptr unsafe.Pointer, size uintptr) uintptr {
|
||||
loadedValue := uintptr(0)
|
||||
shift := uintptr(0)
|
||||
for i := uintptr(0); i < size; i++ {
|
||||
loadedValue |= uintptr(*(*byte)(ptr)) << shift
|
||||
shift += 8
|
||||
ptr = unsafe.Pointer(uintptr(ptr) + 1)
|
||||
}
|
||||
return loadedValue
|
||||
}
|
||||
|
||||
// maskAndShift cuts out a part of a uintptr. Note that the offset may not be 0.
|
||||
func maskAndShift(value, offset, size uintptr) uintptr {
|
||||
mask := ^uintptr(0) >> ((unsafe.Sizeof(uintptr(0)) - size) * 8)
|
||||
return (uintptr(value) >> (offset * 8)) & mask
|
||||
}
|
||||
|
||||
func (v Value) MapKeys() []Value {
|
||||
panic("unimplemented: (reflect.Value).MapKeys()")
|
||||
}
|
||||
@@ -385,19 +541,13 @@ func (it *MapIter) Next() bool {
|
||||
}
|
||||
|
||||
func (v Value) Set(x Value) {
|
||||
if !v.indirect {
|
||||
panic("reflect: value is not addressable")
|
||||
}
|
||||
if v.Type() != x.Type() {
|
||||
if v.Kind() == Interface {
|
||||
panic("reflect: unimplemented: assigning to interface of different type")
|
||||
} else {
|
||||
panic("reflect: cannot assign")
|
||||
}
|
||||
v.checkAddressable()
|
||||
if !v.Type().AssignableTo(x.Type()) {
|
||||
panic("reflect: cannot set")
|
||||
}
|
||||
size := v.Type().Size()
|
||||
xptr := x.value
|
||||
if size <= unsafe.Sizeof(uintptr(0)) && !x.indirect {
|
||||
if size <= unsafe.Sizeof(uintptr(0)) && !x.isIndirect() {
|
||||
value := x.value
|
||||
xptr = unsafe.Pointer(&value)
|
||||
}
|
||||
@@ -405,9 +555,7 @@ func (v Value) Set(x Value) {
|
||||
}
|
||||
|
||||
func (v Value) SetBool(x bool) {
|
||||
if !v.indirect {
|
||||
panic("reflect: value is not addressable")
|
||||
}
|
||||
v.checkAddressable()
|
||||
switch v.Kind() {
|
||||
case Bool:
|
||||
*(*bool)(v.value) = x
|
||||
@@ -417,9 +565,7 @@ func (v Value) SetBool(x bool) {
|
||||
}
|
||||
|
||||
func (v Value) SetInt(x int64) {
|
||||
if !v.indirect {
|
||||
panic("reflect: value is not addressable")
|
||||
}
|
||||
v.checkAddressable()
|
||||
switch v.Kind() {
|
||||
case Int:
|
||||
*(*int)(v.value) = int(x)
|
||||
@@ -437,9 +583,7 @@ func (v Value) SetInt(x int64) {
|
||||
}
|
||||
|
||||
func (v Value) SetUint(x uint64) {
|
||||
if !v.indirect {
|
||||
panic("reflect: value is not addressable")
|
||||
}
|
||||
v.checkAddressable()
|
||||
switch v.Kind() {
|
||||
case Uint:
|
||||
*(*uint)(v.value) = uint(x)
|
||||
@@ -459,9 +603,7 @@ func (v Value) SetUint(x uint64) {
|
||||
}
|
||||
|
||||
func (v Value) SetFloat(x float64) {
|
||||
if !v.indirect {
|
||||
panic("reflect: value is not addressable")
|
||||
}
|
||||
v.checkAddressable()
|
||||
switch v.Kind() {
|
||||
case Float32:
|
||||
*(*float32)(v.value) = float32(x)
|
||||
@@ -473,9 +615,7 @@ func (v Value) SetFloat(x float64) {
|
||||
}
|
||||
|
||||
func (v Value) SetComplex(x complex128) {
|
||||
if !v.indirect {
|
||||
panic("reflect: value is not addressable")
|
||||
}
|
||||
v.checkAddressable()
|
||||
switch v.Kind() {
|
||||
case Complex64:
|
||||
*(*complex64)(v.value) = complex64(x)
|
||||
@@ -487,9 +627,7 @@ func (v Value) SetComplex(x complex128) {
|
||||
}
|
||||
|
||||
func (v Value) SetString(x string) {
|
||||
if !v.indirect {
|
||||
panic("reflect: value is not addressable")
|
||||
}
|
||||
v.checkAddressable()
|
||||
switch v.Kind() {
|
||||
case String:
|
||||
*(*string)(v.value) = x
|
||||
@@ -498,6 +636,12 @@ func (v Value) SetString(x string) {
|
||||
}
|
||||
}
|
||||
|
||||
func (v Value) checkAddressable() {
|
||||
if !v.isIndirect() {
|
||||
panic("reflect: value is not addressable")
|
||||
}
|
||||
}
|
||||
|
||||
func MakeSlice(typ Type, len, cap int) Value {
|
||||
panic("unimplemented: reflect.MakeSlice()")
|
||||
}
|
||||
@@ -515,12 +659,6 @@ type funcHeader struct {
|
||||
Code unsafe.Pointer
|
||||
}
|
||||
|
||||
// This is the same thing as an interface{}.
|
||||
type interfaceHeader struct {
|
||||
typecode Type
|
||||
value unsafe.Pointer
|
||||
}
|
||||
|
||||
type SliceHeader struct {
|
||||
Data uintptr
|
||||
Len uintptr
|
||||
|
||||
@@ -9,3 +9,5 @@ const TargetBits = 32
|
||||
func align(ptr uintptr) uintptr {
|
||||
return (ptr + 3) &^ 3
|
||||
}
|
||||
|
||||
func getCurrentStackPointer() uintptr
|
||||
|
||||
@@ -11,3 +11,5 @@ const TargetBits = 64
|
||||
func align(ptr uintptr) uintptr {
|
||||
return (ptr + 15) &^ 15
|
||||
}
|
||||
|
||||
func getCurrentStackPointer() uintptr
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
// +build arm,!avr,!cortexm,!tinygo.riscv
|
||||
// +build arm,!baremetal arm,arm7tdmi
|
||||
|
||||
package runtime
|
||||
|
||||
import "device/arm"
|
||||
|
||||
const GOARCH = "arm"
|
||||
|
||||
// The bitness of the CPU (e.g. 8, 32, 64).
|
||||
@@ -11,3 +13,7 @@ const TargetBits = 32
|
||||
func align(ptr uintptr) uintptr {
|
||||
return (ptr + 3) &^ 3
|
||||
}
|
||||
|
||||
func getCurrentStackPointer() uintptr {
|
||||
return arm.ReadRegister("sp")
|
||||
}
|
||||
|
||||
@@ -9,3 +9,5 @@ const TargetBits = 64
|
||||
func align(ptr uintptr) uintptr {
|
||||
return (ptr + 7) &^ 7
|
||||
}
|
||||
|
||||
func getCurrentStackPointer() uintptr
|
||||
|
||||
@@ -12,3 +12,5 @@ func align(ptr uintptr) uintptr {
|
||||
// No alignment necessary on the AVR.
|
||||
return ptr
|
||||
}
|
||||
|
||||
func getCurrentStackPointer() uintptr
|
||||
|
||||
@@ -28,3 +28,5 @@ const wasmPageSize = 64 * 1024
|
||||
func align(ptr uintptr) uintptr {
|
||||
return (ptr + 3) &^ 3
|
||||
}
|
||||
|
||||
func getCurrentStackPointer() uintptr
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// +build avr cortexm tinygo.riscv
|
||||
// +build baremetal
|
||||
|
||||
package runtime
|
||||
|
||||
|
||||
+296
-116
@@ -27,21 +27,245 @@ import (
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
func chanDebug(ch *channel) {
|
||||
if schedulerDebug {
|
||||
if ch.bufSize > 0 {
|
||||
println("--- channel update:", ch, ch.state.String(), ch.bufSize, ch.bufUsed)
|
||||
} else {
|
||||
println("--- channel update:", ch, ch.state.String())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type channel struct {
|
||||
elementSize uint16 // the size of one value in this channel
|
||||
elementSize uintptr // the size of one value in this channel
|
||||
bufSize uintptr // size of buffer (in elements)
|
||||
state chanState
|
||||
blocked *coroutine
|
||||
blocked *task
|
||||
bufHead uintptr // head index of buffer (next push index)
|
||||
bufTail uintptr // tail index of buffer (next pop index)
|
||||
bufUsed uintptr // number of elements currently in buffer
|
||||
buf unsafe.Pointer // pointer to first element of buffer
|
||||
}
|
||||
|
||||
// chanMake creates a new channel with the given element size and buffer length in number of elements.
|
||||
// This is a compiler intrinsic.
|
||||
func chanMake(elementSize uintptr, bufSize uintptr) *channel {
|
||||
return &channel{
|
||||
elementSize: elementSize,
|
||||
bufSize: bufSize,
|
||||
buf: alloc(elementSize * bufSize),
|
||||
}
|
||||
}
|
||||
|
||||
// push value to end of channel if space is available
|
||||
// returns whether there was space for the value in the buffer
|
||||
func (ch *channel) push(value unsafe.Pointer) bool {
|
||||
// immediately return false if the channel is not buffered
|
||||
if ch.bufSize == 0 {
|
||||
return false
|
||||
}
|
||||
|
||||
// ensure space is available
|
||||
if ch.bufUsed == ch.bufSize {
|
||||
return false
|
||||
}
|
||||
|
||||
// copy value to buffer
|
||||
memcpy(
|
||||
unsafe.Pointer( // pointer to the base of the buffer + offset = pointer to destination element
|
||||
uintptr(ch.buf)+
|
||||
uintptr( // element size * equivalent slice index = offset
|
||||
ch.elementSize* // element size (bytes)
|
||||
ch.bufHead, // index of first available buffer entry
|
||||
),
|
||||
),
|
||||
value,
|
||||
ch.elementSize,
|
||||
)
|
||||
|
||||
// update buffer state
|
||||
ch.bufUsed++
|
||||
ch.bufHead++
|
||||
if ch.bufHead == ch.bufSize {
|
||||
ch.bufHead = 0
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
// pop value from channel buffer if one is available
|
||||
// returns whether a value was popped or not
|
||||
// result is stored into value pointer
|
||||
func (ch *channel) pop(value unsafe.Pointer) bool {
|
||||
// channel is empty
|
||||
if ch.bufUsed == 0 {
|
||||
return false
|
||||
}
|
||||
|
||||
// compute address of source
|
||||
addr := unsafe.Pointer(uintptr(ch.buf) + (ch.elementSize * ch.bufTail))
|
||||
|
||||
// copy value from buffer
|
||||
memcpy(
|
||||
value,
|
||||
addr,
|
||||
ch.elementSize,
|
||||
)
|
||||
|
||||
// zero buffer element to allow garbage collection of value
|
||||
memzero(
|
||||
addr,
|
||||
ch.elementSize,
|
||||
)
|
||||
|
||||
// update buffer state
|
||||
ch.bufUsed--
|
||||
|
||||
// move tail up
|
||||
ch.bufTail++
|
||||
if ch.bufTail == ch.bufSize {
|
||||
ch.bufTail = 0
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
// try to send a value to a channel, without actually blocking
|
||||
// returns whether the value was sent
|
||||
// will panic if channel is closed
|
||||
func (ch *channel) trySend(value unsafe.Pointer) bool {
|
||||
if ch == nil {
|
||||
// send to nil channel blocks forever
|
||||
// this is non-blocking, so just say no
|
||||
return false
|
||||
}
|
||||
|
||||
switch ch.state {
|
||||
case chanStateEmpty, chanStateBuf:
|
||||
// try to dump the value directly into the buffer
|
||||
if ch.push(value) {
|
||||
ch.state = chanStateBuf
|
||||
return true
|
||||
}
|
||||
return false
|
||||
case chanStateRecv:
|
||||
// unblock reciever
|
||||
receiver := unblockChain(&ch.blocked, nil)
|
||||
|
||||
// copy value to reciever
|
||||
receiverState := receiver.state()
|
||||
memcpy(receiverState.ptr, value, ch.elementSize)
|
||||
receiverState.data = 1 // commaOk = true
|
||||
|
||||
// change state to empty if there are no more receivers
|
||||
if ch.blocked == nil {
|
||||
ch.state = chanStateEmpty
|
||||
}
|
||||
|
||||
return true
|
||||
case chanStateSend:
|
||||
// something else is already waiting to send
|
||||
return false
|
||||
case chanStateClosed:
|
||||
runtimePanic("send on closed channel")
|
||||
default:
|
||||
runtimePanic("invalid channel state")
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
// try to recieve a value from a channel, without really blocking
|
||||
// returns whether a value was recieved
|
||||
// second return is the comma-ok value
|
||||
func (ch *channel) tryRecv(value unsafe.Pointer) (bool, bool) {
|
||||
if ch == nil {
|
||||
// recieve from nil channel blocks forever
|
||||
// this is non-blocking, so just say no
|
||||
return false, false
|
||||
}
|
||||
|
||||
switch ch.state {
|
||||
case chanStateBuf, chanStateSend:
|
||||
// try to pop the value directly from the buffer
|
||||
if ch.pop(value) {
|
||||
// unblock next sender if applicable
|
||||
if sender := unblockChain(&ch.blocked, nil); sender != nil {
|
||||
// push sender's value into buffer
|
||||
ch.push(sender.state().ptr)
|
||||
|
||||
if ch.blocked == nil {
|
||||
// last sender unblocked - update state
|
||||
ch.state = chanStateBuf
|
||||
}
|
||||
}
|
||||
|
||||
if ch.bufUsed == 0 {
|
||||
// channel empty - update state
|
||||
ch.state = chanStateEmpty
|
||||
}
|
||||
|
||||
return true, true
|
||||
} else if sender := unblockChain(&ch.blocked, nil); sender != nil {
|
||||
// unblock next sender if applicable
|
||||
// copy sender's value
|
||||
memcpy(value, sender.state().ptr, ch.elementSize)
|
||||
|
||||
if ch.blocked == nil {
|
||||
// last sender unblocked - update state
|
||||
ch.state = chanStateEmpty
|
||||
}
|
||||
|
||||
return true, true
|
||||
}
|
||||
return false, false
|
||||
case chanStateRecv, chanStateEmpty:
|
||||
// something else is already waiting to recieve
|
||||
return false, false
|
||||
case chanStateClosed:
|
||||
if ch.pop(value) {
|
||||
return true, true
|
||||
}
|
||||
|
||||
// channel closed - nothing to recieve
|
||||
memzero(value, ch.elementSize)
|
||||
return true, false
|
||||
default:
|
||||
runtimePanic("invalid channel state")
|
||||
}
|
||||
|
||||
runtimePanic("unreachable")
|
||||
return false, false
|
||||
}
|
||||
|
||||
type chanState uint8
|
||||
|
||||
const (
|
||||
chanStateEmpty chanState = iota
|
||||
chanStateRecv
|
||||
chanStateSend
|
||||
chanStateClosed
|
||||
chanStateEmpty chanState = iota // nothing in channel, no senders/recievers
|
||||
chanStateRecv // nothing in channel, recievers waiting
|
||||
chanStateSend // senders waiting, buffer full if present
|
||||
chanStateBuf // buffer not empty, no senders waiting
|
||||
chanStateClosed // channel closed
|
||||
)
|
||||
|
||||
func (s chanState) String() string {
|
||||
switch s {
|
||||
case chanStateEmpty:
|
||||
return "empty"
|
||||
case chanStateRecv:
|
||||
return "recv"
|
||||
case chanStateSend:
|
||||
return "send"
|
||||
case chanStateBuf:
|
||||
return "buffered"
|
||||
case chanStateClosed:
|
||||
return "closed"
|
||||
default:
|
||||
return "invalid"
|
||||
}
|
||||
}
|
||||
|
||||
// chanSelectState is a single channel operation (send/recv) in a select
|
||||
// statement. The value pointer is either nil (for receives) or points to the
|
||||
// value to send (for sends).
|
||||
@@ -50,78 +274,59 @@ type chanSelectState struct {
|
||||
value unsafe.Pointer
|
||||
}
|
||||
|
||||
func deadlockStub()
|
||||
|
||||
// chanSend sends a single value over the channel. If this operation can
|
||||
// complete immediately (there is a goroutine waiting for a value), it sends the
|
||||
// value and re-activates both goroutines. If not, it sets itself as waiting on
|
||||
// a value.
|
||||
func chanSend(sender *coroutine, ch *channel, value unsafe.Pointer) {
|
||||
if ch == nil {
|
||||
// A nil channel blocks forever. Do not scheduler this goroutine again.
|
||||
// chanSend sends a single value over the channel.
|
||||
// This operation will block unless a value is immediately available.
|
||||
// May panic if the channel is closed.
|
||||
func chanSend(ch *channel, value unsafe.Pointer) {
|
||||
if ch.trySend(value) {
|
||||
// value immediately sent
|
||||
chanDebug(ch)
|
||||
return
|
||||
}
|
||||
switch ch.state {
|
||||
case chanStateEmpty:
|
||||
sender.promise().ptr = value
|
||||
ch.state = chanStateSend
|
||||
ch.blocked = sender
|
||||
case chanStateRecv:
|
||||
receiver := ch.blocked
|
||||
receiverPromise := receiver.promise()
|
||||
memcpy(receiverPromise.ptr, value, uintptr(ch.elementSize))
|
||||
receiverPromise.data = 1 // commaOk = true
|
||||
ch.blocked = receiverPromise.next
|
||||
receiverPromise.next = nil
|
||||
activateTask(receiver)
|
||||
activateTask(sender)
|
||||
if ch.blocked == nil {
|
||||
ch.state = chanStateEmpty
|
||||
}
|
||||
case chanStateClosed:
|
||||
runtimePanic("send on closed channel")
|
||||
case chanStateSend:
|
||||
sender.promise().ptr = value
|
||||
sender.promise().next = ch.blocked
|
||||
ch.blocked = sender
|
||||
|
||||
if ch == nil {
|
||||
// A nil channel blocks forever. Do not schedule this goroutine again.
|
||||
deadlock()
|
||||
}
|
||||
|
||||
// wait for reciever
|
||||
sender := getCoroutine()
|
||||
ch.state = chanStateSend
|
||||
senderState := sender.state()
|
||||
senderState.ptr = value
|
||||
ch.blocked, senderState.next = sender, ch.blocked
|
||||
chanDebug(ch)
|
||||
yield()
|
||||
senderState.ptr = nil
|
||||
}
|
||||
|
||||
// chanRecv receives a single value over a channel. If there is an available
|
||||
// sender, it receives the value immediately and re-activates both coroutines.
|
||||
// If not, it sets itself as available for receiving. If the channel is closed,
|
||||
// it immediately activates itself with a zero value as the result.
|
||||
func chanRecv(receiver *coroutine, ch *channel, value unsafe.Pointer) {
|
||||
// chanRecv receives a single value over a channel.
|
||||
// It blocks if there is no available value to recieve.
|
||||
// The recieved value is copied into the value pointer.
|
||||
// Returns the comma-ok value.
|
||||
func chanRecv(ch *channel, value unsafe.Pointer) bool {
|
||||
if rx, ok := ch.tryRecv(value); rx {
|
||||
// value immediately available
|
||||
chanDebug(ch)
|
||||
return ok
|
||||
}
|
||||
|
||||
if ch == nil {
|
||||
// A nil channel blocks forever. Do not scheduler this goroutine again.
|
||||
return
|
||||
}
|
||||
switch ch.state {
|
||||
case chanStateSend:
|
||||
sender := ch.blocked
|
||||
senderPromise := sender.promise()
|
||||
memcpy(value, senderPromise.ptr, uintptr(ch.elementSize))
|
||||
receiver.promise().data = 1 // commaOk = true
|
||||
ch.blocked = senderPromise.next
|
||||
senderPromise.next = nil
|
||||
activateTask(receiver)
|
||||
activateTask(sender)
|
||||
if ch.blocked == nil {
|
||||
ch.state = chanStateEmpty
|
||||
}
|
||||
case chanStateEmpty:
|
||||
receiver.promise().ptr = value
|
||||
ch.state = chanStateRecv
|
||||
ch.blocked = receiver
|
||||
case chanStateClosed:
|
||||
memzero(value, uintptr(ch.elementSize))
|
||||
receiver.promise().data = 0 // commaOk = false
|
||||
activateTask(receiver)
|
||||
case chanStateRecv:
|
||||
receiver.promise().ptr = value
|
||||
receiver.promise().next = ch.blocked
|
||||
ch.blocked = receiver
|
||||
// A nil channel blocks forever. Do not schedule this goroutine again.
|
||||
deadlock()
|
||||
}
|
||||
|
||||
// wait for a value
|
||||
receiver := getCoroutine()
|
||||
ch.state = chanStateRecv
|
||||
receiverState := receiver.state()
|
||||
receiverState.ptr, receiverState.data = value, 0
|
||||
ch.blocked, receiverState.next = receiver, ch.blocked
|
||||
chanDebug(ch)
|
||||
yield()
|
||||
ok := receiverState.data == 1
|
||||
receiverState.ptr, receiverState.data = nil, 0
|
||||
return ok
|
||||
}
|
||||
|
||||
// chanClose closes the given channel. If this channel has a receiver or is
|
||||
@@ -142,17 +347,22 @@ func chanClose(ch *channel) {
|
||||
// before the close.
|
||||
runtimePanic("close channel during send")
|
||||
case chanStateRecv:
|
||||
// The receiver must be re-activated with a zero value.
|
||||
receiverPromise := ch.blocked.promise()
|
||||
memzero(receiverPromise.ptr, uintptr(ch.elementSize))
|
||||
receiverPromise.data = 0 // commaOk = false
|
||||
activateTask(ch.blocked)
|
||||
ch.state = chanStateClosed
|
||||
ch.blocked = nil
|
||||
case chanStateEmpty:
|
||||
// unblock all receivers with the zero value
|
||||
for rx := unblockChain(&ch.blocked, nil); rx != nil; rx = unblockChain(&ch.blocked, nil) {
|
||||
// get receiver state
|
||||
state := rx.state()
|
||||
|
||||
// store the zero value
|
||||
memzero(state.ptr, ch.elementSize)
|
||||
|
||||
// set the comma-ok value to false (channel closed)
|
||||
state.data = 0
|
||||
}
|
||||
case chanStateEmpty, chanStateBuf:
|
||||
// Easy case. No available sender or receiver.
|
||||
ch.state = chanStateClosed
|
||||
}
|
||||
ch.state = chanStateClosed
|
||||
chanDebug(ch)
|
||||
}
|
||||
|
||||
// chanSelect is the runtime implementation of the select statement. This is
|
||||
@@ -164,47 +374,17 @@ func chanClose(ch *channel) {
|
||||
func chanSelect(recvbuf unsafe.Pointer, states []chanSelectState, blocking bool) (uintptr, bool) {
|
||||
// See whether we can receive from one of the channels.
|
||||
for i, state := range states {
|
||||
if state.ch == nil {
|
||||
// A nil channel blocks forever, so don't consider it here.
|
||||
continue
|
||||
}
|
||||
if state.value == nil {
|
||||
// A receive operation.
|
||||
switch state.ch.state {
|
||||
case chanStateSend:
|
||||
// We can receive immediately.
|
||||
sender := state.ch.blocked
|
||||
senderPromise := sender.promise()
|
||||
memcpy(recvbuf, senderPromise.ptr, uintptr(state.ch.elementSize))
|
||||
state.ch.blocked = senderPromise.next
|
||||
senderPromise.next = nil
|
||||
activateTask(sender)
|
||||
if state.ch.blocked == nil {
|
||||
state.ch.state = chanStateEmpty
|
||||
}
|
||||
return uintptr(i), true // commaOk = true
|
||||
case chanStateClosed:
|
||||
// Receive the zero value.
|
||||
memzero(recvbuf, uintptr(state.ch.elementSize))
|
||||
return uintptr(i), false // commaOk = false
|
||||
if rx, ok := state.ch.tryRecv(recvbuf); rx {
|
||||
chanDebug(state.ch)
|
||||
return uintptr(i), ok
|
||||
}
|
||||
} else {
|
||||
// A send operation: state.value is not nil.
|
||||
switch state.ch.state {
|
||||
case chanStateRecv:
|
||||
receiver := state.ch.blocked
|
||||
receiverPromise := receiver.promise()
|
||||
memcpy(receiverPromise.ptr, state.value, uintptr(state.ch.elementSize))
|
||||
receiverPromise.data = 1 // commaOk = true
|
||||
state.ch.blocked = receiverPromise.next
|
||||
receiverPromise.next = nil
|
||||
activateTask(receiver)
|
||||
if state.ch.blocked == nil {
|
||||
state.ch.state = chanStateEmpty
|
||||
}
|
||||
return uintptr(i), false
|
||||
case chanStateClosed:
|
||||
runtimePanic("send on closed channel")
|
||||
if state.ch.trySend(state.value) {
|
||||
chanDebug(state.ch)
|
||||
return uintptr(i), true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user