mirror of
https://github.com/tinygo-org/tinygo.git
synced 2026-07-27 23:28:42 +00:00
Compare commits
146 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 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 | |||
| 515daa7d3c | |||
| ced964f039 | |||
| 8a704e43fc | |||
| 847681457f | |||
| 66d8899aa7 | |||
| b0cad7ed63 | |||
| 00cc486619 | |||
| d627208c48 | |||
| 152caa3b0a | |||
| 7ed6b45149 | |||
| c66d979ba3 | |||
| fc9188a298 | |||
| ffa38b183b | |||
| f0eb4eef5a | |||
| fa928e8cd3 | |||
| c49d80628c | |||
| af523c63d5 | |||
| aabb6ba22b | |||
| 6611578ec8 | |||
| d49a363d0d | |||
| 385d1d0a5d | |||
| 00e91ec569 | |||
| 1fd0c8d48c | |||
| d34bb7e708 | |||
| a328bbdff3 | |||
| 4ecd478d82 | |||
| 40b193f1fa | |||
| 7fd5ec8661 | |||
| 84618c45eb | |||
| c0ff4e566d | |||
| ce9b21a270 | |||
| ed7c242a09 | |||
| 46872a70b1 | |||
| e9f6e51fc8 | |||
| fa5855bff5 | |||
| 16201c41dc | |||
| 208e1719ad | |||
| a3d1f1a514 | |||
| 3e1c0d90c5 | |||
| b7197bcaae | |||
| 8890a0f3c8 | |||
| 5be412dabe | |||
| 412ec5b789 | |||
| b0e767c4a7 | |||
| 6b5b4a681d | |||
| 88bb61f287 | |||
| e169e3b996 | |||
| aa8957dd05 | |||
| 8e5731aee7 | |||
| 0cc35b6188 | |||
| 5cf7fba1a4 | |||
| e67506ee68 | |||
| f2c205a008 | |||
| c84c625585 | |||
| 9673ad3774 | |||
| 66aca428ba | |||
| ec87811420 | |||
| 776dc1e0d9 | |||
| beea2f1f30 | |||
| 5f9c683abf | |||
| 0ce4d90779 | |||
| 1047c9bd05 | |||
| 1d7cc2c242 | |||
| 23c8d15847 |
+45
-29
@@ -44,20 +44,14 @@ commands:
|
||||
steps:
|
||||
- restore_cache:
|
||||
keys:
|
||||
- llvm-source-8-v3
|
||||
- llvm-source-8-v5
|
||||
- run:
|
||||
name: "Fetch LLVM source"
|
||||
command: make llvm-source
|
||||
- save_cache:
|
||||
key: llvm-source-8-v3
|
||||
key: llvm-source-8-v5
|
||||
paths:
|
||||
- llvm
|
||||
smoketest:
|
||||
steps:
|
||||
- run: make smoketest
|
||||
smoketest-no-avr:
|
||||
steps:
|
||||
- run: make smoketest-no-avr
|
||||
- llvm-project
|
||||
test-linux:
|
||||
steps:
|
||||
- checkout
|
||||
@@ -67,18 +61,19 @@ 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 ./transform .
|
||||
- run: make gen-device -j4
|
||||
- smoketest
|
||||
- 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
|
||||
build-linux:
|
||||
steps:
|
||||
@@ -101,12 +96,12 @@ 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:
|
||||
- llvm-build-8-linux-v5
|
||||
- llvm-build-8-linux-v7
|
||||
- run:
|
||||
name: "Build LLVM"
|
||||
command: |
|
||||
@@ -124,7 +119,7 @@ commands:
|
||||
make llvm-build
|
||||
fi
|
||||
- save_cache:
|
||||
key: llvm-build-8-linux-v5
|
||||
key: llvm-build-8-linux-v7
|
||||
paths:
|
||||
llvm-build
|
||||
- run:
|
||||
@@ -144,10 +139,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: |
|
||||
@@ -155,7 +151,12 @@ commands:
|
||||
tar -C ~/lib -xf /tmp/tinygo.linux-amd64.tar.gz
|
||||
ln -s ~/lib/tinygo/bin/tinygo /go/bin/tinygo
|
||||
tinygo version
|
||||
- smoketest
|
||||
- run:
|
||||
name: "Download SiFive GNU toolchain"
|
||||
command: |
|
||||
curl -O https://static.dev.sifive.com/dev-tools/riscv64-unknown-elf-gcc-8.2.0-2019.05.3-x86_64-linux-ubuntu14.tar.gz
|
||||
sudo tar -C /usr/local --strip-components=1 -xf riscv64-unknown-elf-gcc-8.2.0-2019.05.3-x86_64-linux-ubuntu14.tar.gz
|
||||
- run: make smoketest
|
||||
build-macos:
|
||||
steps:
|
||||
- checkout
|
||||
@@ -169,17 +170,21 @@ commands:
|
||||
HOMEBREW_NO_AUTO_UPDATE=1 brew install qemu
|
||||
- restore_cache:
|
||||
keys:
|
||||
- llvm-source-8-macos-v3
|
||||
- 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
|
||||
- run:
|
||||
name: "Fetch LLVM source"
|
||||
command: make llvm-source
|
||||
- save_cache:
|
||||
key: llvm-source-8-macos-v3
|
||||
key: llvm-source-8-macos-v5
|
||||
paths:
|
||||
- llvm
|
||||
- llvm-project
|
||||
- restore_cache:
|
||||
keys:
|
||||
- llvm-build-8-macos-v4
|
||||
- llvm-build-8-macos-v6
|
||||
- run:
|
||||
name: "Build LLVM"
|
||||
command: |
|
||||
@@ -191,7 +196,7 @@ commands:
|
||||
make llvm-build
|
||||
fi
|
||||
- save_cache:
|
||||
key: llvm-build-8-macos-v4
|
||||
key: llvm-build-8-macos-v6
|
||||
paths:
|
||||
llvm-build
|
||||
- run:
|
||||
@@ -215,23 +220,34 @@ commands:
|
||||
tar -C /usr/local/opt -xf /tmp/tinygo.darwin-amd64.tar.gz
|
||||
ln -s /usr/local/opt/tinygo/bin/tinygo /usr/local/bin/tinygo
|
||||
tinygo version
|
||||
- smoketest-no-avr
|
||||
- run:
|
||||
name: "Download SiFive GNU toolchain"
|
||||
command: |
|
||||
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
|
||||
build-linux:
|
||||
docker:
|
||||
- image: circleci/golang:1.12
|
||||
- image: circleci/golang:1.12-stretch
|
||||
steps:
|
||||
- build-linux
|
||||
build-macos:
|
||||
|
||||
+4
-2
@@ -5,10 +5,12 @@ src/device/avr/*.ld
|
||||
src/device/avr/*.s
|
||||
src/device/nrf/*.go
|
||||
src/device/nrf/*.s
|
||||
src/device/stm32/*.go
|
||||
src/device/stm32/*.s
|
||||
src/device/sam/*.go
|
||||
src/device/sam/*.s
|
||||
src/device/sifive/*.go
|
||||
src/device/sifive/*.s
|
||||
src/device/stm32/*.go
|
||||
src/device/stm32/*.s
|
||||
vendor
|
||||
llvm
|
||||
llvm-build
|
||||
|
||||
+1
-1
@@ -9,7 +9,7 @@
|
||||
url = https://github.com/avr-rust/avr-mcu.git
|
||||
[submodule "lib/cmsis-svd"]
|
||||
path = lib/cmsis-svd
|
||||
url = https://github.com/posborne/cmsis-svd
|
||||
url = https://github.com/tinygo-org/cmsis-svd
|
||||
[submodule "lib/compiler-rt"]
|
||||
path = lib/compiler-rt
|
||||
url = https://github.com/llvm-mirror/compiler-rt.git
|
||||
|
||||
+8
-5
@@ -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
|
||||
@@ -27,16 +26,20 @@ on a different system like Mac.
|
||||
|
||||
## Download the source
|
||||
|
||||
The first step is to download the TinyGo sources. Then, inside the directory,
|
||||
perform these steps:
|
||||
The first step is to download the TinyGo sources (use `--recursive` if you clone
|
||||
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,99 @@
|
||||
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**
|
||||
- try more locations to find Clang built-in headers
|
||||
- add support for `tinygo test`
|
||||
- build current directory if no package is specified
|
||||
- support custom .json target spec with `-target` flag
|
||||
- use zversion.go to detect version of GOROOT version
|
||||
- make initial heap size configurable for some targets (currently WebAssembly
|
||||
only)
|
||||
* **cgo**
|
||||
- add support for bitfields using generated getters and setters
|
||||
- add support for anonymous structs
|
||||
* **compiler**
|
||||
- show an error instead of panicking on duplicate function definitions
|
||||
- allow packages like github.com/tinygo-org/tinygo/src/\* by aliasing it
|
||||
- remove `//go:volatile` support
|
||||
It has been replaced with the runtime/volatile package.
|
||||
- allow poiners in map keys
|
||||
- support non-constant syscall numbers
|
||||
- implement non-blocking selects
|
||||
- add support for the `-tags` flag
|
||||
- add support for `string` to `[]rune` conversion
|
||||
- implement a portable conservative garbage collector (with support for wasm)
|
||||
- add the `//go:noinline` pragma
|
||||
* **standard library**
|
||||
- `os`: add `os.Exit` and `syscall.Exit`
|
||||
- `os`: add several stubs
|
||||
- `runtime`: fix heap corruption in conservative GC
|
||||
- `runtime`: add support for math intrinsics where supported, massively
|
||||
speeding up some benchmarks
|
||||
- `testing`: add basic support for testing
|
||||
* **targets**
|
||||
- add support for a generic target that calls `__tinygo_*` functions for
|
||||
peripheral access
|
||||
- `arduino-nano33`: add support for this board
|
||||
- `hifive1`: add support for this RISC-V board
|
||||
- `reelboard`: add e-paper pins
|
||||
- `reelboard`: add `PowerSupplyActive` to enable voltage for on-board devices
|
||||
- `wasm`: put the stack at the start of linear memory, to detect stack
|
||||
overflows
|
||||
|
||||
0.6.0
|
||||
---
|
||||
* **command line**
|
||||
|
||||
@@ -16,12 +16,16 @@ Please open a Github issue with your problem, and we will be happy to assist.
|
||||
|
||||
We probably have not implemented it yet. Please take a look at our [Roadmap](https://github.com/tinygo-org/tinygo/wiki/Roadmap). Your pull request adding the functionality to TinyGo would be greatly appreciated.
|
||||
|
||||
Please open a Github issue. We want to help, and also make sure that there is no duplications of efforts. Sometimes what you need is already being worked on by someone else.
|
||||
|
||||
A long tail of small (and large) language features haven't been implemented yet. In almost all cases, the compiler will show a `todo:` error from `compiler/compiler.go` when you try to use it. You can try implementing it, or open a bug report with a small code sample that fails to compile.
|
||||
|
||||
### Some specific hardware you want to use does not appear to be in TinyGo
|
||||
|
||||
As above, we probably have not implemented it yet. Your contribution adding the hardware support to TinyGo would be greatly appreciated.
|
||||
|
||||
Please start by opening a Github issue. We want to help you to help us to help you.
|
||||
|
||||
Lots of targets/boards are still unsupported. Adding an architecture often requires a few compiler changes, but if the architecture is supported you can try implementing support for a new chip or board in `src/runtime`. For details, see [this wiki entry on adding archs/chips/boards](https://github.com/tinygo-org/tinygo/wiki/Adding-a-new-board).
|
||||
|
||||
Microcontrollers have lots of peripherals (I2C, SPI, ADC, etc.) and many don't have an implementation yet in the `machine` package. Adding support for new peripherals is very useful.
|
||||
|
||||
@@ -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.12, LLVM 8 and the TinyGo compiler itself.
|
||||
FROM golang:1.12 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
|
||||
@@ -3,12 +3,12 @@
|
||||
all: tinygo
|
||||
tinygo: build/tinygo
|
||||
|
||||
.PHONY: all tinygo build/tinygo test llvm-build llvm-source clean fmt gen-device gen-device-nrf gen-device-avr
|
||||
|
||||
# Default build and source directories, as created by `make llvm-build`.
|
||||
LLVM_BUILDDIR ?= llvm-build
|
||||
CLANG_SRC ?= llvm/tools/clang
|
||||
LLD_SRC ?= llvm/tools/lld
|
||||
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
|
||||
|
||||
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
|
||||
|
||||
@@ -18,15 +18,17 @@ ifeq ($(UNAME_S),Linux)
|
||||
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 -lclangToolingRefactor $(END_GROUP) -lstdc++
|
||||
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++
|
||||
|
||||
LLD_LIBS = $(START_GROUP) -llldCOFF -llldCommon -llldCore -llldDriver -llldELF -llldMachO -llldMinGW -llldReaderWriter -llldWasm -llldYAML $(END_GROUP)
|
||||
|
||||
|
||||
# For static linking.
|
||||
CGO_CPPFLAGS=$(shell $(LLVM_BUILDDIR)/bin/llvm-config --cppflags) -I$(abspath $(CLANG_SRC))/include -I$(abspath $(LLD_SRC))/include
|
||||
CGO_CXXFLAGS=-std=c++11
|
||||
CGO_LDFLAGS=-L$(LLVM_BUILDDIR)/lib $(CLANG_LIBS) $(LLD_LIBS) $(shell $(LLVM_BUILDDIR)/bin/llvm-config --ldflags --libs --system-libs $(LLVM_COMPONENTS))
|
||||
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))
|
||||
endif
|
||||
|
||||
|
||||
clean:
|
||||
@@ -38,7 +40,8 @@ fmt:
|
||||
fmt-check:
|
||||
@unformatted=$$(gofmt -l $(FMT_PATHS)); [ -z "$$unformatted" ] && exit 0; echo "Unformatted:"; for fn in $$unformatted; do echo " $$fn"; done; exit 1
|
||||
|
||||
gen-device: gen-device-avr gen-device-nrf gen-device-sam gen-device-stm32
|
||||
|
||||
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/
|
||||
@@ -53,42 +56,43 @@ 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
|
||||
|
||||
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
|
||||
|
||||
gen-device-stm32:
|
||||
./tools/gen-device-svd.py lib/cmsis-svd/data/STMicro/ src/device/stm32/ --source=https://github.com/posborne/cmsis-svd/tree/master/data/STMicro
|
||||
go fmt ./src/device/stm32
|
||||
|
||||
|
||||
# Get LLVM sources.
|
||||
llvm/README.txt:
|
||||
git clone -b release_80 https://github.com/llvm-mirror/llvm.git llvm
|
||||
llvm/tools/clang/README.txt:
|
||||
git clone -b release_80 https://github.com/llvm-mirror/clang.git llvm/tools/clang
|
||||
llvm/tools/lld/README.md:
|
||||
git clone -b release_80 https://github.com/llvm-mirror/lld.git llvm/tools/lld
|
||||
llvm-source: llvm/README.txt llvm/tools/clang/README.txt llvm/tools/lld/README.md
|
||||
llvm-project/README.md:
|
||||
git clone -b release/8.x https://github.com/llvm/llvm-project
|
||||
llvm-source: llvm-project/README.md
|
||||
|
||||
# Configure LLVM.
|
||||
llvm-build/build.ninja: llvm-source
|
||||
mkdir -p llvm-build; cd llvm-build; cmake -G Ninja ../llvm "-DLLVM_TARGETS_TO_BUILD=X86;ARM;AArch64;WebAssembly" "-DLLVM_EXPERIMENTAL_TARGETS_TO_BUILD=AVR" -DCMAKE_BUILD_TYPE=Release -DLLVM_ENABLE_ASSERTIONS=OFF -DLIBCLANG_BUILD_STATIC=ON -DLLVM_ENABLE_TERMINFO=OFF -DLLVM_ENABLE_ZLIB=OFF
|
||||
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
|
||||
|
||||
# Build LLVM.
|
||||
llvm-build: llvm-build/build.ninja
|
||||
cd llvm-build; ninja
|
||||
$(LLVM_BUILDDIR): $(LLVM_BUILDDIR)/build.ninja
|
||||
cd $(LLVM_BUILDDIR); ninja
|
||||
|
||||
|
||||
# Build the Go compiler.
|
||||
build/tinygo:
|
||||
@if [ ! -f llvm-build/bin/llvm-config ]; then echo "Fetch and build LLVM first by running:\n make llvm-source\n make llvm-build"; exit 1; fi
|
||||
@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 .
|
||||
|
||||
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 ./transform .
|
||||
|
||||
.PHONY: smoketest smoketest-no-avr
|
||||
smoketest: smoketest-no-avr
|
||||
tinygo build -size short -o test.elf -target=arduino examples/blinky1
|
||||
tinygo build -size short -o test.elf -target=digispark examples/blinky1
|
||||
smoketest-no-avr:
|
||||
tinygo-test:
|
||||
cd tests/tinygotest && 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
|
||||
@@ -104,7 +108,7 @@ smoketest-no-avr:
|
||||
tinygo build -size short -o test.elf -target=pca10040 examples/serial
|
||||
tinygo build -size short -o test.elf -target=pca10040 examples/test
|
||||
# test all targets/boards
|
||||
tinygo build -o test.elf examples/blinky2 # TODO: re-enable -size flag with MachO support
|
||||
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
|
||||
@@ -120,6 +124,16 @@ smoketest-no-avr:
|
||||
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 -size short -o test.elf -target=gameboy-advance examples/gba-display
|
||||
tinygo build -size short -o test.elf -target=itsybitsy-m4 examples/blinky1
|
||||
tinygo build -size short -o test.elf -target=nucleo-f103rb examples/blinky1
|
||||
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
|
||||
endif
|
||||
ifneq ($(RISCV), 0)
|
||||
tinygo build -size short -o test.elf -target=hifive1b examples/blinky1
|
||||
endif
|
||||
tinygo build -o wasm.wasm -target=wasm examples/wasm/export
|
||||
tinygo build -o wasm.wasm -target=wasm examples/wasm/main
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
[](https://circleci.com/gh/tinygo-org/tinygo/tree/dev)
|
||||
|
||||
TinyGo is a Go compiler intended for use in small places such as microcontrollers, WebAssembly (WASM), and command-line tools.
|
||||
TinyGo is a Go compiler intended for use in small places such as microcontrollers, WebAssembly (Wasm), and command-line tools.
|
||||
|
||||
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,15 +43,19 @@ See the [getting started instructions](https://tinygo.org/getting-started/) for
|
||||
|
||||
You can compile TinyGo programs for microcontrollers, WebAssembly and Linux.
|
||||
|
||||
The following 14 microcontroller boards are currently supported:
|
||||
The following 18 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/)
|
||||
* [Game Boy Advance](https://en.wikipedia.org/wiki/Game_Boy_Advance)
|
||||
* [ST Micro STM32F103XX "Bluepill"](http://wiki.stm32duino.com/index.php?title=Blue_Pill)
|
||||
* [ST Micro "Nucleo F103RB"](https://www.st.com/en/evaluation-tools/nucleo-f103rb.html)
|
||||
* [ST Micro STM32F407 "Discovery"](https://www.st.com/en/evaluation-tools/stm32f4discovery.html)
|
||||
* [Digispark](http://digistump.com/products/1)
|
||||
* [Nordic Semiconductor PCA10031](https://www.nordicsemi.com/eng/Products/nRF51-Dongle)
|
||||
|
||||
+303
-1
@@ -36,6 +36,7 @@ type cgoPackage struct {
|
||||
typedefs map[string]*typedefInfo
|
||||
elaboratedTypes map[string]*elaboratedTypeInfo
|
||||
enums map[string]enumInfo
|
||||
anonStructNum int
|
||||
}
|
||||
|
||||
// constantInfo stores some information about a CGo constant found by libclang
|
||||
@@ -68,8 +69,20 @@ type typedefInfo struct {
|
||||
// elaboratedTypeInfo contains some information about an elaborated type
|
||||
// (struct, union) found in the C AST.
|
||||
type elaboratedTypeInfo struct {
|
||||
typeExpr ast.Expr
|
||||
typeExpr *ast.StructType
|
||||
pos token.Pos
|
||||
bitfields []bitfieldInfo
|
||||
}
|
||||
|
||||
// bitfieldInfo contains information about a single bitfield in a struct. It
|
||||
// keeps information about the start, end, and the special (renamed) base field
|
||||
// of this bitfield.
|
||||
type bitfieldInfo struct {
|
||||
field *ast.Field
|
||||
name string
|
||||
pos token.Pos
|
||||
startBit int64
|
||||
endBit int64 // may be 0 meaning "until the end of the field"
|
||||
}
|
||||
|
||||
// enumInfo contains information about an enum in the C.
|
||||
@@ -581,10 +594,299 @@ func (p *cgoPackage) addElaboratedTypes() {
|
||||
}
|
||||
obj.Decl = typeSpec
|
||||
gen.Specs = append(gen.Specs, typeSpec)
|
||||
// If this struct has bitfields, create getters for them.
|
||||
for _, bitfield := range typ.bitfields {
|
||||
p.createBitfieldGetter(bitfield, typeName)
|
||||
p.createBitfieldSetter(bitfield, typeName)
|
||||
}
|
||||
}
|
||||
p.generated.Decls = append(p.generated.Decls, gen)
|
||||
}
|
||||
|
||||
// createBitfieldGetter creates a bitfield getter function like the following:
|
||||
//
|
||||
// func (s *C.struct_foo) bitfield_b() byte {
|
||||
// return (s.__bitfield_1 >> 5) & 0x1
|
||||
// }
|
||||
func (p *cgoPackage) createBitfieldGetter(bitfield bitfieldInfo, typeName string) {
|
||||
// The value to return from the getter.
|
||||
// Not complete: this is just an expression to get the complete field.
|
||||
var result ast.Expr = &ast.SelectorExpr{
|
||||
X: &ast.Ident{
|
||||
NamePos: bitfield.pos,
|
||||
Name: "s",
|
||||
Obj: nil,
|
||||
},
|
||||
Sel: &ast.Ident{
|
||||
NamePos: bitfield.pos,
|
||||
Name: bitfield.field.Names[0].Name,
|
||||
},
|
||||
}
|
||||
if bitfield.startBit != 0 {
|
||||
// Shift to the right by .startBit so that fields that come before are
|
||||
// shifted off.
|
||||
result = &ast.BinaryExpr{
|
||||
X: result,
|
||||
OpPos: bitfield.pos,
|
||||
Op: token.SHR,
|
||||
Y: &ast.BasicLit{
|
||||
ValuePos: bitfield.pos,
|
||||
Kind: token.INT,
|
||||
Value: strconv.FormatInt(bitfield.startBit, 10),
|
||||
},
|
||||
}
|
||||
}
|
||||
if bitfield.endBit != 0 {
|
||||
// Mask off the high bits so that fields that come after this field are
|
||||
// masked off.
|
||||
and := (uint64(1) << uint64(bitfield.endBit-bitfield.startBit)) - 1
|
||||
result = &ast.BinaryExpr{
|
||||
X: result,
|
||||
OpPos: bitfield.pos,
|
||||
Op: token.AND,
|
||||
Y: &ast.BasicLit{
|
||||
ValuePos: bitfield.pos,
|
||||
Kind: token.INT,
|
||||
Value: "0x" + strconv.FormatUint(and, 16),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// Create the getter function.
|
||||
getter := &ast.FuncDecl{
|
||||
Recv: &ast.FieldList{
|
||||
Opening: bitfield.pos,
|
||||
List: []*ast.Field{
|
||||
&ast.Field{
|
||||
Names: []*ast.Ident{
|
||||
&ast.Ident{
|
||||
NamePos: bitfield.pos,
|
||||
Name: "s",
|
||||
Obj: &ast.Object{
|
||||
Kind: ast.Var,
|
||||
Name: "s",
|
||||
Decl: nil,
|
||||
},
|
||||
},
|
||||
},
|
||||
Type: &ast.StarExpr{
|
||||
Star: bitfield.pos,
|
||||
X: &ast.Ident{
|
||||
NamePos: bitfield.pos,
|
||||
Name: typeName,
|
||||
Obj: nil,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
Closing: bitfield.pos,
|
||||
},
|
||||
Name: &ast.Ident{
|
||||
NamePos: bitfield.pos,
|
||||
Name: "bitfield_" + bitfield.name,
|
||||
},
|
||||
Type: &ast.FuncType{
|
||||
Func: bitfield.pos,
|
||||
Params: &ast.FieldList{
|
||||
Opening: bitfield.pos,
|
||||
Closing: bitfield.pos,
|
||||
},
|
||||
Results: &ast.FieldList{
|
||||
List: []*ast.Field{
|
||||
&ast.Field{
|
||||
Type: bitfield.field.Type,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
Body: &ast.BlockStmt{
|
||||
Lbrace: bitfield.pos,
|
||||
List: []ast.Stmt{
|
||||
&ast.ReturnStmt{
|
||||
Return: bitfield.pos,
|
||||
Results: []ast.Expr{
|
||||
result,
|
||||
},
|
||||
},
|
||||
},
|
||||
Rbrace: bitfield.pos,
|
||||
},
|
||||
}
|
||||
p.generated.Decls = append(p.generated.Decls, getter)
|
||||
}
|
||||
|
||||
// createBitfieldSetter creates a bitfield setter function like the following:
|
||||
//
|
||||
// func (s *C.struct_foo) set_bitfield_b(value byte) {
|
||||
// s.__bitfield_1 = s.__bitfield_1 ^ 0x60 | ((value & 1) << 5)
|
||||
// }
|
||||
//
|
||||
// Or the following:
|
||||
//
|
||||
// func (s *C.struct_foo) set_bitfield_c(value byte) {
|
||||
// s.__bitfield_1 = s.__bitfield_1 & 0x3f | (value << 6)
|
||||
// }
|
||||
func (p *cgoPackage) createBitfieldSetter(bitfield bitfieldInfo, typeName string) {
|
||||
// The full field with all bitfields.
|
||||
var field ast.Expr = &ast.SelectorExpr{
|
||||
X: &ast.Ident{
|
||||
NamePos: bitfield.pos,
|
||||
Name: "s",
|
||||
Obj: nil,
|
||||
},
|
||||
Sel: &ast.Ident{
|
||||
NamePos: bitfield.pos,
|
||||
Name: bitfield.field.Names[0].Name,
|
||||
},
|
||||
}
|
||||
// The value to insert into the field.
|
||||
var valueToInsert ast.Expr = &ast.Ident{
|
||||
NamePos: bitfield.pos,
|
||||
Name: "value",
|
||||
}
|
||||
|
||||
if bitfield.endBit != 0 {
|
||||
// Make sure the value is in range with a mask.
|
||||
valueToInsert = &ast.BinaryExpr{
|
||||
X: valueToInsert,
|
||||
OpPos: bitfield.pos,
|
||||
Op: token.AND,
|
||||
Y: &ast.BasicLit{
|
||||
ValuePos: bitfield.pos,
|
||||
Kind: token.INT,
|
||||
Value: "0x" + strconv.FormatUint((uint64(1)<<uint64(bitfield.endBit-bitfield.startBit))-1, 16),
|
||||
},
|
||||
}
|
||||
// Create a mask for the AND NOT operation.
|
||||
mask := ((uint64(1) << uint64(bitfield.endBit-bitfield.startBit)) - 1) << uint64(bitfield.startBit)
|
||||
// Zero the bits in the field that will soon be inserted.
|
||||
field = &ast.BinaryExpr{
|
||||
X: field,
|
||||
OpPos: bitfield.pos,
|
||||
Op: token.AND_NOT,
|
||||
Y: &ast.BasicLit{
|
||||
ValuePos: bitfield.pos,
|
||||
Kind: token.INT,
|
||||
Value: "0x" + strconv.FormatUint(mask, 16),
|
||||
},
|
||||
}
|
||||
} else { // bitfield.endBit == 0
|
||||
// We don't know exactly how many high bits should be zeroed. So we do
|
||||
// something different: keep the low bits with a mask and OR the new
|
||||
// value with it.
|
||||
mask := (uint64(1) << uint64(bitfield.startBit)) - 1
|
||||
// Extract the lower bits.
|
||||
field = &ast.BinaryExpr{
|
||||
X: field,
|
||||
OpPos: bitfield.pos,
|
||||
Op: token.AND,
|
||||
Y: &ast.BasicLit{
|
||||
ValuePos: bitfield.pos,
|
||||
Kind: token.INT,
|
||||
Value: "0x" + strconv.FormatUint(mask, 16),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// Bitwise OR with the new value (after the new value has been shifted).
|
||||
field = &ast.BinaryExpr{
|
||||
X: field,
|
||||
OpPos: bitfield.pos,
|
||||
Op: token.OR,
|
||||
Y: &ast.BinaryExpr{
|
||||
X: valueToInsert,
|
||||
OpPos: bitfield.pos,
|
||||
Op: token.SHL,
|
||||
Y: &ast.BasicLit{
|
||||
ValuePos: bitfield.pos,
|
||||
Kind: token.INT,
|
||||
Value: strconv.FormatInt(bitfield.startBit, 10),
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
// Create the setter function.
|
||||
setter := &ast.FuncDecl{
|
||||
Recv: &ast.FieldList{
|
||||
Opening: bitfield.pos,
|
||||
List: []*ast.Field{
|
||||
&ast.Field{
|
||||
Names: []*ast.Ident{
|
||||
&ast.Ident{
|
||||
NamePos: bitfield.pos,
|
||||
Name: "s",
|
||||
Obj: &ast.Object{
|
||||
Kind: ast.Var,
|
||||
Name: "s",
|
||||
Decl: nil,
|
||||
},
|
||||
},
|
||||
},
|
||||
Type: &ast.StarExpr{
|
||||
Star: bitfield.pos,
|
||||
X: &ast.Ident{
|
||||
NamePos: bitfield.pos,
|
||||
Name: typeName,
|
||||
Obj: nil,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
Closing: bitfield.pos,
|
||||
},
|
||||
Name: &ast.Ident{
|
||||
NamePos: bitfield.pos,
|
||||
Name: "set_bitfield_" + bitfield.name,
|
||||
},
|
||||
Type: &ast.FuncType{
|
||||
Func: bitfield.pos,
|
||||
Params: &ast.FieldList{
|
||||
Opening: bitfield.pos,
|
||||
List: []*ast.Field{
|
||||
&ast.Field{
|
||||
Names: []*ast.Ident{
|
||||
&ast.Ident{
|
||||
NamePos: bitfield.pos,
|
||||
Name: "value",
|
||||
Obj: nil,
|
||||
},
|
||||
},
|
||||
Type: bitfield.field.Type,
|
||||
},
|
||||
},
|
||||
Closing: bitfield.pos,
|
||||
},
|
||||
},
|
||||
Body: &ast.BlockStmt{
|
||||
Lbrace: bitfield.pos,
|
||||
List: []ast.Stmt{
|
||||
&ast.AssignStmt{
|
||||
Lhs: []ast.Expr{
|
||||
&ast.SelectorExpr{
|
||||
X: &ast.Ident{
|
||||
NamePos: bitfield.pos,
|
||||
Name: "s",
|
||||
Obj: nil,
|
||||
},
|
||||
Sel: &ast.Ident{
|
||||
NamePos: bitfield.pos,
|
||||
Name: bitfield.field.Names[0].Name,
|
||||
},
|
||||
},
|
||||
},
|
||||
TokPos: bitfield.pos,
|
||||
Tok: token.ASSIGN,
|
||||
Rhs: []ast.Expr{
|
||||
field,
|
||||
},
|
||||
},
|
||||
},
|
||||
Rbrace: bitfield.pos,
|
||||
},
|
||||
}
|
||||
p.generated.Decls = append(p.generated.Decls, setter)
|
||||
}
|
||||
|
||||
// addEnumTypes adds C enums to the AST. For example, the following C code:
|
||||
//
|
||||
// enum option {
|
||||
|
||||
+158
-69
@@ -51,6 +51,7 @@ CXSourceRange tinygo_clang_getCursorExtent(GoCXCursor c);
|
||||
CXTranslationUnit tinygo_clang_Cursor_getTranslationUnit(GoCXCursor c);
|
||||
long long tinygo_clang_getEnumConstantDeclValue(GoCXCursor c);
|
||||
CXType tinygo_clang_getEnumDeclIntegerType(GoCXCursor c);
|
||||
unsigned tinygo_clang_Cursor_isBitField(GoCXCursor c);
|
||||
|
||||
int tinygo_clang_globals_visitor(GoCXCursor c, GoCXCursor parent, CXClientData client_data);
|
||||
int tinygo_clang_struct_visitor(GoCXCursor c, GoCXCursor parent, CXClientData client_data);
|
||||
@@ -512,78 +513,48 @@ func (p *cgoPackage) makeASTType(typ C.CXType, pos token.Pos) ast.Expr {
|
||||
case C.CXType_Record:
|
||||
cursor := C.tinygo_clang_getTypeDeclaration(typ)
|
||||
name := getString(C.tinygo_clang_getCursorSpelling(cursor))
|
||||
var cgoName string
|
||||
switch C.tinygo_clang_getCursorKind(cursor) {
|
||||
case C.CXCursor_StructDecl:
|
||||
cgoName = "struct_" + name
|
||||
case C.CXCursor_UnionDecl:
|
||||
cgoName = "union_" + name
|
||||
default:
|
||||
panic("unknown record declaration")
|
||||
}
|
||||
if _, ok := p.elaboratedTypes[cgoName]; !ok {
|
||||
p.elaboratedTypes[cgoName] = nil // predeclare (to avoid endless recursion)
|
||||
fieldList := &ast.FieldList{
|
||||
Opening: pos,
|
||||
Closing: pos,
|
||||
if name == "" {
|
||||
// Anonymous record, probably inside a typedef.
|
||||
typeExpr, bitfieldList := p.makeASTRecordType(cursor, pos)
|
||||
if bitfieldList != nil {
|
||||
// This struct has bitfields, so we have to declare it as a
|
||||
// named type (for bitfield getters/setters to work).
|
||||
p.anonStructNum++
|
||||
cgoName := "struct_" + strconv.Itoa(p.anonStructNum)
|
||||
p.elaboratedTypes[cgoName] = &elaboratedTypeInfo{
|
||||
typeExpr: typeExpr,
|
||||
pos: pos,
|
||||
bitfields: bitfieldList,
|
||||
}
|
||||
return &ast.Ident{
|
||||
NamePos: pos,
|
||||
Name: "C." + cgoName,
|
||||
}
|
||||
}
|
||||
ref := storedRefs.Put(struct {
|
||||
fieldList *ast.FieldList
|
||||
pkg *cgoPackage
|
||||
}{fieldList, p})
|
||||
defer storedRefs.Remove(ref)
|
||||
C.tinygo_clang_visitChildren(cursor, C.CXCursorVisitor(C.tinygo_clang_struct_visitor), C.CXClientData(ref))
|
||||
return typeExpr
|
||||
} else {
|
||||
var cgoName string
|
||||
switch C.tinygo_clang_getCursorKind(cursor) {
|
||||
case C.CXCursor_StructDecl:
|
||||
p.elaboratedTypes[cgoName] = &elaboratedTypeInfo{
|
||||
typeExpr: &ast.StructType{
|
||||
Struct: pos,
|
||||
Fields: fieldList,
|
||||
},
|
||||
pos: pos,
|
||||
}
|
||||
cgoName = "struct_" + name
|
||||
case C.CXCursor_UnionDecl:
|
||||
if len(fieldList.List) > 1 {
|
||||
// Insert a special field at the front (of zero width) as a
|
||||
// marker that this is struct is actually a union. This is done
|
||||
// by giving the field a name that cannot be expressed directly
|
||||
// in Go.
|
||||
// Other parts of the compiler look at the first element in a
|
||||
// struct (of size > 2) to know whether this is a union.
|
||||
// Note that we don't have to insert it for single-element
|
||||
// unions as they're basically equivalent to a struct.
|
||||
unionMarker := &ast.Field{
|
||||
Type: &ast.StructType{
|
||||
Struct: pos,
|
||||
},
|
||||
}
|
||||
unionMarker.Names = []*ast.Ident{
|
||||
&ast.Ident{
|
||||
NamePos: pos,
|
||||
Name: "C union",
|
||||
Obj: &ast.Object{
|
||||
Kind: ast.Var,
|
||||
Name: "C union",
|
||||
Decl: unionMarker,
|
||||
},
|
||||
},
|
||||
}
|
||||
fieldList.List = append([]*ast.Field{unionMarker}, fieldList.List...)
|
||||
}
|
||||
p.elaboratedTypes[cgoName] = &elaboratedTypeInfo{
|
||||
typeExpr: &ast.StructType{
|
||||
Struct: pos,
|
||||
Fields: fieldList,
|
||||
},
|
||||
pos: pos,
|
||||
}
|
||||
cgoName = "union_" + name
|
||||
default:
|
||||
panic("unreachable")
|
||||
panic("unknown record declaration")
|
||||
}
|
||||
if _, ok := p.elaboratedTypes[cgoName]; !ok {
|
||||
p.elaboratedTypes[cgoName] = nil // predeclare (to avoid endless recursion)
|
||||
typeExpr, bitfieldList := p.makeASTRecordType(cursor, pos)
|
||||
p.elaboratedTypes[cgoName] = &elaboratedTypeInfo{
|
||||
typeExpr: typeExpr,
|
||||
pos: pos,
|
||||
bitfields: bitfieldList,
|
||||
}
|
||||
}
|
||||
return &ast.Ident{
|
||||
NamePos: pos,
|
||||
Name: "C." + cgoName,
|
||||
}
|
||||
}
|
||||
return &ast.Ident{
|
||||
NamePos: pos,
|
||||
Name: "C." + cgoName,
|
||||
}
|
||||
case C.CXType_Enum:
|
||||
cursor := C.tinygo_clang_getTypeDeclaration(typ)
|
||||
@@ -629,25 +600,143 @@ func (p *cgoPackage) makeASTType(typ C.CXType, pos token.Pos) ast.Expr {
|
||||
}
|
||||
}
|
||||
|
||||
// makeASTRecordType parses a C record (struct or union) and translates it into
|
||||
// a Go struct type. Unions are implemented by setting the first field to a
|
||||
// zero-lengt "C union" field, which cannot be written in Go directly.
|
||||
func (p *cgoPackage) makeASTRecordType(cursor C.GoCXCursor, pos token.Pos) (*ast.StructType, []bitfieldInfo) {
|
||||
fieldList := &ast.FieldList{
|
||||
Opening: pos,
|
||||
Closing: pos,
|
||||
}
|
||||
var bitfieldList []bitfieldInfo
|
||||
inBitfield := false
|
||||
bitfieldNum := 0
|
||||
ref := storedRefs.Put(struct {
|
||||
fieldList *ast.FieldList
|
||||
pkg *cgoPackage
|
||||
inBitfield *bool
|
||||
bitfieldNum *int
|
||||
bitfieldList *[]bitfieldInfo
|
||||
}{fieldList, p, &inBitfield, &bitfieldNum, &bitfieldList})
|
||||
defer storedRefs.Remove(ref)
|
||||
C.tinygo_clang_visitChildren(cursor, C.CXCursorVisitor(C.tinygo_clang_struct_visitor), C.CXClientData(ref))
|
||||
switch C.tinygo_clang_getCursorKind(cursor) {
|
||||
case C.CXCursor_StructDecl:
|
||||
return &ast.StructType{
|
||||
Struct: pos,
|
||||
Fields: fieldList,
|
||||
}, bitfieldList
|
||||
case C.CXCursor_UnionDecl:
|
||||
if bitfieldList != nil {
|
||||
// This is valid C... but please don't do this.
|
||||
p.errors = append(p.errors, scanner.Error{
|
||||
Pos: p.fset.PositionFor(pos, true),
|
||||
Msg: fmt.Sprintf("bitfield in a union is not supported"),
|
||||
})
|
||||
}
|
||||
if len(fieldList.List) > 1 {
|
||||
// Insert a special field at the front (of zero width) as a
|
||||
// marker that this is struct is actually a union. This is done
|
||||
// by giving the field a name that cannot be expressed directly
|
||||
// in Go.
|
||||
// Other parts of the compiler look at the first element in a
|
||||
// struct (of size > 2) to know whether this is a union.
|
||||
// Note that we don't have to insert it for single-element
|
||||
// unions as they're basically equivalent to a struct.
|
||||
unionMarker := &ast.Field{
|
||||
Type: &ast.StructType{
|
||||
Struct: pos,
|
||||
},
|
||||
}
|
||||
unionMarker.Names = []*ast.Ident{
|
||||
&ast.Ident{
|
||||
NamePos: pos,
|
||||
Name: "C union",
|
||||
Obj: &ast.Object{
|
||||
Kind: ast.Var,
|
||||
Name: "C union",
|
||||
Decl: unionMarker,
|
||||
},
|
||||
},
|
||||
}
|
||||
fieldList.List = append([]*ast.Field{unionMarker}, fieldList.List...)
|
||||
}
|
||||
return &ast.StructType{
|
||||
Struct: pos,
|
||||
Fields: fieldList,
|
||||
}, bitfieldList
|
||||
default:
|
||||
panic("unknown record declaration")
|
||||
}
|
||||
}
|
||||
|
||||
//export tinygo_clang_struct_visitor
|
||||
func tinygo_clang_struct_visitor(c, parent C.GoCXCursor, client_data C.CXClientData) C.int {
|
||||
passed := storedRefs.Get(unsafe.Pointer(client_data)).(struct {
|
||||
fieldList *ast.FieldList
|
||||
pkg *cgoPackage
|
||||
fieldList *ast.FieldList
|
||||
pkg *cgoPackage
|
||||
inBitfield *bool
|
||||
bitfieldNum *int
|
||||
bitfieldList *[]bitfieldInfo
|
||||
})
|
||||
fieldList := passed.fieldList
|
||||
p := passed.pkg
|
||||
inBitfield := passed.inBitfield
|
||||
bitfieldNum := passed.bitfieldNum
|
||||
bitfieldList := passed.bitfieldList
|
||||
if C.tinygo_clang_getCursorKind(c) != C.CXCursor_FieldDecl {
|
||||
panic("expected field inside cursor")
|
||||
}
|
||||
name := getString(C.tinygo_clang_getCursorSpelling(c))
|
||||
if name == "" {
|
||||
// Assume this is a bitfield of 0 bits.
|
||||
// Warning: this is not necessarily true!
|
||||
return C.CXChildVisit_Continue
|
||||
}
|
||||
typ := C.tinygo_clang_getCursorType(c)
|
||||
pos := p.getCursorPosition(c)
|
||||
field := &ast.Field{
|
||||
Type: p.makeASTType(typ, p.getCursorPosition(c)),
|
||||
}
|
||||
offsetof := int64(C.clang_Type_getOffsetOf(C.tinygo_clang_getCursorType(parent), C.CString(name)))
|
||||
alignOf := int64(C.clang_Type_getAlignOf(typ) * 8)
|
||||
bitfieldOffset := offsetof % alignOf
|
||||
if bitfieldOffset != 0 {
|
||||
if C.tinygo_clang_Cursor_isBitField(c) != 1 {
|
||||
panic("expected a bitfield")
|
||||
}
|
||||
if !*inBitfield {
|
||||
*bitfieldNum++
|
||||
}
|
||||
bitfieldName := "__bitfield_" + strconv.Itoa(*bitfieldNum)
|
||||
prevField := fieldList.List[len(fieldList.List)-1]
|
||||
if !*inBitfield {
|
||||
// The previous element also was a bitfield, but wasn't noticed
|
||||
// then. Add it now.
|
||||
*inBitfield = true
|
||||
*bitfieldList = append(*bitfieldList, bitfieldInfo{
|
||||
field: prevField,
|
||||
name: prevField.Names[0].Name,
|
||||
startBit: 0,
|
||||
pos: prevField.Names[0].NamePos,
|
||||
})
|
||||
prevField.Names[0].Name = bitfieldName
|
||||
prevField.Names[0].Obj.Name = bitfieldName
|
||||
}
|
||||
prevBitfield := &(*bitfieldList)[len(*bitfieldList)-1]
|
||||
prevBitfield.endBit = bitfieldOffset
|
||||
*bitfieldList = append(*bitfieldList, bitfieldInfo{
|
||||
field: prevField,
|
||||
name: name,
|
||||
startBit: bitfieldOffset,
|
||||
pos: pos,
|
||||
})
|
||||
return C.CXChildVisit_Continue
|
||||
}
|
||||
*inBitfield = false
|
||||
field.Names = []*ast.Ident{
|
||||
&ast.Ident{
|
||||
NamePos: p.getCursorPosition(c),
|
||||
NamePos: pos,
|
||||
Name: name,
|
||||
Obj: &ast.Object{
|
||||
Kind: ast.Var,
|
||||
|
||||
@@ -64,3 +64,7 @@ long long tinygo_clang_getEnumConstantDeclValue(CXCursor c) {
|
||||
CXType tinygo_clang_getEnumDeclIntegerType(CXCursor c) {
|
||||
return clang_getEnumDeclIntegerType(c);
|
||||
}
|
||||
|
||||
unsigned tinygo_clang_Cursor_isBitField(CXCursor c) {
|
||||
return clang_Cursor_isBitField(c);
|
||||
}
|
||||
@@ -37,6 +37,7 @@ func execCommand(cmdNames []string, args ...string) error {
|
||||
// this command was not found, try the next
|
||||
continue
|
||||
}
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
+15
-3
@@ -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.
|
||||
|
||||
+2
-2
@@ -38,7 +38,7 @@ func (c *Compiler) createCall(fn llvm.Value, args []llvm.Value, name string) llv
|
||||
}
|
||||
|
||||
// Expand an argument type to a list that can be used in a function call
|
||||
// paramter list.
|
||||
// parameter list.
|
||||
func (c *Compiler) expandFormalParamType(t llvm.Type) []llvm.Type {
|
||||
switch t.TypeKind() {
|
||||
case llvm.StructTypeKind:
|
||||
@@ -163,7 +163,7 @@ func (c *Compiler) collapseFormalParamInternal(t llvm.Type, fields []llvm.Value)
|
||||
switch t.TypeKind() {
|
||||
case llvm.StructTypeKind:
|
||||
if len(c.flattenAggregateType(t)) <= MaxFieldsPerParam {
|
||||
value := c.getZeroValue(t)
|
||||
value := llvm.ConstNull(t)
|
||||
for i, subtyp := range t.StructElementTypes() {
|
||||
structField, remaining := c.collapseFormalParamInternal(subtyp, fields)
|
||||
fields = remaining
|
||||
|
||||
+170
-11
@@ -4,6 +4,7 @@ package compiler
|
||||
// or pseudo-operations that are lowered during goroutine lowering.
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"go/types"
|
||||
|
||||
"golang.org/x/tools/go/ssa"
|
||||
@@ -12,11 +13,22 @@ import (
|
||||
|
||||
// emitMakeChan returns a new channel value for the given channel type.
|
||||
func (c *Compiler) emitMakeChan(expr *ssa.MakeChan) (llvm.Value, error) {
|
||||
chanType := c.mod.GetTypeByName("runtime.channel")
|
||||
size := c.targetData.TypeAllocSize(chanType)
|
||||
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, llvm.PointerType(chanType, 0), "chan")
|
||||
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),
|
||||
}, "")
|
||||
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
|
||||
}
|
||||
|
||||
@@ -33,8 +45,7 @@ func (c *Compiler) emitChanSend(frame *Frame, instr *ssa.Send) {
|
||||
|
||||
// Do the send.
|
||||
coroutine := c.createRuntimeCall("getCoroutine", nil, "")
|
||||
valueSize := llvm.ConstInt(c.uintptrType, c.targetData.TypeAllocSize(chanValue.Type()), false)
|
||||
c.createRuntimeCall("chanSend", []llvm.Value{coroutine, ch, valueAllocaCast, valueSize}, "")
|
||||
c.createRuntimeCall("chanSend", []llvm.Value{coroutine, ch, valueAllocaCast}, "")
|
||||
|
||||
// End the lifetime of the alloca.
|
||||
// This also works around a bug in CoroSplit, at least in LLVM 8:
|
||||
@@ -53,13 +64,12 @@ func (c *Compiler) emitChanRecv(frame *Frame, unop *ssa.UnOp) llvm.Value {
|
||||
|
||||
// Do the receive.
|
||||
coroutine := c.createRuntimeCall("getCoroutine", nil, "")
|
||||
valueSize := llvm.ConstInt(c.uintptrType, c.targetData.TypeAllocSize(valueType), false)
|
||||
c.createRuntimeCall("chanRecv", []llvm.Value{coroutine, ch, valueAllocaCast, valueSize}, "")
|
||||
c.createRuntimeCall("chanRecv", []llvm.Value{coroutine, 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.createRuntimeCall("getTaskStateData", []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, "")
|
||||
@@ -72,8 +82,157 @@ func (c *Compiler) emitChanRecv(frame *Frame, unop *ssa.UnOp) llvm.Value {
|
||||
|
||||
// emitChanClose closes the given channel.
|
||||
func (c *Compiler) emitChanClose(frame *Frame, param ssa.Value) {
|
||||
valueType := c.getLLVMType(param.Type().(*types.Chan).Elem())
|
||||
valueSize := llvm.ConstInt(c.uintptrType, c.targetData.TypeAllocSize(valueType), false)
|
||||
ch := c.getValue(frame, param)
|
||||
c.createRuntimeCall("chanClose", []llvm.Value{ch, valueSize}, "")
|
||||
c.createRuntimeCall("chanClose", []llvm.Value{ch}, "")
|
||||
}
|
||||
|
||||
// emitSelect emits all IR necessary for a select statements. That's a
|
||||
// non-trivial amount of code because select is very complex to implement.
|
||||
func (c *Compiler) emitSelect(frame *Frame, expr *ssa.Select) llvm.Value {
|
||||
if len(expr.States) == 0 {
|
||||
// Shortcuts for some simple selects.
|
||||
llvmType := c.getLLVMType(expr.Type())
|
||||
if expr.Blocking {
|
||||
// Blocks forever:
|
||||
// select {}
|
||||
c.createRuntimeCall("deadlock", nil, "")
|
||||
return llvm.Undef(llvmType)
|
||||
} else {
|
||||
// No-op:
|
||||
// select {
|
||||
// default:
|
||||
// }
|
||||
retval := llvm.Undef(llvmType)
|
||||
retval = c.builder.CreateInsertValue(retval, llvm.ConstInt(c.intType, 0xffffffffffffffff, true), 0, "")
|
||||
return retval // {-1, false}
|
||||
}
|
||||
}
|
||||
|
||||
// This code create a (stack-allocated) slice containing all the select
|
||||
// cases and then calls runtime.chanSelect to perform the actual select
|
||||
// statement.
|
||||
// Simple selects (blocking and with just one case) are already transformed
|
||||
// into regular chan operations during SSA construction so we don't have to
|
||||
// optimize such small selects.
|
||||
|
||||
// Go through all the cases. Create the selectStates slice and and
|
||||
// determine the receive buffer size and alignment.
|
||||
recvbufSize := uint64(0)
|
||||
recvbufAlign := 0
|
||||
hasReceives := false
|
||||
var selectStates []llvm.Value
|
||||
chanSelectStateType := c.getLLVMRuntimeType("chanSelectState")
|
||||
for _, state := range expr.States {
|
||||
ch := c.getValue(frame, state.Chan)
|
||||
selectState := llvm.ConstNull(chanSelectStateType)
|
||||
selectState = c.builder.CreateInsertValue(selectState, ch, 0, "")
|
||||
switch state.Dir {
|
||||
case types.RecvOnly:
|
||||
// Make sure the receive buffer is big enough and has the correct alignment.
|
||||
llvmType := c.getLLVMType(state.Chan.Type().(*types.Chan).Elem())
|
||||
if size := c.targetData.TypeAllocSize(llvmType); size > recvbufSize {
|
||||
recvbufSize = size
|
||||
}
|
||||
if align := c.targetData.ABITypeAlignment(llvmType); align > recvbufAlign {
|
||||
recvbufAlign = align
|
||||
}
|
||||
hasReceives = true
|
||||
case types.SendOnly:
|
||||
// Store this value in an alloca and put a pointer to this alloca
|
||||
// in the send state.
|
||||
sendValue := c.getValue(frame, state.Send)
|
||||
alloca := c.createEntryBlockAlloca(sendValue.Type(), "select.send.value")
|
||||
c.builder.CreateStore(sendValue, alloca)
|
||||
ptr := c.builder.CreateBitCast(alloca, c.i8ptrType, "")
|
||||
selectState = c.builder.CreateInsertValue(selectState, ptr, 1, "")
|
||||
default:
|
||||
panic("unreachable")
|
||||
}
|
||||
selectStates = append(selectStates, selectState)
|
||||
}
|
||||
|
||||
// Create a receive buffer, where the received value will be stored.
|
||||
recvbuf := llvm.Undef(c.i8ptrType)
|
||||
if hasReceives {
|
||||
allocaType := llvm.ArrayType(c.ctx.Int8Type(), int(recvbufSize))
|
||||
recvbufAlloca := c.builder.CreateAlloca(allocaType, "select.recvbuf.alloca")
|
||||
recvbufAlloca.SetAlignment(recvbufAlign)
|
||||
recvbuf = c.builder.CreateGEP(recvbufAlloca, []llvm.Value{
|
||||
llvm.ConstInt(c.ctx.Int32Type(), 0, false),
|
||||
llvm.ConstInt(c.ctx.Int32Type(), 0, false),
|
||||
}, "select.recvbuf")
|
||||
}
|
||||
|
||||
// Create the states slice (allocated on the stack).
|
||||
statesAllocaType := llvm.ArrayType(chanSelectStateType, len(selectStates))
|
||||
statesAlloca := c.builder.CreateAlloca(statesAllocaType, "select.states.alloca")
|
||||
for i, state := range selectStates {
|
||||
// Set each slice element to the appropriate channel.
|
||||
gep := c.builder.CreateGEP(statesAlloca, []llvm.Value{
|
||||
llvm.ConstInt(c.ctx.Int32Type(), 0, false),
|
||||
llvm.ConstInt(c.ctx.Int32Type(), uint64(i), false),
|
||||
}, "")
|
||||
c.builder.CreateStore(state, gep)
|
||||
}
|
||||
statesPtr := c.builder.CreateGEP(statesAlloca, []llvm.Value{
|
||||
llvm.ConstInt(c.ctx.Int32Type(), 0, false),
|
||||
llvm.ConstInt(c.ctx.Int32Type(), 0, false),
|
||||
}, "select.states")
|
||||
statesLen := llvm.ConstInt(c.uintptrType, uint64(len(selectStates)), false)
|
||||
|
||||
// Convert the 'blocking' flag on this select into a LLVM value.
|
||||
blockingInt := uint64(0)
|
||||
if expr.Blocking {
|
||||
blockingInt = 1
|
||||
}
|
||||
blockingValue := llvm.ConstInt(c.ctx.Int1Type(), blockingInt, false)
|
||||
|
||||
// Do the select in the runtime.
|
||||
results := c.createRuntimeCall("chanSelect", []llvm.Value{
|
||||
recvbuf,
|
||||
statesPtr, statesLen, statesLen, // []chanSelectState
|
||||
blockingValue,
|
||||
}, "")
|
||||
|
||||
// The result value does not include all the possible received values,
|
||||
// because we can't load them in advance. Instead, the *ssa.Extract
|
||||
// instruction will treat a *ssa.Select specially and load it there inline.
|
||||
// Store the receive alloca in a sidetable until we hit this extract
|
||||
// instruction.
|
||||
if frame.selectRecvBuf == nil {
|
||||
frame.selectRecvBuf = make(map[*ssa.Select]llvm.Value)
|
||||
}
|
||||
frame.selectRecvBuf[expr] = recvbuf
|
||||
|
||||
return results
|
||||
}
|
||||
|
||||
// getChanSelectResult returns the special values from a *ssa.Extract expression
|
||||
// when extracting a value from a select statement (*ssa.Select). Because
|
||||
// *ssa.Select cannot load all values in advance, it does this later in the
|
||||
// *ssa.Extract expression.
|
||||
func (c *Compiler) getChanSelectResult(frame *Frame, expr *ssa.Extract) llvm.Value {
|
||||
if expr.Index == 0 {
|
||||
// index
|
||||
value := c.getValue(frame, expr.Tuple)
|
||||
index := c.builder.CreateExtractValue(value, expr.Index, "")
|
||||
if index.Type().IntTypeWidth() < c.intType.IntTypeWidth() {
|
||||
index = c.builder.CreateSExt(index, c.intType, "")
|
||||
}
|
||||
return index
|
||||
} else if expr.Index == 1 {
|
||||
// comma-ok
|
||||
value := c.getValue(frame, expr.Tuple)
|
||||
return c.builder.CreateExtractValue(value, expr.Index, "")
|
||||
} else {
|
||||
// Select statements are (index, ok, ...) where ... is a number of
|
||||
// received values, depending on how many receive statements there
|
||||
// are. They are all combined into one alloca (because only one
|
||||
// receive can proceed at a time) so we'll get that alloca, bitcast
|
||||
// it to the correct type, and dereference it.
|
||||
recvbuf := frame.selectRecvBuf[expr.Tuple.(*ssa.Select)]
|
||||
typ := llvm.PointerType(c.getLLVMType(expr.Type()), 0)
|
||||
ptr := c.builder.CreateBitCast(recvbuf, typ, "")
|
||||
return c.builder.CreateLoad(ptr, "")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
+244
-199
@@ -3,6 +3,7 @@ package compiler
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"go/ast"
|
||||
"go/build"
|
||||
"go/constant"
|
||||
"go/token"
|
||||
@@ -26,6 +27,24 @@ func init() {
|
||||
llvm.InitializeAllAsmPrinters()
|
||||
}
|
||||
|
||||
// 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.sleepTask",
|
||||
"runtime.sleepCurrentTask",
|
||||
"runtime.setTaskStatePtr",
|
||||
"runtime.getTaskStatePtr",
|
||||
"runtime.activateTask",
|
||||
"runtime.scheduler",
|
||||
"runtime.startGoroutine",
|
||||
}
|
||||
|
||||
// Configure the compiler.
|
||||
type Config struct {
|
||||
Triple string // LLVM target triple, e.g. x86_64-unknown-linux-gnu (empty string means default)
|
||||
@@ -34,15 +53,24 @@ 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
|
||||
GOPATH string // GOPATH, like `go env GOPATH`
|
||||
BuildTags []string // build tags for TinyGo (empty means {Config.GOOS/Config.GOARCH})
|
||||
TestConfig TestConfig
|
||||
}
|
||||
|
||||
type TestConfig struct {
|
||||
CompileTestBinary bool
|
||||
// TODO: Filter the test functions to run, include verbose flag, etc
|
||||
}
|
||||
|
||||
type Compiler struct {
|
||||
@@ -63,6 +91,7 @@ type Compiler struct {
|
||||
interfaceInvokeWrappers []interfaceInvokeWrapper
|
||||
ir *ir.Program
|
||||
diagnostics []error
|
||||
astComments map[string]*ast.CommentGroup
|
||||
}
|
||||
|
||||
type Frame struct {
|
||||
@@ -79,6 +108,7 @@ type Frame struct {
|
||||
deferFuncs map[*ir.Function]int
|
||||
deferInvokeFuncs map[string]int
|
||||
deferClosureFuncs map[*ir.Function]int
|
||||
selectRecvBuf map[*ssa.Select]llvm.Value
|
||||
}
|
||||
|
||||
type Phi struct {
|
||||
@@ -154,11 +184,21 @@ func (c *Compiler) TargetData() llvm.TargetData {
|
||||
|
||||
// selectGC picks an appropriate GC strategy if none was provided.
|
||||
func (c *Compiler) selectGC() string {
|
||||
gc := c.GC
|
||||
if gc == "" {
|
||||
gc = "dumb"
|
||||
if c.GC != "" {
|
||||
return c.GC
|
||||
}
|
||||
return gc
|
||||
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"
|
||||
}
|
||||
|
||||
// Compile the given package path or .go file path. Return an error when this
|
||||
@@ -177,6 +217,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,
|
||||
@@ -186,7 +227,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,
|
||||
@@ -196,24 +237,31 @@ 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,
|
||||
},
|
||||
ShouldOverlay: func(path string) bool {
|
||||
OverlayPath: func(path string) string {
|
||||
// Return the (overlay) import path when it should be overlaid, and
|
||||
// "" if it should not.
|
||||
if strings.HasPrefix(path, tinygoPath+"/src/") {
|
||||
// Avoid issues with packages that are imported twice, one from
|
||||
// GOPATH and one from TINYGOPATH.
|
||||
path = path[len(tinygoPath+"/src/"):]
|
||||
}
|
||||
switch path {
|
||||
case "machine", "os", "reflect", "runtime", "runtime/volatile", "sync":
|
||||
return true
|
||||
case "machine", "os", "reflect", "runtime", "runtime/volatile", "sync", "testing":
|
||||
return path
|
||||
default:
|
||||
if strings.HasPrefix(path, "device/") || strings.HasPrefix(path, "examples/") {
|
||||
return true
|
||||
return path
|
||||
} else if path == "syscall" {
|
||||
for _, tag := range c.BuildTags {
|
||||
if tag == "avr" || tag == "cortexm" || tag == "darwin" {
|
||||
return true
|
||||
if tag == "baremetal" || tag == "darwin" {
|
||||
return path
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
return ""
|
||||
},
|
||||
TypeChecker: types.Config{
|
||||
Sizes: &StdSizes{
|
||||
@@ -222,10 +270,12 @@ func (c *Compiler) Compile(mainPath string) []error {
|
||||
MaxAlign: int64(c.targetData.PrefTypeAlignment(c.i8ptrType)),
|
||||
},
|
||||
},
|
||||
Dir: wd,
|
||||
TINYGOROOT: c.TINYGOROOT,
|
||||
CFlags: c.CFlags,
|
||||
Dir: wd,
|
||||
TINYGOROOT: c.TINYGOROOT,
|
||||
CFlags: c.CFlags,
|
||||
ClangHeaders: c.ClangHeaders,
|
||||
}
|
||||
|
||||
if strings.HasSuffix(mainPath, ".go") {
|
||||
_, err = lprogram.ImportFile(mainPath)
|
||||
if err != nil {
|
||||
@@ -237,12 +287,13 @@ func (c *Compiler) Compile(mainPath string) []error {
|
||||
return []error{err}
|
||||
}
|
||||
}
|
||||
|
||||
_, err = lprogram.Import("runtime", "")
|
||||
if err != nil {
|
||||
return []error{err}
|
||||
}
|
||||
|
||||
err = lprogram.Parse()
|
||||
err = lprogram.Parse(c.TestConfig.CompileTestBinary)
|
||||
if err != nil {
|
||||
return []error{err}
|
||||
}
|
||||
@@ -265,39 +316,7 @@ func (c *Compiler) Compile(mainPath string) []error {
|
||||
|
||||
var frames []*Frame
|
||||
|
||||
// Declare all named struct types.
|
||||
for _, t := range c.ir.NamedTypes {
|
||||
if named, ok := t.Type.Type().(*types.Named); ok {
|
||||
if _, ok := named.Underlying().(*types.Struct); ok {
|
||||
t.LLVMType = c.ctx.StructCreateNamed(named.Obj().Pkg().Path() + "." + named.Obj().Name())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Define all named struct types.
|
||||
for _, t := range c.ir.NamedTypes {
|
||||
if named, ok := t.Type.Type().(*types.Named); ok {
|
||||
if st, ok := named.Underlying().(*types.Struct); ok {
|
||||
llvmType := c.getLLVMType(st)
|
||||
t.LLVMType.StructSetBody(llvmType.StructElementTypes(), false)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Declare all globals.
|
||||
for _, g := range c.ir.Globals {
|
||||
typ := g.Type().(*types.Pointer).Elem()
|
||||
llvmType := c.getLLVMType(typ)
|
||||
global := c.mod.NamedGlobal(g.LinkName())
|
||||
if global.IsNil() {
|
||||
global = llvm.AddGlobal(c.mod, llvmType, g.LinkName())
|
||||
}
|
||||
g.LLVMGlobal = global
|
||||
if !g.IsExtern() {
|
||||
global.SetLinkage(llvm.InternalLinkage)
|
||||
global.SetInitializer(c.getZeroValue(llvmType))
|
||||
}
|
||||
}
|
||||
c.loadASTComments(lprogram)
|
||||
|
||||
// Declare all functions.
|
||||
for _, f := range c.ir.Functions {
|
||||
@@ -345,13 +364,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 functionsUsedInTransforms {
|
||||
fn := c.mod.NamedFunction(name)
|
||||
if fn.IsNil() {
|
||||
continue
|
||||
}
|
||||
fn.SetLinkage(llvm.ExternalLinkage)
|
||||
}
|
||||
|
||||
// Load some attributes
|
||||
getAttr := func(attrName string) llvm.Attribute {
|
||||
@@ -371,6 +392,15 @@ func (c *Compiler) Compile(mainPath string) []error {
|
||||
// See emitNilCheck in asserts.go.
|
||||
c.mod.NamedFunction("runtime.isnil").AddAttributeAtIndex(1, nocapture)
|
||||
|
||||
// This function is necessary for tracking pointers on the stack in a
|
||||
// portable way (see gc.go). Indicate to the optimizer that the only thing
|
||||
// we'll do is read the pointer.
|
||||
trackPointer := c.mod.NamedFunction("runtime.trackPointer")
|
||||
if !trackPointer.IsNil() {
|
||||
trackPointer.AddAttributeAtIndex(1, nocapture)
|
||||
trackPointer.AddAttributeAtIndex(1, readonly)
|
||||
}
|
||||
|
||||
// Memory copy operations do not capture pointers, even though some weird
|
||||
// pointer arithmetic is happening in the Go implementation.
|
||||
for _, fnName := range []string{"runtime.memcpy", "runtime.memmove"} {
|
||||
@@ -403,6 +433,22 @@ func (c *Compiler) Compile(mainPath string) []error {
|
||||
return c.diagnostics
|
||||
}
|
||||
|
||||
// getRuntimeType obtains a named type from the runtime package and returns it
|
||||
// as a Go type.
|
||||
func (c *Compiler) getRuntimeType(name string) types.Type {
|
||||
return c.ir.Program.ImportedPackage("runtime").Type(name).Type()
|
||||
}
|
||||
|
||||
// getLLVMRuntimeType obtains a named type from the runtime package and returns
|
||||
// it as a LLVM type, creating it if necessary. It is a shorthand for
|
||||
// getLLVMType(getRuntimeType(name)).
|
||||
func (c *Compiler) getLLVMRuntimeType(name string) llvm.Type {
|
||||
return c.getLLVMType(c.getRuntimeType(name))
|
||||
}
|
||||
|
||||
// getLLVMType creates and returns a LLVM type for a Go type. In the case of
|
||||
// named struct types (or Go types implemented as named LLVM structs such as
|
||||
// strings) it also creates it first if necessary.
|
||||
func (c *Compiler) getLLVMType(goType types.Type) llvm.Type {
|
||||
switch typ := goType.(type) {
|
||||
case *types.Array:
|
||||
@@ -431,7 +477,7 @@ func (c *Compiler) getLLVMType(goType types.Type) llvm.Type {
|
||||
case types.Complex128:
|
||||
return c.ctx.StructType([]llvm.Type{c.ctx.DoubleType(), c.ctx.DoubleType()}, false)
|
||||
case types.String, types.UntypedString:
|
||||
return c.mod.GetTypeByName("runtime._string")
|
||||
return c.getLLVMRuntimeType("_string")
|
||||
case types.Uintptr:
|
||||
return c.uintptrType
|
||||
case types.UnsafePointer:
|
||||
@@ -440,16 +486,23 @@ func (c *Compiler) getLLVMType(goType types.Type) llvm.Type {
|
||||
panic("unknown basic type: " + typ.String())
|
||||
}
|
||||
case *types.Chan:
|
||||
return llvm.PointerType(c.mod.GetTypeByName("runtime.channel"), 0)
|
||||
return llvm.PointerType(c.getLLVMRuntimeType("channel"), 0)
|
||||
case *types.Interface:
|
||||
return c.mod.GetTypeByName("runtime._interface")
|
||||
return c.getLLVMRuntimeType("_interface")
|
||||
case *types.Map:
|
||||
return llvm.PointerType(c.mod.GetTypeByName("runtime.hashmap"), 0)
|
||||
return llvm.PointerType(c.getLLVMRuntimeType("hashmap"), 0)
|
||||
case *types.Named:
|
||||
if _, ok := typ.Underlying().(*types.Struct); ok {
|
||||
llvmType := c.mod.GetTypeByName(typ.Obj().Pkg().Path() + "." + typ.Obj().Name())
|
||||
if st, ok := typ.Underlying().(*types.Struct); ok {
|
||||
// Structs are a special case. While other named types are ignored
|
||||
// in LLVM IR, named structs are implemented as named structs in
|
||||
// LLVM. This is because it is otherwise impossible to create
|
||||
// self-referencing types such as linked lists.
|
||||
llvmName := typ.Obj().Pkg().Path() + "." + typ.Obj().Name()
|
||||
llvmType := c.mod.GetTypeByName(llvmName)
|
||||
if llvmType.IsNil() {
|
||||
panic("underlying type not found: " + typ.Obj().Pkg().Path() + "." + typ.Obj().Name())
|
||||
llvmType = c.ctx.StructCreateNamed(llvmName)
|
||||
underlying := c.getLLVMType(st)
|
||||
llvmType.StructSetBody(underlying.StructElementTypes(), false)
|
||||
}
|
||||
return llvmType
|
||||
}
|
||||
@@ -511,42 +564,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 {
|
||||
@@ -769,6 +786,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 {
|
||||
@@ -826,7 +848,8 @@ func (c *Compiler) parseFunc(frame *Frame) {
|
||||
fmt.Printf("\nfunc %s:\n", frame.fn.Function)
|
||||
}
|
||||
if !frame.fn.LLVMFn.IsDeclaration() {
|
||||
panic("function is already defined: " + frame.fn.LLVMFn.Name())
|
||||
c.addError(frame.fn.Pos(), "function is already defined:"+frame.fn.LLVMFn.Name())
|
||||
return
|
||||
}
|
||||
if !frame.fn.IsExported() {
|
||||
frame.fn.LLVMFn.SetLinkage(llvm.InternalLinkage)
|
||||
@@ -842,6 +865,10 @@ func (c *Compiler) parseFunc(frame *Frame) {
|
||||
// Add LLVM inline hint to functions with //go:inline pragma.
|
||||
inline := c.ctx.CreateEnumAttribute(llvm.AttributeKindID("inlinehint"), 0)
|
||||
frame.fn.LLVMFn.AddFunctionAttr(inline)
|
||||
case ir.InlineNone:
|
||||
// Add LLVM attribute to always avoid inlining this function.
|
||||
noinline := c.ctx.CreateEnumAttribute(llvm.AttributeKindID("noinline"), 0)
|
||||
frame.fn.LLVMFn.AddFunctionAttr(noinline)
|
||||
}
|
||||
|
||||
// Add debug info, if needed.
|
||||
@@ -994,42 +1021,56 @@ func (c *Compiler) parseInstr(frame *Frame, instr ssa.Instruction) {
|
||||
frame.locals[instr] = llvm.Undef(c.getLLVMType(instr.Type()))
|
||||
} else {
|
||||
frame.locals[instr] = value
|
||||
if len(*instr.Referrers()) != 0 && c.needsStackObjects() {
|
||||
c.trackExpr(frame, instr, value)
|
||||
}
|
||||
}
|
||||
case *ssa.DebugRef:
|
||||
// ignore
|
||||
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()
|
||||
@@ -1056,7 +1097,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, "")
|
||||
@@ -1075,12 +1116,7 @@ func (c *Compiler) parseInstr(frame *Frame, instr ssa.Instruction) {
|
||||
// nothing to store
|
||||
return
|
||||
}
|
||||
store := c.builder.CreateStore(llvmVal, llvmAddr)
|
||||
valType := instr.Addr.Type().Underlying().(*types.Pointer).Elem()
|
||||
if c.ir.IsVolatile(valType) {
|
||||
// Volatile store, for memory-mapped registers.
|
||||
store.SetVolatile(true)
|
||||
}
|
||||
c.builder.CreateStore(llvmVal, llvmAddr)
|
||||
default:
|
||||
c.addError(instr.Pos(), "unknown instruction: "+instr.String())
|
||||
}
|
||||
@@ -1283,11 +1319,11 @@ func (c *Compiler) parseCall(frame *Frame, instr *ssa.CallCommon) (llvm.Value, e
|
||||
if fn := instr.StaticCallee(); fn != nil {
|
||||
name := fn.RelString(nil)
|
||||
switch {
|
||||
case name == "device/arm.ReadRegister":
|
||||
return c.emitReadRegister(instr.Args)
|
||||
case name == "device/arm.Asm" || name == "device/avr.Asm":
|
||||
case name == "device/arm.ReadRegister" || name == "device/riscv.ReadRegister":
|
||||
return c.emitReadRegister(name, instr.Args)
|
||||
case name == "device/arm.Asm" || name == "device/avr.Asm" || name == "device/riscv.Asm":
|
||||
return c.emitAsm(instr.Args)
|
||||
case name == "device/arm.AsmFull" || name == "device/avr.AsmFull":
|
||||
case name == "device/arm.AsmFull" || name == "device/avr.AsmFull" || name == "device/riscv.AsmFull":
|
||||
return c.emitAsmFull(frame, instr)
|
||||
case strings.HasPrefix(name, "device/arm.SVCall"):
|
||||
return c.emitSVCall(frame, instr.Args)
|
||||
@@ -1327,10 +1363,7 @@ func (c *Compiler) parseCall(frame *Frame, instr *ssa.CallCommon) (llvm.Value, e
|
||||
value := c.getValue(frame, instr.Value)
|
||||
// This is a func value, which cannot be called directly. We have to
|
||||
// extract the function pointer and context first from the func value.
|
||||
funcPtr, context, err := c.decodeFuncValue(value, instr.Value.Type().Underlying().(*types.Signature))
|
||||
if err != nil {
|
||||
return llvm.Value{}, err
|
||||
}
|
||||
funcPtr, context := c.decodeFuncValue(value, instr.Value.Type().Underlying().(*types.Signature))
|
||||
c.emitNilCheck(frame, funcPtr, "fpcall")
|
||||
return c.parseFunctionCall(frame, instr.Args, funcPtr, context, false), nil
|
||||
}
|
||||
@@ -1350,9 +1383,9 @@ func (c *Compiler) getValue(frame *Frame, expr ssa.Value) llvm.Value {
|
||||
}
|
||||
return c.createFuncValue(fn.LLVMFn, llvm.Undef(c.i8ptrType), fn.Signature)
|
||||
case *ssa.Global:
|
||||
value := c.ir.GetGlobal(expr).LLVMGlobal
|
||||
value := c.getGlobal(expr)
|
||||
if value.IsNil() {
|
||||
c.addError(expr.Pos(), "global not found: "+c.ir.GetGlobal(expr).LinkName())
|
||||
c.addError(expr.Pos(), "global not found: "+expr.RelString(nil))
|
||||
return llvm.Undef(c.getLLVMType(expr.Type()))
|
||||
}
|
||||
return value
|
||||
@@ -1385,7 +1418,6 @@ func (c *Compiler) parseExpr(frame *Frame, expr ssa.Value) (llvm.Value, error) {
|
||||
// Size would be truncated if truncated to uintptr.
|
||||
return llvm.Value{}, c.makeError(expr.Pos(), fmt.Sprintf("value is too big (%v bytes)", size))
|
||||
}
|
||||
// TODO: escape analysis
|
||||
sizeValue := llvm.ConstInt(c.uintptrType, size, false)
|
||||
buf := c.createRuntimeCall("alloc", []llvm.Value{sizeValue}, expr.Comment)
|
||||
buf = c.builder.CreateBitCast(buf, llvm.PointerType(typ, 0), "")
|
||||
@@ -1393,7 +1425,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
|
||||
}
|
||||
@@ -1449,9 +1481,11 @@ func (c *Compiler) parseExpr(frame *Frame, expr ssa.Value) (llvm.Value, error) {
|
||||
x := c.getValue(frame, expr.X)
|
||||
return c.parseConvert(expr.X.Type(), expr.Type(), x, expr.Pos())
|
||||
case *ssa.Extract:
|
||||
if _, ok := expr.Tuple.(*ssa.Select); ok {
|
||||
return c.getChanSelectResult(frame, expr), nil
|
||||
}
|
||||
value := c.getValue(frame, expr.Tuple)
|
||||
result := c.builder.CreateExtractValue(value, expr.Index, "")
|
||||
return result, nil
|
||||
return c.builder.CreateExtractValue(value, expr.Index, ""), nil
|
||||
case *ssa.Field:
|
||||
value := c.getValue(frame, expr.X)
|
||||
if s := expr.X.Type().Underlying().(*types.Struct); s.NumFields() > 2 && s.Field(0).Name() == "C union" {
|
||||
@@ -1627,10 +1661,11 @@ 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.
|
||||
// TODO: escape analysis
|
||||
sliceCapCast, err := c.parseConvert(expr.Cap.Type(), types.Typ[types.Uintptr], sliceCap, expr.Pos())
|
||||
if err != nil {
|
||||
return llvm.Value{}, err
|
||||
@@ -1690,43 +1725,22 @@ func (c *Compiler) parseExpr(frame *Frame, expr ssa.Value) (llvm.Value, error) {
|
||||
var iteratorType llvm.Type
|
||||
switch typ := expr.X.Type().Underlying().(type) {
|
||||
case *types.Basic: // string
|
||||
iteratorType = c.mod.GetTypeByName("runtime.stringIterator")
|
||||
iteratorType = c.getLLVMRuntimeType("stringIterator")
|
||||
case *types.Map:
|
||||
iteratorType = c.mod.GetTypeByName("runtime.hashmapIterator")
|
||||
iteratorType = c.getLLVMRuntimeType("hashmapIterator")
|
||||
default:
|
||||
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:
|
||||
if len(expr.States) == 0 {
|
||||
// Shortcuts for some simple selects.
|
||||
llvmType := c.getLLVMType(expr.Type())
|
||||
if expr.Blocking {
|
||||
// Blocks forever:
|
||||
// select {}
|
||||
c.createRuntimeCall("deadlockStub", nil, "")
|
||||
return llvm.Undef(llvmType), nil
|
||||
} else {
|
||||
// No-op:
|
||||
// select {
|
||||
// default:
|
||||
// }
|
||||
retval := llvm.Undef(llvmType)
|
||||
retval = c.builder.CreateInsertValue(retval, llvm.ConstInt(c.intType, 0xffffffffffffffff, true), 0, "")
|
||||
return retval, nil // {-1, false}
|
||||
}
|
||||
}
|
||||
return llvm.Value{}, c.makeError(expr.Pos(), "unimplemented: "+expr.String())
|
||||
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)
|
||||
@@ -1757,6 +1771,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
|
||||
@@ -1765,25 +1793,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()),
|
||||
@@ -1803,8 +1837,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.
|
||||
@@ -1814,10 +1851,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),
|
||||
@@ -1833,13 +1873,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.
|
||||
@@ -1852,7 +1897,7 @@ func (c *Compiler) parseExpr(frame *Frame, expr ssa.Value) (llvm.Value, error) {
|
||||
|
||||
newPtr := c.builder.CreateInBoundsGEP(oldPtr, []llvm.Value{low}, "")
|
||||
newLen := c.builder.CreateSub(high, low, "")
|
||||
str := llvm.Undef(c.mod.GetTypeByName("runtime._string"))
|
||||
str := llvm.Undef(c.getLLVMRuntimeType("_string"))
|
||||
str = c.builder.CreateInsertValue(str, newPtr, 0, "")
|
||||
str = c.builder.CreateInsertValue(str, newLen, 1, "")
|
||||
return str, nil
|
||||
@@ -2231,7 +2276,7 @@ func (c *Compiler) parseConst(prefix string, expr *ssa.Const) llvm.Value {
|
||||
global.SetUnnamedAddr(true)
|
||||
zero := llvm.ConstInt(c.ctx.Int32Type(), 0, false)
|
||||
strPtr := c.builder.CreateInBoundsGEP(global, []llvm.Value{zero, zero}, "")
|
||||
strObj := llvm.ConstNamedStruct(c.mod.GetTypeByName("runtime._string"), []llvm.Value{strPtr, strLen})
|
||||
strObj := llvm.ConstNamedStruct(c.getLLVMRuntimeType("_string"), []llvm.Value{strPtr, strLen})
|
||||
return strObj
|
||||
} else if typ.Kind() == types.UnsafePointer {
|
||||
if !expr.IsNil() {
|
||||
@@ -2269,12 +2314,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")
|
||||
@@ -2284,7 +2329,7 @@ func (c *Compiler) parseConst(prefix string, expr *ssa.Const) llvm.Value {
|
||||
llvm.ConstInt(c.uintptrType, 0, false),
|
||||
llvm.ConstPointerNull(c.i8ptrType),
|
||||
}
|
||||
return llvm.ConstNamedStruct(c.mod.GetTypeByName("runtime._interface"), fields)
|
||||
return llvm.ConstNamedStruct(c.getLLVMRuntimeType("_interface"), fields)
|
||||
case *types.Pointer:
|
||||
if expr.Value != nil {
|
||||
panic("expected nil pointer constant")
|
||||
@@ -2309,7 +2354,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())
|
||||
}
|
||||
@@ -2381,6 +2426,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())
|
||||
}
|
||||
@@ -2467,8 +2514,10 @@ func (c *Compiler) parseConvert(typeFrom, typeTo types.Type, value llvm.Value, p
|
||||
switch elemType.Kind() {
|
||||
case types.Byte:
|
||||
return c.createRuntimeCall("stringToBytes", []llvm.Value{value}, ""), nil
|
||||
case types.Rune:
|
||||
return c.createRuntimeCall("stringToRunes", []llvm.Value{value}, ""), nil
|
||||
default:
|
||||
return llvm.Value{}, c.makeError(pos, "todo: convert from string: "+elemType.String())
|
||||
panic("unexpected type in string to slice conversion")
|
||||
}
|
||||
|
||||
default:
|
||||
@@ -2494,18 +2543,18 @@ func (c *Compiler) parseUnOp(frame *Frame, unop *ssa.UnOp) (llvm.Value, error) {
|
||||
return llvm.Value{}, c.makeError(unop.Pos(), "todo: unknown type for negate: "+unop.X.Type().Underlying().String())
|
||||
}
|
||||
case token.MUL: // *x, dereference pointer
|
||||
valType := unop.X.Type().Underlying().(*types.Pointer).Elem()
|
||||
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:
|
||||
// var C.add unsafe.Pointer
|
||||
// Instead of a load from the global, create a bitcast of the
|
||||
// function pointer itself.
|
||||
global := c.ir.GetGlobal(unop.X.(*ssa.Global))
|
||||
name := global.LinkName()[:len(global.LinkName())-len("$funcaddr")]
|
||||
globalName := c.getGlobalInfo(unop.X.(*ssa.Global)).linkName
|
||||
name := globalName[:len(globalName)-len("$funcaddr")]
|
||||
fn := c.mod.NamedFunction(name)
|
||||
if fn.IsNil() {
|
||||
return llvm.Value{}, c.makeError(unop.Pos(), "cgo function not found: "+name)
|
||||
@@ -2514,10 +2563,6 @@ func (c *Compiler) parseUnOp(frame *Frame, unop *ssa.UnOp) (llvm.Value, error) {
|
||||
} else {
|
||||
c.emitNilCheck(frame, x, "deref")
|
||||
load := c.builder.CreateLoad(x, "")
|
||||
if c.ir.IsVolatile(valType) {
|
||||
// Volatile load, for memory-mapped registers.
|
||||
load.SetVolatile(true)
|
||||
}
|
||||
return load, nil
|
||||
}
|
||||
case token.XOR: // ^x, toggle all bits in integer
|
||||
@@ -2668,7 +2713,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 {
|
||||
|
||||
+13
-10
@@ -29,7 +29,7 @@ func (c *Compiler) deferInitFunc(frame *Frame) {
|
||||
frame.deferClosureFuncs = make(map[*ir.Function]int)
|
||||
|
||||
// Create defer list pointer.
|
||||
deferType := llvm.PointerType(c.mod.GetTypeByName("runtime._defer"), 0)
|
||||
deferType := llvm.PointerType(c.getLLVMRuntimeType("_defer"), 0)
|
||||
frame.deferPtr = c.builder.CreateAlloca(deferType, "deferPtr")
|
||||
c.builder.CreateStore(llvm.ConstPointerNull(deferType), frame.deferPtr)
|
||||
}
|
||||
@@ -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, "")
|
||||
}
|
||||
@@ -130,6 +130,9 @@ func (c *Compiler) emitDefer(frame *Frame, instr *ssa.Defer) {
|
||||
// Put this struct in an alloca.
|
||||
alloca := c.builder.CreateAlloca(deferFrameType, "defer.alloca")
|
||||
c.builder.CreateStore(deferFrame, alloca)
|
||||
if c.needsStackObjects() {
|
||||
c.trackPointer(alloca)
|
||||
}
|
||||
|
||||
// Push it on top of the linked list by replacing deferPtr.
|
||||
allocaCast := c.builder.CreateBitCast(alloca, next.Type(), "defer.alloca.cast")
|
||||
@@ -154,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:
|
||||
@@ -189,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) {
|
||||
@@ -200,7 +203,7 @@ func (c *Compiler) emitRunDefers(frame *Frame) {
|
||||
}
|
||||
|
||||
// Get the real defer struct type and cast to it.
|
||||
valueTypes := []llvm.Type{c.uintptrType, llvm.PointerType(c.mod.GetTypeByName("runtime._defer"), 0), c.i8ptrType}
|
||||
valueTypes := []llvm.Type{c.uintptrType, llvm.PointerType(c.getLLVMRuntimeType("_defer"), 0), c.i8ptrType}
|
||||
for _, arg := range callback.Args {
|
||||
valueTypes = append(valueTypes, c.getLLVMType(arg.Type()))
|
||||
}
|
||||
@@ -231,7 +234,7 @@ func (c *Compiler) emitRunDefers(frame *Frame) {
|
||||
// Direct call.
|
||||
|
||||
// Get the real defer struct type and cast to it.
|
||||
valueTypes := []llvm.Type{c.uintptrType, llvm.PointerType(c.mod.GetTypeByName("runtime._defer"), 0)}
|
||||
valueTypes := []llvm.Type{c.uintptrType, llvm.PointerType(c.getLLVMRuntimeType("_defer"), 0)}
|
||||
for _, param := range callback.Params {
|
||||
valueTypes = append(valueTypes, c.getLLVMType(param.Type()))
|
||||
}
|
||||
@@ -260,7 +263,7 @@ func (c *Compiler) emitRunDefers(frame *Frame) {
|
||||
case *ssa.MakeClosure:
|
||||
// Get the real defer struct type and cast to it.
|
||||
fn := c.ir.GetFunction(callback.Fn.(*ssa.Function))
|
||||
valueTypes := []llvm.Type{c.uintptrType, llvm.PointerType(c.mod.GetTypeByName("runtime._defer"), 0)}
|
||||
valueTypes := []llvm.Type{c.uintptrType, llvm.PointerType(c.getLLVMRuntimeType("_defer"), 0)}
|
||||
params := fn.Signature.Params()
|
||||
for i := 0; i < params.Len(); i++ {
|
||||
valueTypes = append(valueTypes, c.getLLVMType(params.At(i).Type()))
|
||||
|
||||
+95
-79
@@ -51,7 +51,7 @@ func (c *Compiler) LowerFuncValues() {
|
||||
}
|
||||
|
||||
// Find all func values used in the program with their signatures.
|
||||
funcValueWithSignaturePtr := llvm.PointerType(c.mod.GetTypeByName("runtime.funcValueWithSignature"), 0)
|
||||
funcValueWithSignaturePtr := llvm.PointerType(c.getLLVMRuntimeType("funcValueWithSignature"), 0)
|
||||
signatures := map[string]*funcSignatureInfo{}
|
||||
for global := c.mod.FirstGlobal(); !global.IsNil(); global = llvm.NextGlobal(global) {
|
||||
if global.Type() != funcValueWithSignaturePtr {
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
+12
-23
@@ -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,11 +53,11 @@ 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() {
|
||||
funcValueWithSignatureType := c.mod.GetTypeByName("runtime.funcValueWithSignature")
|
||||
funcValueWithSignatureType := c.getLLVMRuntimeType("funcValueWithSignature")
|
||||
funcValueWithSignature := llvm.ConstNamedStruct(funcValueWithSignatureType, []llvm.Value{
|
||||
llvm.ConstPtrToInt(funcPtr, c.uintptrType),
|
||||
sigGlobal,
|
||||
@@ -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 {
|
||||
@@ -103,14 +92,14 @@ func (c *Compiler) extractFuncContext(funcValue llvm.Value) llvm.Value {
|
||||
|
||||
// decodeFuncValue extracts the context and the function pointer from this func
|
||||
// value. This may be an expensive operation.
|
||||
func (c *Compiler) decodeFuncValue(funcValue llvm.Value, sig *types.Signature) (funcPtr, context llvm.Value, err error) {
|
||||
func (c *Compiler) decodeFuncValue(funcValue llvm.Value, sig *types.Signature) (funcPtr, context llvm.Value) {
|
||||
context = c.builder.CreateExtractValue(funcValue, 0, "")
|
||||
switch c.funcImplementation() {
|
||||
case funcValueDoubleword:
|
||||
funcPtr = c.builder.CreateExtractValue(funcValue, 1, "")
|
||||
case funcValueSwitch:
|
||||
llvmSig := c.getRawFuncType(sig)
|
||||
sigGlobal := c.getFuncSignature(sig)
|
||||
sigGlobal := c.getTypeCode(sig)
|
||||
funcPtr = c.createRuntimeCall("getFuncPtr", []llvm.Value{funcValue, sigGlobal}, "")
|
||||
funcPtr = c.builder.CreateIntToPtr(funcPtr, llvmSig, "")
|
||||
default:
|
||||
@@ -126,7 +115,7 @@ func (c *Compiler) getFuncType(typ *types.Signature) llvm.Type {
|
||||
rawPtr := c.getRawFuncType(typ)
|
||||
return c.ctx.StructType([]llvm.Type{c.i8ptrType, rawPtr}, false)
|
||||
case funcValueSwitch:
|
||||
return c.mod.GetTypeByName("runtime.funcValue")
|
||||
return c.getLLVMRuntimeType("funcValue")
|
||||
default:
|
||||
panic("unimplemented func value variant")
|
||||
}
|
||||
|
||||
+471
@@ -0,0 +1,471 @@
|
||||
package compiler
|
||||
|
||||
// This file provides IR transformations necessary for precise and portable
|
||||
// garbage collectors.
|
||||
|
||||
import (
|
||||
"go/token"
|
||||
"math/big"
|
||||
|
||||
"golang.org/x/tools/go/ssa"
|
||||
"tinygo.org/x/go-llvm"
|
||||
)
|
||||
|
||||
// needsStackObjects returns true if the compiler should insert stack objects
|
||||
// that can be traced by the garbage collector.
|
||||
func (c *Compiler) needsStackObjects() bool {
|
||||
if c.selectGC() != "conservative" {
|
||||
return false
|
||||
}
|
||||
for _, tag := range c.BuildTags {
|
||||
if tag == "baremetal" {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
// trackExpr inserts pointer tracking intrinsics for the GC if the expression is
|
||||
// one of the expressions that need this.
|
||||
func (c *Compiler) trackExpr(frame *Frame, expr ssa.Value, value llvm.Value) {
|
||||
// There are uses of this expression, Make sure the pointers
|
||||
// are tracked during GC.
|
||||
switch expr := expr.(type) {
|
||||
case *ssa.Alloc, *ssa.MakeChan, *ssa.MakeMap:
|
||||
// These values are always of pointer type in IR.
|
||||
c.trackPointer(value)
|
||||
case *ssa.Call, *ssa.Convert, *ssa.MakeClosure, *ssa.MakeInterface, *ssa.MakeSlice, *ssa.Next:
|
||||
if !value.IsNil() {
|
||||
c.trackValue(value)
|
||||
}
|
||||
case *ssa.Select:
|
||||
if alloca, ok := frame.selectRecvBuf[expr]; ok {
|
||||
if alloca.IsAUndefValue().IsNil() {
|
||||
c.trackPointer(alloca)
|
||||
}
|
||||
}
|
||||
case *ssa.UnOp:
|
||||
switch expr.Op {
|
||||
case token.MUL:
|
||||
// Pointer dereference.
|
||||
c.trackValue(value)
|
||||
case token.ARROW:
|
||||
// Channel receive operator.
|
||||
// It's not necessary to look at commaOk here, because in that
|
||||
// case it's just an aggregate and trackValue will extract the
|
||||
// pointer in there (if there is one).
|
||||
c.trackValue(value)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// trackValue locates pointers in a value (possibly an aggregate) and tracks the
|
||||
// individual pointers
|
||||
func (c *Compiler) trackValue(value llvm.Value) {
|
||||
typ := value.Type()
|
||||
switch typ.TypeKind() {
|
||||
case llvm.PointerTypeKind:
|
||||
c.trackPointer(value)
|
||||
case llvm.StructTypeKind:
|
||||
if !typeHasPointers(typ) {
|
||||
return
|
||||
}
|
||||
numElements := typ.StructElementTypesCount()
|
||||
for i := 0; i < numElements; i++ {
|
||||
subValue := c.builder.CreateExtractValue(value, i, "")
|
||||
c.trackValue(subValue)
|
||||
}
|
||||
case llvm.ArrayTypeKind:
|
||||
if !typeHasPointers(typ) {
|
||||
return
|
||||
}
|
||||
numElements := typ.ArrayLength()
|
||||
for i := 0; i < numElements; i++ {
|
||||
subValue := c.builder.CreateExtractValue(value, i, "")
|
||||
c.trackValue(subValue)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// trackPointer creates a call to runtime.trackPointer, bitcasting the poitner
|
||||
// first if needed. The input value must be of LLVM pointer type.
|
||||
func (c *Compiler) trackPointer(value llvm.Value) {
|
||||
if value.Type() != c.i8ptrType {
|
||||
value = c.builder.CreateBitCast(value, c.i8ptrType, "")
|
||||
}
|
||||
c.createRuntimeCall("trackPointer", []llvm.Value{value}, "")
|
||||
}
|
||||
|
||||
// typeHasPointers returns whether this type is a pointer or contains pointers.
|
||||
// If the type is an aggregate type, it will check whether there is a pointer
|
||||
// inside.
|
||||
func typeHasPointers(t llvm.Type) bool {
|
||||
switch t.TypeKind() {
|
||||
case llvm.PointerTypeKind:
|
||||
return true
|
||||
case llvm.StructTypeKind:
|
||||
for _, subType := range t.StructElementTypes() {
|
||||
if typeHasPointers(subType) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
case llvm.ArrayTypeKind:
|
||||
if typeHasPointers(t.ElementType()) {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// makeGCStackSlots converts all calls to runtime.trackPointer to explicit
|
||||
// stores to stack slots that are scannable by the GC.
|
||||
func (c *Compiler) makeGCStackSlots() bool {
|
||||
// Check whether there are allocations at all.
|
||||
alloc := c.mod.NamedFunction("runtime.alloc")
|
||||
if alloc.IsNil() {
|
||||
// Nothing to. Make sure all remaining bits and pieces for stack
|
||||
// chains are neutralized.
|
||||
for _, call := range getUses(c.mod.NamedFunction("runtime.trackPointer")) {
|
||||
call.EraseFromParentAsInstruction()
|
||||
}
|
||||
stackChainStart := c.mod.NamedGlobal("runtime.stackChainStart")
|
||||
if !stackChainStart.IsNil() {
|
||||
stackChainStart.SetInitializer(llvm.ConstNull(stackChainStart.Type().ElementType()))
|
||||
stackChainStart.SetGlobalConstant(true)
|
||||
}
|
||||
}
|
||||
|
||||
trackPointer := c.mod.NamedFunction("runtime.trackPointer")
|
||||
if trackPointer.IsNil() || trackPointer.FirstUse().IsNil() {
|
||||
return false // nothing to do
|
||||
}
|
||||
|
||||
// Look at *all* functions to see whether they are free of function pointer
|
||||
// calls.
|
||||
// This takes less than 5ms for ~100kB of WebAssembly but would perhaps be
|
||||
// faster when written in C++ (to avoid the CGo overhead).
|
||||
funcsWithFPCall := map[llvm.Value]struct{}{}
|
||||
n := 0
|
||||
for fn := c.mod.FirstFunction(); !fn.IsNil(); fn = llvm.NextFunction(fn) {
|
||||
n++
|
||||
if _, ok := funcsWithFPCall[fn]; ok {
|
||||
continue // already found
|
||||
}
|
||||
done := false
|
||||
for bb := fn.FirstBasicBlock(); !bb.IsNil() && !done; bb = llvm.NextBasicBlock(bb) {
|
||||
for call := bb.FirstInstruction(); !call.IsNil() && !done; call = llvm.NextInstruction(call) {
|
||||
if call.IsACallInst().IsNil() {
|
||||
continue // only looking at calls
|
||||
}
|
||||
called := call.CalledValue()
|
||||
if !called.IsAFunction().IsNil() {
|
||||
continue // only looking for function pointers
|
||||
}
|
||||
funcsWithFPCall[fn] = struct{}{}
|
||||
markParentFunctions(funcsWithFPCall, fn)
|
||||
done = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Determine which functions need stack objects. Many leaf functions don't
|
||||
// need it: it only causes overhead for them.
|
||||
// Actually, in one test it was only able to eliminate stack object from 12%
|
||||
// of functions that had a call to runtime.trackPointer (8 out of 68
|
||||
// functions), so this optimization is not as big as it may seem.
|
||||
allocatingFunctions := map[llvm.Value]struct{}{} // set of allocating functions
|
||||
|
||||
// Work from runtime.alloc and trace all parents to check which functions do
|
||||
// a heap allocation (and thus which functions do not).
|
||||
markParentFunctions(allocatingFunctions, alloc)
|
||||
|
||||
// Also trace all functions that call a function pointer.
|
||||
for fn := range funcsWithFPCall {
|
||||
// Assume that functions that call a function pointer do a heap
|
||||
// allocation as a conservative guess because the called function might
|
||||
// do a heap allocation.
|
||||
allocatingFunctions[fn] = struct{}{}
|
||||
markParentFunctions(allocatingFunctions, fn)
|
||||
}
|
||||
|
||||
// Collect some variables used below in the loop.
|
||||
stackChainStart := c.mod.NamedGlobal("runtime.stackChainStart")
|
||||
if stackChainStart.IsNil() {
|
||||
panic("stack chain start not found!")
|
||||
}
|
||||
stackChainStartType := stackChainStart.Type().ElementType()
|
||||
stackChainStart.SetInitializer(llvm.ConstNull(stackChainStartType))
|
||||
|
||||
// Iterate until runtime.trackPointer has no uses left.
|
||||
for use := trackPointer.FirstUse(); !use.IsNil(); use = trackPointer.FirstUse() {
|
||||
// Pick the first use of runtime.trackPointer.
|
||||
call := use.User()
|
||||
if call.IsACallInst().IsNil() {
|
||||
panic("expected runtime.trackPointer use to be a call")
|
||||
}
|
||||
|
||||
// Pick the parent function.
|
||||
fn := call.InstructionParent().Parent()
|
||||
|
||||
if _, ok := allocatingFunctions[fn]; !ok {
|
||||
// This function nor any of the functions it calls (recursively)
|
||||
// allocate anything from the heap, so it will not trigger a garbage
|
||||
// collection cycle. Thus, it does not need to track local pointer
|
||||
// values.
|
||||
// This is a useful optimization but not as big as you might guess,
|
||||
// as described above (it avoids stack objects for ~12% of
|
||||
// functions).
|
||||
call.EraseFromParentAsInstruction()
|
||||
continue
|
||||
}
|
||||
|
||||
// Find all calls to runtime.trackPointer in this function.
|
||||
var calls []llvm.Value
|
||||
var returns []llvm.Value
|
||||
for bb := fn.FirstBasicBlock(); !bb.IsNil(); bb = llvm.NextBasicBlock(bb) {
|
||||
for inst := bb.FirstInstruction(); !inst.IsNil(); inst = llvm.NextInstruction(inst) {
|
||||
switch inst.InstructionOpcode() {
|
||||
case llvm.Call:
|
||||
if inst.CalledValue() == trackPointer {
|
||||
calls = append(calls, inst)
|
||||
}
|
||||
case llvm.Ret:
|
||||
returns = append(returns, inst)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Determine what to do with each call.
|
||||
var allocas, pointers []llvm.Value
|
||||
for _, call := range calls {
|
||||
ptr := call.Operand(0)
|
||||
call.EraseFromParentAsInstruction()
|
||||
if ptr.IsAInstruction().IsNil() {
|
||||
continue
|
||||
}
|
||||
|
||||
// Some trivial optimizations.
|
||||
if ptr.IsAInstruction().IsNil() {
|
||||
continue
|
||||
}
|
||||
switch ptr.InstructionOpcode() {
|
||||
case llvm.PHI, llvm.GetElementPtr:
|
||||
// These values do not create new values: the values already
|
||||
// existed locally in this function so must have been tracked
|
||||
// already.
|
||||
continue
|
||||
case llvm.ExtractValue, llvm.BitCast:
|
||||
// These instructions do not create new values, but their
|
||||
// original value may not be tracked. So keep tracking them for
|
||||
// now.
|
||||
// With more analysis, it should be possible to optimize a
|
||||
// significant chunk of these away.
|
||||
case llvm.Call, llvm.Load, llvm.IntToPtr:
|
||||
// These create new values so must be stored locally. But
|
||||
// perhaps some of these can be fused when they actually refer
|
||||
// to the same value.
|
||||
default:
|
||||
// Ambiguous. These instructions are uncommon, but perhaps could
|
||||
// be optimized if needed.
|
||||
}
|
||||
|
||||
if !ptr.IsAAllocaInst().IsNil() {
|
||||
if typeHasPointers(ptr.Type().ElementType()) {
|
||||
allocas = append(allocas, ptr)
|
||||
}
|
||||
} else {
|
||||
pointers = append(pointers, ptr)
|
||||
}
|
||||
}
|
||||
|
||||
if len(allocas) == 0 && len(pointers) == 0 {
|
||||
// This function does not need to keep track of stack pointers.
|
||||
continue
|
||||
}
|
||||
|
||||
// Determine the type of the required stack slot.
|
||||
fields := []llvm.Type{
|
||||
stackChainStartType, // Pointer to parent frame.
|
||||
c.uintptrType, // Number of elements in this frame.
|
||||
}
|
||||
for _, alloca := range allocas {
|
||||
fields = append(fields, alloca.Type().ElementType())
|
||||
}
|
||||
for _, ptr := range pointers {
|
||||
fields = append(fields, ptr.Type())
|
||||
}
|
||||
stackObjectType := c.ctx.StructType(fields, false)
|
||||
|
||||
// Create the stack object at the function entry.
|
||||
c.builder.SetInsertPointBefore(fn.EntryBasicBlock().FirstInstruction())
|
||||
stackObject := c.builder.CreateAlloca(stackObjectType, "gc.stackobject")
|
||||
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})
|
||||
c.builder.CreateStore(initialStackObject, stackObject)
|
||||
|
||||
// Update stack start.
|
||||
parent := c.builder.CreateLoad(stackChainStart, "")
|
||||
gep := c.builder.CreateGEP(stackObject, []llvm.Value{
|
||||
llvm.ConstInt(c.ctx.Int32Type(), 0, false),
|
||||
llvm.ConstInt(c.ctx.Int32Type(), 0, false),
|
||||
}, "")
|
||||
c.builder.CreateStore(parent, gep)
|
||||
stackObjectCast := c.builder.CreateBitCast(stackObject, stackChainStartType, "")
|
||||
c.builder.CreateStore(stackObjectCast, stackChainStart)
|
||||
|
||||
// Replace all independent allocas with GEPs in the stack object.
|
||||
for i, alloca := range allocas {
|
||||
gep := c.builder.CreateGEP(stackObject, []llvm.Value{
|
||||
llvm.ConstInt(c.ctx.Int32Type(), 0, false),
|
||||
llvm.ConstInt(c.ctx.Int32Type(), uint64(2+i), false),
|
||||
}, "")
|
||||
alloca.ReplaceAllUsesWith(gep)
|
||||
alloca.EraseFromParentAsInstruction()
|
||||
}
|
||||
|
||||
// Do a store to the stack object after each new pointer that is created.
|
||||
for i, ptr := range pointers {
|
||||
c.builder.SetInsertPointBefore(llvm.NextInstruction(ptr))
|
||||
gep := c.builder.CreateGEP(stackObject, []llvm.Value{
|
||||
llvm.ConstInt(c.ctx.Int32Type(), 0, false),
|
||||
llvm.ConstInt(c.ctx.Int32Type(), uint64(2+len(allocas)+i), false),
|
||||
}, "")
|
||||
c.builder.CreateStore(ptr, gep)
|
||||
}
|
||||
|
||||
// Make sure this stack object is popped from the linked list of stack
|
||||
// objects at return.
|
||||
for _, ret := range returns {
|
||||
c.builder.SetInsertPointBefore(ret)
|
||||
c.builder.CreateStore(parent, stackChainStart)
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
func (c *Compiler) addGlobalsBitmap() bool {
|
||||
if c.mod.NamedGlobal("runtime.trackedGlobalsStart").IsNil() {
|
||||
return false // nothing to do: no GC in use
|
||||
}
|
||||
|
||||
var trackedGlobals []llvm.Value
|
||||
var trackedGlobalTypes []llvm.Type
|
||||
for global := c.mod.FirstGlobal(); !global.IsNil(); global = llvm.NextGlobal(global) {
|
||||
if global.IsDeclaration() {
|
||||
continue
|
||||
}
|
||||
typ := global.Type().ElementType()
|
||||
ptrs := c.getPointerBitmap(typ, global.Name())
|
||||
if ptrs.BitLen() == 0 {
|
||||
continue
|
||||
}
|
||||
trackedGlobals = append(trackedGlobals, global)
|
||||
trackedGlobalTypes = append(trackedGlobalTypes, typ)
|
||||
}
|
||||
|
||||
globalsBundleType := c.ctx.StructType(trackedGlobalTypes, false)
|
||||
globalsBundle := llvm.AddGlobal(c.mod, globalsBundleType, "tinygo.trackedGlobals")
|
||||
globalsBundle.SetLinkage(llvm.InternalLinkage)
|
||||
globalsBundle.SetUnnamedAddr(true)
|
||||
initializer := llvm.Undef(globalsBundleType)
|
||||
for i, global := range trackedGlobals {
|
||||
initializer = llvm.ConstInsertValue(initializer, global.Initializer(), []uint32{uint32(i)})
|
||||
gep := llvm.ConstGEP(globalsBundle, []llvm.Value{
|
||||
llvm.ConstInt(c.ctx.Int32Type(), 0, false),
|
||||
llvm.ConstInt(c.ctx.Int32Type(), uint64(i), false),
|
||||
})
|
||||
global.ReplaceAllUsesWith(gep)
|
||||
global.EraseFromParentAsGlobal()
|
||||
}
|
||||
globalsBundle.SetInitializer(initializer)
|
||||
|
||||
trackedGlobalsStart := llvm.ConstPtrToInt(globalsBundle, c.uintptrType)
|
||||
c.mod.NamedGlobal("runtime.trackedGlobalsStart").SetInitializer(trackedGlobalsStart)
|
||||
|
||||
alignment := c.targetData.PrefTypeAlignment(c.i8ptrType)
|
||||
trackedGlobalsLength := llvm.ConstInt(c.uintptrType, c.targetData.TypeAllocSize(globalsBundleType)/uint64(alignment), false)
|
||||
c.mod.NamedGlobal("runtime.trackedGlobalsLength").SetInitializer(trackedGlobalsLength)
|
||||
|
||||
bitmapBytes := c.getPointerBitmap(globalsBundleType, "globals bundle").Bytes()
|
||||
bitmapValues := make([]llvm.Value, len(bitmapBytes))
|
||||
for i, b := range bitmapBytes {
|
||||
bitmapValues[len(bitmapBytes)-i-1] = llvm.ConstInt(c.ctx.Int8Type(), uint64(b), false)
|
||||
}
|
||||
bitmapArray := llvm.ConstArray(c.ctx.Int8Type(), bitmapValues)
|
||||
bitmapNew := llvm.AddGlobal(c.mod, bitmapArray.Type(), "runtime.trackedGlobalsBitmap.tmp")
|
||||
bitmapOld := c.mod.NamedGlobal("runtime.trackedGlobalsBitmap")
|
||||
bitmapOld.ReplaceAllUsesWith(llvm.ConstBitCast(bitmapNew, bitmapOld.Type()))
|
||||
bitmapNew.SetInitializer(bitmapArray)
|
||||
bitmapNew.SetName("runtime.trackedGlobalsBitmap")
|
||||
|
||||
return true // the IR was changed
|
||||
}
|
||||
|
||||
func (c *Compiler) getPointerBitmap(typ llvm.Type, name string) *big.Int {
|
||||
alignment := c.targetData.PrefTypeAlignment(c.i8ptrType)
|
||||
switch typ.TypeKind() {
|
||||
case llvm.IntegerTypeKind, llvm.FloatTypeKind, llvm.DoubleTypeKind:
|
||||
return big.NewInt(0)
|
||||
case llvm.PointerTypeKind:
|
||||
return big.NewInt(1)
|
||||
case llvm.StructTypeKind:
|
||||
ptrs := big.NewInt(0)
|
||||
for i, subtyp := range typ.StructElementTypes() {
|
||||
subptrs := c.getPointerBitmap(subtyp, name)
|
||||
if subptrs.BitLen() == 0 {
|
||||
continue
|
||||
}
|
||||
offset := c.targetData.ElementOffset(typ, i)
|
||||
if offset%uint64(alignment) != 0 {
|
||||
panic("precise GC: global contains unaligned pointer: " + name)
|
||||
}
|
||||
subptrs.Lsh(subptrs, uint(offset)/uint(alignment))
|
||||
ptrs.Or(ptrs, subptrs)
|
||||
}
|
||||
return ptrs
|
||||
case llvm.ArrayTypeKind:
|
||||
subtyp := typ.ElementType()
|
||||
subptrs := c.getPointerBitmap(subtyp, name)
|
||||
ptrs := big.NewInt(0)
|
||||
if subptrs.BitLen() == 0 {
|
||||
return ptrs
|
||||
}
|
||||
elementSize := c.targetData.TypeAllocSize(subtyp)
|
||||
for i := 0; i < typ.ArrayLength(); i++ {
|
||||
ptrs.Lsh(ptrs, uint(elementSize)/uint(alignment))
|
||||
ptrs.Or(ptrs, subptrs)
|
||||
}
|
||||
return ptrs
|
||||
default:
|
||||
panic("unknown type kind of global: " + name)
|
||||
}
|
||||
}
|
||||
|
||||
// markParentFunctions traverses all parent function calls (recursively) and
|
||||
// adds them to the set of marked functions. It only considers function calls:
|
||||
// any other uses of such a function is ignored.
|
||||
func markParentFunctions(marked map[llvm.Value]struct{}, fn llvm.Value) {
|
||||
worklist := []llvm.Value{fn}
|
||||
for len(worklist) != 0 {
|
||||
fn := worklist[len(worklist)-1]
|
||||
worklist = worklist[:len(worklist)-1]
|
||||
for _, use := range getUses(fn) {
|
||||
if use.IsACallInst().IsNil() || use.CalledValue() != fn {
|
||||
// Not the parent function.
|
||||
continue
|
||||
}
|
||||
parent := use.InstructionParent().Parent()
|
||||
if _, ok := marked[parent]; !ok {
|
||||
marked[parent] = struct{}{}
|
||||
worklist = append(worklist, parent)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+113
-38
@@ -1,5 +1,16 @@
|
||||
package compiler
|
||||
|
||||
// This file implements lowering for the goroutine scheduler. There are two
|
||||
// scheduler implementations, one based on tasks (like RTOSes and the main Go
|
||||
// runtime) and one based on a coroutine compiler transformation. The task based
|
||||
// implementation requires very little work from the compiler but is not very
|
||||
// portable (in particular, it is very hard if not impossible to support on
|
||||
// WebAssembly). The coroutine based one requires a lot of work by the compiler
|
||||
// to implement, but can run virtually anywhere with a single scheduler
|
||||
// implementation.
|
||||
//
|
||||
// The below description is for the coroutine based scheduler.
|
||||
//
|
||||
// This file lowers goroutine pseudo-functions into coroutines scheduled by a
|
||||
// scheduler at runtime. It uses coroutine support in LLVM for this
|
||||
// transformation: https://llvm.org/docs/Coroutines.html
|
||||
@@ -62,7 +73,7 @@ package compiler
|
||||
// llvm.suspend(hdl) // suspend point
|
||||
// println("some other operation")
|
||||
// var i *int // allocate space on the stack for the return value
|
||||
// runtime.setTaskPromisePtr(hdl, &i) // store return value alloca in our coroutine promise
|
||||
// runtime.setTaskStatePtr(hdl, &i) // store return value alloca in our coroutine promise
|
||||
// bar(hdl) // await, pass a continuation (hdl) to bar
|
||||
// llvm.suspend(hdl) // suspend point, wait for the callee to re-activate
|
||||
// println("done", *i)
|
||||
@@ -106,10 +117,69 @@ type asyncFunc struct {
|
||||
unreachableBlock llvm.BasicBlock
|
||||
}
|
||||
|
||||
// LowerGoroutines is a pass called during optimization that transforms the IR
|
||||
// into one where all blocking functions are turned into goroutines and blocking
|
||||
// calls into await calls.
|
||||
// LowerGoroutines performs some IR transformations necessary to support
|
||||
// goroutines. It does something different based on whether it uses the
|
||||
// coroutine or the tasks implementation of goroutines, and whether goroutines
|
||||
// are necessary at all.
|
||||
func (c *Compiler) LowerGoroutines() error {
|
||||
switch c.selectScheduler() {
|
||||
case "coroutines":
|
||||
return c.lowerCoroutines()
|
||||
case "tasks":
|
||||
return c.lowerTasks()
|
||||
default:
|
||||
panic("unknown scheduler type")
|
||||
}
|
||||
}
|
||||
|
||||
// lowerTasks starts the main goroutine and then runs the scheduler.
|
||||
// This is enough compiler-level transformation for the task-based scheduler.
|
||||
func (c *Compiler) lowerTasks() error {
|
||||
uses := getUses(c.mod.NamedFunction("runtime.callMain"))
|
||||
if len(uses) != 1 || uses[0].IsACallInst().IsNil() {
|
||||
panic("expected exactly 1 call of runtime.callMain, check the entry point")
|
||||
}
|
||||
mainCall := uses[0]
|
||||
|
||||
realMain := c.mod.NamedFunction(c.ir.MainPkg().Pkg.Path() + ".main")
|
||||
if len(getUses(c.mod.NamedFunction("runtime.startGoroutine"))) != 0 {
|
||||
// Program needs a scheduler. Start main.main as a goroutine and start
|
||||
// the scheduler.
|
||||
realMainWrapper := c.createGoroutineStartWrapper(realMain)
|
||||
c.builder.SetInsertPointBefore(mainCall)
|
||||
zero := llvm.ConstInt(c.uintptrType, 0, false)
|
||||
c.createRuntimeCall("startGoroutine", []llvm.Value{realMainWrapper, zero}, "")
|
||||
c.createRuntimeCall("scheduler", nil, "")
|
||||
sleep := c.mod.NamedFunction("time.Sleep")
|
||||
if !sleep.IsNil() {
|
||||
sleep.ReplaceAllUsesWith(c.mod.NamedFunction("runtime.sleepCurrentTask"))
|
||||
}
|
||||
} else {
|
||||
// Program doesn't need a scheduler. Call main.main directly.
|
||||
c.builder.SetInsertPointBefore(mainCall)
|
||||
params := []llvm.Value{
|
||||
llvm.Undef(c.i8ptrType), // unused context parameter
|
||||
llvm.Undef(c.i8ptrType), // unused coroutine handle
|
||||
}
|
||||
c.createCall(realMain, params, "")
|
||||
// runtime.Goexit isn't needed so let it be optimized away by
|
||||
// globalopt.
|
||||
c.mod.NamedFunction("runtime.Goexit").SetLinkage(llvm.InternalLinkage)
|
||||
}
|
||||
mainCall.EraseFromParentAsInstruction()
|
||||
|
||||
// main.main was set to external linkage during IR construction. Set it to
|
||||
// internal linkage to enable interprocedural optimizations.
|
||||
realMain.SetLinkage(llvm.InternalLinkage)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// lowerCoroutines transforms the IR into one where all blocking functions are
|
||||
// turned into goroutines and blocking calls into await calls. It also makes
|
||||
// sure that the first coroutine is started and the coroutine scheduler will be
|
||||
// run.
|
||||
func (c *Compiler) lowerCoroutines() error {
|
||||
needsScheduler, err := c.markAsyncFunctions()
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -144,12 +214,6 @@ func (c *Compiler) LowerGoroutines() error {
|
||||
// main.main was set to external linkage during IR construction. Set it to
|
||||
// internal linkage to enable interprocedural optimizations.
|
||||
realMain.SetLinkage(llvm.InternalLinkage)
|
||||
c.mod.NamedFunction("runtime.alloc").SetLinkage(llvm.InternalLinkage)
|
||||
c.mod.NamedFunction("runtime.free").SetLinkage(llvm.InternalLinkage)
|
||||
c.mod.NamedFunction("runtime.sleepTask").SetLinkage(llvm.InternalLinkage)
|
||||
c.mod.NamedFunction("runtime.setTaskPromisePtr").SetLinkage(llvm.InternalLinkage)
|
||||
c.mod.NamedFunction("runtime.getTaskPromisePtr").SetLinkage(llvm.InternalLinkage)
|
||||
c.mod.NamedFunction("runtime.scheduler").SetLinkage(llvm.InternalLinkage)
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -173,9 +237,9 @@ func (c *Compiler) markAsyncFunctions() (needsScheduler bool, err error) {
|
||||
if !sleep.IsNil() {
|
||||
worklist = append(worklist, sleep)
|
||||
}
|
||||
deadlockStub := c.mod.NamedFunction("runtime.deadlockStub")
|
||||
if !deadlockStub.IsNil() {
|
||||
worklist = append(worklist, deadlockStub)
|
||||
deadlock := c.mod.NamedFunction("runtime.deadlock")
|
||||
if !deadlock.IsNil() {
|
||||
worklist = append(worklist, deadlock)
|
||||
}
|
||||
chanSend := c.mod.NamedFunction("runtime.chanSend")
|
||||
if !chanSend.IsNil() {
|
||||
@@ -211,17 +275,25 @@ func (c *Compiler) markAsyncFunctions() (needsScheduler bool, err error) {
|
||||
|
||||
// Add all callees to the worklist.
|
||||
for _, use := range getUses(f) {
|
||||
if use.IsConstant() && use.Opcode() == llvm.BitCast {
|
||||
bitcastUses := getUses(use)
|
||||
for _, call := range bitcastUses {
|
||||
if use.IsConstant() && use.Opcode() == llvm.PtrToInt {
|
||||
for _, call := range getUses(use) {
|
||||
if call.IsACallInst().IsNil() || call.CalledValue().Name() != "runtime.makeGoroutine" {
|
||||
return false, errors.New("async function " + f.Name() + " incorrectly used in bitcast, expected runtime.makeGoroutine")
|
||||
return false, errors.New("async function " + f.Name() + " incorrectly used in ptrtoint, expected runtime.makeGoroutine")
|
||||
}
|
||||
}
|
||||
// This is a go statement. Do not mark the parent as async, as
|
||||
// starting a goroutine is not a blocking operation.
|
||||
continue
|
||||
}
|
||||
if use.IsConstant() && use.Opcode() == llvm.BitCast {
|
||||
// Not sure why this const bitcast is here but as long as it
|
||||
// has no uses it can be ignored, I guess?
|
||||
// I think it was created for the runtime.isnil check but
|
||||
// somehow wasn't removed when all these checks are removed.
|
||||
if len(getUses(use)) == 0 {
|
||||
continue
|
||||
}
|
||||
}
|
||||
if use.IsACallInst().IsNil() {
|
||||
// Not a call instruction. Maybe a store to a global? In any
|
||||
// case, this requires support for async calls across function
|
||||
@@ -250,12 +322,12 @@ func (c *Compiler) markAsyncFunctions() (needsScheduler bool, err error) {
|
||||
// goroutine is not async (does not do any blocking operation), no
|
||||
// scheduler is necessary as it can be called directly.
|
||||
for _, use := range getUses(makeGoroutine) {
|
||||
// Input param must be const bitcast of function.
|
||||
bitcast := use.Operand(0)
|
||||
if !bitcast.IsConstant() || bitcast.Opcode() != llvm.BitCast {
|
||||
panic("expected const bitcast operand of runtime.makeGoroutine")
|
||||
// Input param must be const ptrtoint of function.
|
||||
ptrtoint := use.Operand(0)
|
||||
if !ptrtoint.IsConstant() || ptrtoint.Opcode() != llvm.PtrToInt {
|
||||
panic("expected const ptrtoint operand of runtime.makeGoroutine")
|
||||
}
|
||||
goroutine := bitcast.Operand(0)
|
||||
goroutine := ptrtoint.Operand(0)
|
||||
if _, ok := asyncFuncs[goroutine]; ok {
|
||||
needsScheduler = true
|
||||
break
|
||||
@@ -292,7 +364,7 @@ func (c *Compiler) markAsyncFunctions() (needsScheduler bool, err error) {
|
||||
|
||||
// Transform all async functions into coroutines.
|
||||
for _, f := range asyncList {
|
||||
if f == sleep || f == deadlockStub || f == chanSend || f == chanRecv {
|
||||
if f == sleep || f == deadlock || f == chanSend || f == chanRecv {
|
||||
continue
|
||||
}
|
||||
|
||||
@@ -309,7 +381,7 @@ func (c *Compiler) markAsyncFunctions() (needsScheduler bool, err error) {
|
||||
for inst := bb.FirstInstruction(); !inst.IsNil(); inst = llvm.NextInstruction(inst) {
|
||||
if !inst.IsACallInst().IsNil() {
|
||||
callee := inst.CalledValue()
|
||||
if _, ok := asyncFuncs[callee]; !ok || callee == sleep || callee == deadlockStub || callee == chanSend || callee == chanRecv {
|
||||
if _, ok := asyncFuncs[callee]; !ok || callee == sleep || callee == deadlock || callee == chanSend || callee == chanRecv {
|
||||
continue
|
||||
}
|
||||
asyncCalls = append(asyncCalls, inst)
|
||||
@@ -321,7 +393,7 @@ func (c *Compiler) markAsyncFunctions() (needsScheduler bool, err error) {
|
||||
|
||||
// Coroutine setup.
|
||||
c.builder.SetInsertPointBefore(f.EntryBasicBlock().FirstInstruction())
|
||||
taskState := c.builder.CreateAlloca(c.mod.GetTypeByName("runtime.taskState"), "task.state")
|
||||
taskState := c.builder.CreateAlloca(c.getLLVMRuntimeType("taskState"), "task.state")
|
||||
stateI8 := c.builder.CreateBitCast(taskState, c.i8ptrType, "task.state.i8")
|
||||
id := c.builder.CreateCall(coroIdFunc, []llvm.Value{
|
||||
llvm.ConstInt(c.ctx.Int32Type(), 0, false),
|
||||
@@ -336,6 +408,9 @@ func (c *Compiler) markAsyncFunctions() (needsScheduler bool, err error) {
|
||||
size = c.builder.CreateZExt(size, c.uintptrType, "task.size.uintptr")
|
||||
}
|
||||
data := c.createRuntimeCall("alloc", []llvm.Value{size}, "task.data")
|
||||
if c.needsStackObjects() {
|
||||
c.trackPointer(data)
|
||||
}
|
||||
frame.taskHandle = c.builder.CreateCall(coroBeginFunc, []llvm.Value{id, data}, "task.handle")
|
||||
|
||||
// Modify async calls so this function suspends right after the child
|
||||
@@ -354,7 +429,7 @@ func (c *Compiler) markAsyncFunctions() (needsScheduler bool, err error) {
|
||||
retvalAlloca = c.builder.CreateAlloca(inst.Type(), "coro.retvalAlloca")
|
||||
c.builder.SetInsertPointBefore(inst)
|
||||
data := c.builder.CreateBitCast(retvalAlloca, c.i8ptrType, "")
|
||||
c.createRuntimeCall("setTaskPromisePtr", []llvm.Value{frame.taskHandle, data}, "")
|
||||
c.createRuntimeCall("setTaskStatePtr", []llvm.Value{frame.taskHandle, data}, "")
|
||||
}
|
||||
|
||||
// Suspend.
|
||||
@@ -392,7 +467,7 @@ func (c *Compiler) markAsyncFunctions() (needsScheduler bool, err error) {
|
||||
var parentHandle llvm.Value
|
||||
if f.Linkage() == llvm.ExternalLinkage {
|
||||
// Exported function.
|
||||
// Note that getTaskPromisePtr will panic if it is called with
|
||||
// Note that getTaskStatePtr will panic if it is called with
|
||||
// a nil pointer, so blocking exported functions that try to
|
||||
// return anything will not work.
|
||||
parentHandle = llvm.ConstPointerNull(c.i8ptrType)
|
||||
@@ -400,7 +475,7 @@ func (c *Compiler) markAsyncFunctions() (needsScheduler bool, err error) {
|
||||
parentHandle = f.LastParam()
|
||||
if parentHandle.IsNil() || parentHandle.Name() != "parentHandle" {
|
||||
// sanity check
|
||||
panic("trying to make exported function async")
|
||||
panic("trying to make exported function async: " + f.Name())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -412,7 +487,7 @@ func (c *Compiler) markAsyncFunctions() (needsScheduler bool, err error) {
|
||||
// Return this value by writing to the pointer stored in the
|
||||
// parent handle. The parent coroutine has made an alloca that
|
||||
// we can write to to store our return value.
|
||||
returnValuePtr := c.createRuntimeCall("getTaskPromisePtr", []llvm.Value{parentHandle}, "coro.parentData")
|
||||
returnValuePtr := c.createRuntimeCall("getTaskStatePtr", []llvm.Value{parentHandle}, "coro.parentData")
|
||||
alloca := c.builder.CreateBitCast(returnValuePtr, llvm.PointerType(inst.Operand(0).Type(), 0), "coro.parentAlloca")
|
||||
c.builder.CreateStore(inst.Operand(0), alloca)
|
||||
default:
|
||||
@@ -491,9 +566,9 @@ func (c *Compiler) markAsyncFunctions() (needsScheduler bool, err error) {
|
||||
sleepCall.EraseFromParentAsInstruction()
|
||||
}
|
||||
|
||||
// Transform calls to runtime.deadlockStub into coroutine suspends (without
|
||||
// Transform calls to runtime.deadlock into coroutine suspends (without
|
||||
// resume).
|
||||
for _, deadlockCall := range getUses(deadlockStub) {
|
||||
for _, deadlockCall := range getUses(deadlock) {
|
||||
// deadlockCall must be a call instruction.
|
||||
frame := asyncFuncs[deadlockCall.InstructionParent().Parent()]
|
||||
|
||||
@@ -568,14 +643,14 @@ func (c *Compiler) lowerMakeGoroutineCalls() error {
|
||||
|
||||
makeGoroutine := c.mod.NamedFunction("runtime.makeGoroutine")
|
||||
for _, goroutine := range getUses(makeGoroutine) {
|
||||
bitcastIn := goroutine.Operand(0)
|
||||
origFunc := bitcastIn.Operand(0)
|
||||
ptrtointIn := goroutine.Operand(0)
|
||||
origFunc := ptrtointIn.Operand(0)
|
||||
uses := getUses(goroutine)
|
||||
if len(uses) != 1 || uses[0].IsABitCastInst().IsNil() {
|
||||
return errors.New("expected exactly 1 bitcast use of runtime.makeGoroutine")
|
||||
if len(uses) != 1 || uses[0].IsAIntToPtrInst().IsNil() {
|
||||
return errors.New("expected exactly 1 inttoptr use of runtime.makeGoroutine")
|
||||
}
|
||||
bitcastOut := uses[0]
|
||||
uses = getUses(bitcastOut)
|
||||
inttoptrOut := uses[0]
|
||||
uses = getUses(inttoptrOut)
|
||||
if len(uses) != 1 || uses[0].IsACallInst().IsNil() {
|
||||
return errors.New("expected exactly 1 call use of runtime.makeGoroutine bitcast")
|
||||
}
|
||||
@@ -590,7 +665,7 @@ func (c *Compiler) lowerMakeGoroutineCalls() error {
|
||||
c.builder.SetInsertPointBefore(realCall)
|
||||
c.builder.CreateCall(origFunc, params, "")
|
||||
realCall.EraseFromParentAsInstruction()
|
||||
bitcastOut.EraseFromParentAsInstruction()
|
||||
inttoptrOut.EraseFromParentAsInstruction()
|
||||
goroutine.EraseFromParentAsInstruction()
|
||||
}
|
||||
|
||||
|
||||
@@ -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, "")
|
||||
}
|
||||
+11
-2
@@ -18,10 +18,19 @@ import (
|
||||
// func ReadRegister(name string) uintptr
|
||||
//
|
||||
// The register name must be a constant, for example "sp".
|
||||
func (c *Compiler) emitReadRegister(args []ssa.Value) (llvm.Value, error) {
|
||||
func (c *Compiler) emitReadRegister(name string, args []ssa.Value) (llvm.Value, error) {
|
||||
fnType := llvm.FunctionType(c.uintptrType, []llvm.Type{}, false)
|
||||
regname := constant.StringVal(args[0].(*ssa.Const).Value)
|
||||
target := llvm.InlineAsm(fnType, "mov $0, "+regname, "=r", false, false, 0)
|
||||
var asm string
|
||||
switch name {
|
||||
case "device/arm.ReadRegister":
|
||||
asm = "mov $0, " + regname
|
||||
case "device/riscv.ReadRegister":
|
||||
asm = "mv $0, " + regname
|
||||
default:
|
||||
panic("unknown architecture")
|
||||
}
|
||||
target := llvm.InlineAsm(fnType, asm, "=r", false, false, 0)
|
||||
return c.builder.CreateCall(target, nil, ""), nil
|
||||
}
|
||||
|
||||
|
||||
@@ -163,8 +163,8 @@ func (c *Compiler) LowerInterfaces() {
|
||||
// run runs the pass itself.
|
||||
func (p *lowerInterfacesPass) run() {
|
||||
// Collect all type codes.
|
||||
typecodeIDPtr := llvm.PointerType(p.mod.GetTypeByName("runtime.typecodeID"), 0)
|
||||
typeInInterfacePtr := llvm.PointerType(p.mod.GetTypeByName("runtime.typeInInterface"), 0)
|
||||
typecodeIDPtr := llvm.PointerType(p.getLLVMRuntimeType("typecodeID"), 0)
|
||||
typeInInterfacePtr := llvm.PointerType(p.getLLVMRuntimeType("typeInInterface"), 0)
|
||||
var typesInInterfaces []llvm.Value
|
||||
for global := p.mod.FirstGlobal(); !global.IsNil(); global = llvm.NextGlobal(global) {
|
||||
switch global.Type() {
|
||||
@@ -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.
|
||||
|
||||
+102
-22
@@ -28,14 +28,14 @@ func (c *Compiler) parseMakeInterface(val llvm.Value, typ types.Type, pos token.
|
||||
itfMethodSetGlobal := c.getTypeMethodSet(typ)
|
||||
itfConcreteTypeGlobal := c.mod.NamedGlobal("typeInInterface:" + itfTypeCodeGlobal.Name())
|
||||
if itfConcreteTypeGlobal.IsNil() {
|
||||
typeInInterface := c.mod.GetTypeByName("runtime.typeInInterface")
|
||||
typeInInterface := c.getLLVMRuntimeType("typeInInterface")
|
||||
itfConcreteTypeGlobal = llvm.AddGlobal(c.mod, typeInInterface, "typeInInterface:"+itfTypeCodeGlobal.Name())
|
||||
itfConcreteTypeGlobal.SetInitializer(llvm.ConstNamedStruct(typeInInterface, []llvm.Value{itfTypeCodeGlobal, itfMethodSetGlobal}))
|
||||
itfConcreteTypeGlobal.SetGlobalConstant(true)
|
||||
itfConcreteTypeGlobal.SetLinkage(llvm.PrivateLinkage)
|
||||
}
|
||||
itfTypeCode := c.builder.CreatePtrToInt(itfConcreteTypeGlobal, c.uintptrType, "")
|
||||
itf := llvm.Undef(c.mod.GetTypeByName("runtime._interface"))
|
||||
itf := llvm.Undef(c.getLLVMRuntimeType("_interface"))
|
||||
itf = c.builder.CreateInsertValue(itf, itfTypeCode, 0, "")
|
||||
itf = c.builder.CreateInsertValue(itf, itfValue, 1, "")
|
||||
return itf
|
||||
@@ -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() {
|
||||
global = llvm.AddGlobal(c.mod, c.mod.GetTypeByName("runtime.typecodeID"), globalName)
|
||||
// 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())
|
||||
}
|
||||
@@ -163,11 +243,11 @@ func (c *Compiler) getTypeMethodSet(typ types.Type) llvm.Value {
|
||||
ms := c.ir.Program.MethodSets.MethodSet(typ)
|
||||
if ms.Len() == 0 {
|
||||
// no methods, so can leave that one out
|
||||
return llvm.ConstPointerNull(llvm.PointerType(c.mod.GetTypeByName("runtime.interfaceMethodInfo"), 0))
|
||||
return llvm.ConstPointerNull(llvm.PointerType(c.getLLVMRuntimeType("interfaceMethodInfo"), 0))
|
||||
}
|
||||
|
||||
methods := make([]llvm.Value, ms.Len())
|
||||
interfaceMethodInfoType := c.mod.GetTypeByName("runtime.interfaceMethodInfo")
|
||||
interfaceMethodInfoType := c.getLLVMRuntimeType("interfaceMethodInfo")
|
||||
for i := 0; i < ms.Len(); i++ {
|
||||
method := ms.At(i)
|
||||
signatureGlobal := c.getMethodSignature(method.Obj().(*types.Func))
|
||||
@@ -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"
|
||||
)
|
||||
|
||||
@@ -152,3 +154,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
|
||||
}
|
||||
|
||||
+5
-3
@@ -35,7 +35,7 @@ func (c *Compiler) emitMapLookup(keyType, valueType types.Type, m, key llvm.Valu
|
||||
c.emitLifetimeEnd(mapKeyPtr, mapKeySize)
|
||||
} else {
|
||||
// Not trivially comparable using memcmp.
|
||||
return llvm.Value{}, c.makeError(pos, "only strings, bools, ints or structs of bools/ints are supported as map keys, but got: "+keyType.String())
|
||||
return llvm.Value{}, c.makeError(pos, "only strings, bools, ints, pointers or structs of bools/ints are supported as map keys, but got: "+keyType.String())
|
||||
}
|
||||
|
||||
// Load the resulting value from the hashmap. The value is set to the zero
|
||||
@@ -69,7 +69,7 @@ func (c *Compiler) emitMapUpdate(keyType types.Type, m, key, value llvm.Value, p
|
||||
c.createRuntimeCall("hashmapBinarySet", params, "")
|
||||
c.emitLifetimeEnd(keyPtr, keySize)
|
||||
} else {
|
||||
c.addError(pos, "only strings, bools, ints or structs of bools/ints are supported as map keys, but got: "+keyType.String())
|
||||
c.addError(pos, "only strings, bools, ints, pointers or structs of bools/ints are supported as map keys, but got: "+keyType.String())
|
||||
}
|
||||
c.emitLifetimeEnd(valuePtr, valueSize)
|
||||
}
|
||||
@@ -89,7 +89,7 @@ func (c *Compiler) emitMapDelete(keyType types.Type, m, key llvm.Value, pos toke
|
||||
c.emitLifetimeEnd(keyPtr, keySize)
|
||||
return nil
|
||||
} else {
|
||||
return c.makeError(pos, "only strings, bools, ints or structs of bools/ints are supported as map keys, but got: "+keyType.String())
|
||||
return c.makeError(pos, "only strings, bools, ints, pointers or structs of bools/ints are supported as map keys, but got: "+keyType.String())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -121,6 +121,8 @@ func hashmapIsBinaryKey(keyType types.Type) bool {
|
||||
switch keyType := keyType.(type) {
|
||||
case *types.Basic:
|
||||
return keyType.Info()&(types.IsBoolean|types.IsInteger) != 0
|
||||
case *types.Pointer:
|
||||
return true
|
||||
case *types.Struct:
|
||||
for i := 0; i < keyType.NumFields(); i++ {
|
||||
fieldType := keyType.Field(i).Type().Underlying()
|
||||
|
||||
+26
-140
@@ -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()
|
||||
transform.OptimizeMaps(c.mod)
|
||||
c.OptimizeStringToBytes()
|
||||
c.OptimizeAllocs()
|
||||
transform.OptimizeAllocs(c.mod)
|
||||
c.LowerInterfaces()
|
||||
c.LowerFuncValues()
|
||||
|
||||
@@ -55,7 +61,7 @@ func (c *Compiler) Optimize(optLevel, sizeLevel int, inlinerThreshold uint) erro
|
||||
goPasses.Run(c.mod)
|
||||
|
||||
// Run TinyGo-specific interprocedural optimizations.
|
||||
c.OptimizeAllocs()
|
||||
transform.OptimizeAllocs(c.mod)
|
||||
c.OptimizeStringToBytes()
|
||||
|
||||
// Lower runtime.isnil calls to regular nil comparisons.
|
||||
@@ -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 functionsUsedInTransforms {
|
||||
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) {
|
||||
@@ -114,6 +129,14 @@ func (c *Compiler) Optimize(optLevel, sizeLevel int, inlinerThreshold uint) erro
|
||||
builder.Populate(modPasses)
|
||||
modPasses.Run(c.mod)
|
||||
|
||||
hasGCPass := c.addGlobalsBitmap()
|
||||
hasGCPass = c.makeGCStackSlots() || hasGCPass
|
||||
if hasGCPass {
|
||||
if err := c.Verify(); err != nil {
|
||||
return errors.New("GC pass caused a verification failure")
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -136,48 +159,6 @@ 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"))
|
||||
@@ -228,101 +209,6 @@ func (c *Compiler) OptimizeStringToBytes() {
|
||||
}
|
||||
}
|
||||
|
||||
// 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)
|
||||
|
||||
+407
-74
@@ -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,6 +59,72 @@ 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() {
|
||||
@@ -38,13 +137,21 @@ func (c *Compiler) assignTypeCodes(typeSlice typeInfoSlice) {
|
||||
}
|
||||
|
||||
// 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 +161,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 +204,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)]
|
||||
}
|
||||
|
||||
@@ -0,0 +1,107 @@
|
||||
package compiler
|
||||
|
||||
// This file manages symbols, that is, functions and globals. It reads their
|
||||
// pragmas, determines the link name, etc.
|
||||
|
||||
import (
|
||||
"go/ast"
|
||||
"go/token"
|
||||
"go/types"
|
||||
"strings"
|
||||
|
||||
"github.com/tinygo-org/tinygo/loader"
|
||||
"golang.org/x/tools/go/ssa"
|
||||
"tinygo.org/x/go-llvm"
|
||||
)
|
||||
|
||||
// globalInfo contains some information about a specific global. By default,
|
||||
// linkName is equal to .RelString(nil) on a global and extern is false, but for
|
||||
// some symbols this is different (due to //go:extern for example).
|
||||
type globalInfo struct {
|
||||
linkName string // go:extern
|
||||
extern bool // go:extern
|
||||
}
|
||||
|
||||
// loadASTComments loads comments on globals from the AST, for use later in the
|
||||
// program. In particular, they are required for //go:extern pragmas on globals.
|
||||
func (c *Compiler) loadASTComments(lprogram *loader.Program) {
|
||||
c.astComments = map[string]*ast.CommentGroup{}
|
||||
for _, pkgInfo := range lprogram.Sorted() {
|
||||
for _, file := range pkgInfo.Files {
|
||||
for _, decl := range file.Decls {
|
||||
switch decl := decl.(type) {
|
||||
case *ast.GenDecl:
|
||||
switch decl.Tok {
|
||||
case token.VAR:
|
||||
if len(decl.Specs) != 1 {
|
||||
continue
|
||||
}
|
||||
for _, spec := range decl.Specs {
|
||||
switch spec := spec.(type) {
|
||||
case *ast.ValueSpec: // decl.Tok == token.VAR
|
||||
for _, name := range spec.Names {
|
||||
id := pkgInfo.Pkg.Path() + "." + name.Name
|
||||
c.astComments[id] = decl.Doc
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// getGlobal returns a LLVM IR global value for a Go SSA global. It is added to
|
||||
// the LLVM IR if it has not been added already.
|
||||
func (c *Compiler) getGlobal(g *ssa.Global) llvm.Value {
|
||||
info := c.getGlobalInfo(g)
|
||||
llvmGlobal := c.mod.NamedGlobal(info.linkName)
|
||||
if llvmGlobal.IsNil() {
|
||||
llvmType := c.getLLVMType(g.Type().(*types.Pointer).Elem())
|
||||
llvmGlobal = llvm.AddGlobal(c.mod, llvmType, info.linkName)
|
||||
if !info.extern {
|
||||
llvmGlobal.SetInitializer(llvm.ConstNull(llvmType))
|
||||
llvmGlobal.SetLinkage(llvm.InternalLinkage)
|
||||
}
|
||||
}
|
||||
return llvmGlobal
|
||||
}
|
||||
|
||||
// getGlobalInfo returns some information about a specific global.
|
||||
func (c *Compiler) getGlobalInfo(g *ssa.Global) globalInfo {
|
||||
info := globalInfo{}
|
||||
if strings.HasPrefix(g.Name(), "C.") {
|
||||
// Created by CGo: such a name cannot be created by regular C code.
|
||||
info.linkName = g.Name()[2:]
|
||||
info.extern = true
|
||||
} else {
|
||||
// Pick the default linkName.
|
||||
info.linkName = g.RelString(nil)
|
||||
// Check for //go: pragmas, which may change the link name (among
|
||||
// others).
|
||||
doc := c.astComments[info.linkName]
|
||||
if doc != nil {
|
||||
info.parsePragmas(doc)
|
||||
}
|
||||
}
|
||||
return info
|
||||
}
|
||||
|
||||
// Parse //go: pragma comments from the source. In particular, it parses the
|
||||
// //go:extern pragma on globals.
|
||||
func (info *globalInfo) parsePragmas(doc *ast.CommentGroup) {
|
||||
for _, comment := range doc.List {
|
||||
if !strings.HasPrefix(comment.Text, "//go:") {
|
||||
continue
|
||||
}
|
||||
parts := strings.Fields(comment.Text)
|
||||
switch parts[0] {
|
||||
case "//go:extern":
|
||||
info.extern = true
|
||||
if len(parts) == 2 {
|
||||
info.linkName = parts[1]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+33
-8
@@ -4,7 +4,6 @@ package compiler
|
||||
// compiler builtins.
|
||||
|
||||
import (
|
||||
"go/constant"
|
||||
"strconv"
|
||||
|
||||
"golang.org/x/tools/go/ssa"
|
||||
@@ -14,7 +13,7 @@ import (
|
||||
// emitSyscall emits an inline system call instruction, depending on the target
|
||||
// OS/arch.
|
||||
func (c *Compiler) emitSyscall(frame *Frame, call *ssa.CallCommon) (llvm.Value, error) {
|
||||
num, _ := constant.Uint64Val(call.Args[0].(*ssa.Const).Value)
|
||||
num := c.getValue(frame, call.Args[0])
|
||||
var syscallResult llvm.Value
|
||||
switch {
|
||||
case c.GOARCH == "amd64":
|
||||
@@ -29,12 +28,12 @@ func (c *Compiler) emitSyscall(frame *Frame, call *ssa.CallCommon) (llvm.Value,
|
||||
// > All system classes enter the kernel via the syscall instruction.
|
||||
//
|
||||
// Source: https://opensource.apple.com/source/xnu/xnu-792.13.8/osfmk/mach/i386/syscall_sw.h
|
||||
num += 0x2000000
|
||||
num = c.builder.CreateOr(num, llvm.ConstInt(c.uintptrType, 0x2000000, false), "")
|
||||
}
|
||||
// Sources:
|
||||
// https://stackoverflow.com/a/2538212
|
||||
// https://en.wikibooks.org/wiki/X86_Assembly/Interfacing_with_Linux#syscall
|
||||
args := []llvm.Value{llvm.ConstInt(c.uintptrType, num, false)}
|
||||
args := []llvm.Value{num}
|
||||
argTypes := []llvm.Type{c.uintptrType}
|
||||
// Constraints will look something like:
|
||||
// "={rax},0,{rdi},{rsi},{rdx},{r10},{r8},{r9},~{rcx},~{r11}"
|
||||
@@ -59,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.
|
||||
@@ -81,7 +106,7 @@ func (c *Compiler) emitSyscall(frame *Frame, call *ssa.CallCommon) (llvm.Value,
|
||||
args = append(args, llvmValue)
|
||||
argTypes = append(argTypes, llvmValue.Type())
|
||||
}
|
||||
args = append(args, llvm.ConstInt(c.uintptrType, num, false))
|
||||
args = append(args, num)
|
||||
argTypes = append(argTypes, c.uintptrType)
|
||||
constraints += ",{r7}" // syscall number
|
||||
for i := len(call.Args) - 1; i < 4; i++ {
|
||||
@@ -111,7 +136,7 @@ func (c *Compiler) emitSyscall(frame *Frame, call *ssa.CallCommon) (llvm.Value,
|
||||
args = append(args, llvmValue)
|
||||
argTypes = append(argTypes, llvmValue.Type())
|
||||
}
|
||||
args = append(args, llvm.ConstInt(c.uintptrType, num, false))
|
||||
args = append(args, num)
|
||||
argTypes = append(argTypes, c.uintptrType)
|
||||
constraints += ",{x8}" // syscall number
|
||||
for i := len(call.Args) - 1; i < 8; i++ {
|
||||
@@ -140,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, "")
|
||||
@@ -156,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, "")
|
||||
|
||||
@@ -39,6 +39,9 @@ func (c *Compiler) emitPointerPack(values []llvm.Value) llvm.Value {
|
||||
// Packed data is bigger than a pointer, so allocate it on the heap.
|
||||
sizeValue := llvm.ConstInt(c.uintptrType, size, false)
|
||||
packedHeapAlloc = c.createRuntimeCall("alloc", []llvm.Value{sizeValue}, "")
|
||||
if c.needsStackObjects() {
|
||||
c.trackPointer(packedHeapAlloc)
|
||||
}
|
||||
packedAlloc = c.builder.CreateBitCast(packedHeapAlloc, llvm.PointerType(packedType, 0), "")
|
||||
}
|
||||
// Store all values in the alloca or heap pointer.
|
||||
@@ -98,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{
|
||||
|
||||
@@ -6,5 +6,5 @@ require (
|
||||
github.com/blakesmith/ar v0.0.0-20150311145944-8bd4349a67f2
|
||||
github.com/marcinbor85/gohex v0.0.0-20180128172054-7a43cd876e46
|
||||
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
|
||||
)
|
||||
|
||||
@@ -12,3 +12,5 @@ golang.org/x/tools v0.0.0-20190227180812-8dcc6e70cdef/go.mod h1:9Yl7xja0Znq3iFh3
|
||||
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=
|
||||
|
||||
+20
-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,
|
||||
@@ -176,6 +176,20 @@ func (fr *frame) evalBasicBlock(bb, incoming llvm.BasicBlock, indent string) (re
|
||||
continue // special case: bitcast of alloc
|
||||
}
|
||||
}
|
||||
if _, ok := fr.getLocal(operand).(*MapValue); ok {
|
||||
// Special case for runtime.trackPointer calls.
|
||||
// Note: this might not be entirely sound in some rare cases
|
||||
// where the map is stored in a dirty global.
|
||||
uses := getUses(inst)
|
||||
if len(uses) == 1 {
|
||||
use := uses[0]
|
||||
if !use.IsACallInst().IsNil() && !use.CalledValue().IsAFunction().IsNil() && use.CalledValue().Name() == "runtime.trackPointer" {
|
||||
continue
|
||||
}
|
||||
}
|
||||
// It is not possible in Go to bitcast a map value to a pointer.
|
||||
panic("unimplemented: bitcast of map")
|
||||
}
|
||||
value := fr.getLocal(operand).(*LocalValue)
|
||||
fr.locals[inst] = &LocalValue{fr.Eval, fr.builder.CreateBitCast(value.Value(), inst.Type(), "")}
|
||||
|
||||
@@ -239,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,
|
||||
@@ -298,7 +312,7 @@ 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}
|
||||
@@ -321,7 +335,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
|
||||
@@ -368,6 +382,8 @@ func (fr *frame) evalBasicBlock(bb, incoming llvm.BasicBlock, indent string) (re
|
||||
fr.locals[inst] = &LocalValue{fr.Eval, llvm.ConstInt(fr.Mod.Context().Int64Type(), 0, false)}
|
||||
case callee.Name() == "llvm.dbg.value":
|
||||
// do nothing
|
||||
case callee.Name() == "runtime.trackPointer":
|
||||
// do nothing
|
||||
case strings.HasPrefix(callee.Name(), "runtime.print") || callee.Name() == "runtime._panic":
|
||||
// This are all print instructions, which necessarily have side
|
||||
// effects but no results.
|
||||
|
||||
@@ -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.trackPointer":
|
||||
return &sideEffectResult{severity: sideEffectNone}
|
||||
case "llvm.dbg.value":
|
||||
return &sideEffectResult{severity: sideEffectNone}
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
+2
-2
@@ -162,7 +162,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 +311,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})
|
||||
|
||||
|
||||
@@ -2,7 +2,6 @@ package ir
|
||||
|
||||
import (
|
||||
"go/ast"
|
||||
"go/token"
|
||||
"go/types"
|
||||
"sort"
|
||||
"strings"
|
||||
@@ -23,16 +22,13 @@ type Program struct {
|
||||
mainPkg *ssa.Package
|
||||
Functions []*Function
|
||||
functionMap map[*ssa.Function]*Function
|
||||
Globals []*Global
|
||||
globalMap map[*ssa.Global]*Global
|
||||
comments map[string]*ast.CommentGroup
|
||||
NamedTypes []*NamedType
|
||||
}
|
||||
|
||||
// Function or method.
|
||||
type Function struct {
|
||||
*ssa.Function
|
||||
LLVMFn llvm.Value
|
||||
module string // go:wasm-module
|
||||
linkName string // go:linkname, go:export, go:interrupt
|
||||
exported bool // go:export
|
||||
nobounds bool // go:nobounds
|
||||
@@ -41,28 +37,6 @@ type Function struct {
|
||||
inline InlineType // go:inline
|
||||
}
|
||||
|
||||
// Global variable, possibly constant.
|
||||
type Global struct {
|
||||
*ssa.Global
|
||||
program *Program
|
||||
LLVMGlobal llvm.Value
|
||||
linkName string // go:extern
|
||||
extern bool // go:extern
|
||||
}
|
||||
|
||||
// Type with a name and possibly methods.
|
||||
type NamedType struct {
|
||||
*ssa.Type
|
||||
LLVMType llvm.Type
|
||||
}
|
||||
|
||||
// Type that is at some point put in an interface.
|
||||
type TypeWithMethods struct {
|
||||
t types.Type
|
||||
Num int
|
||||
Methods map[string]*types.Selection
|
||||
}
|
||||
|
||||
// Interface type that is at some point used in a type assert (to check whether
|
||||
// it implements another interface).
|
||||
type Interface struct {
|
||||
@@ -83,39 +57,14 @@ const (
|
||||
// //go:inline). The compiler will be more likely to inline this function,
|
||||
// but it is not a guarantee.
|
||||
InlineHint
|
||||
|
||||
// Don't inline, just like the GCC noinline attribute. Signalled using
|
||||
// //go:noinline.
|
||||
InlineNone
|
||||
)
|
||||
|
||||
// Create and initialize a new *Program from a *ssa.Program.
|
||||
func NewProgram(lprogram *loader.Program, mainPath string) *Program {
|
||||
comments := map[string]*ast.CommentGroup{}
|
||||
for _, pkgInfo := range lprogram.Sorted() {
|
||||
for _, file := range pkgInfo.Files {
|
||||
for _, decl := range file.Decls {
|
||||
switch decl := decl.(type) {
|
||||
case *ast.GenDecl:
|
||||
switch decl.Tok {
|
||||
case token.TYPE, token.VAR:
|
||||
if len(decl.Specs) != 1 {
|
||||
continue
|
||||
}
|
||||
for _, spec := range decl.Specs {
|
||||
switch spec := spec.(type) {
|
||||
case *ast.TypeSpec: // decl.Tok == token.TYPE
|
||||
id := pkgInfo.Pkg.Path() + "." + spec.Name.Name
|
||||
comments[id] = decl.Doc
|
||||
case *ast.ValueSpec: // decl.Tok == token.VAR
|
||||
for _, name := range spec.Names {
|
||||
id := pkgInfo.Pkg.Path() + "." + name.Name
|
||||
comments[id] = decl.Doc
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
program := lprogram.LoadSSA()
|
||||
program.Build()
|
||||
|
||||
@@ -187,8 +136,6 @@ func NewProgram(lprogram *loader.Program, mainPath string) *Program {
|
||||
LoaderProgram: lprogram,
|
||||
mainPkg: mainPkg,
|
||||
functionMap: make(map[*ssa.Function]*Function),
|
||||
globalMap: make(map[*ssa.Global]*Global),
|
||||
comments: comments,
|
||||
}
|
||||
|
||||
for _, pkg := range packageList {
|
||||
@@ -213,8 +160,6 @@ func (p *Program) AddPackage(pkg *ssa.Package) {
|
||||
case *ssa.Function:
|
||||
p.addFunction(member)
|
||||
case *ssa.Type:
|
||||
t := &NamedType{Type: member}
|
||||
p.NamedTypes = append(p.NamedTypes, t)
|
||||
methods := getAllMethods(pkg.Prog, member.Type())
|
||||
if !types.IsInterface(member.Type()) {
|
||||
// named type
|
||||
@@ -223,13 +168,7 @@ func (p *Program) AddPackage(pkg *ssa.Package) {
|
||||
}
|
||||
}
|
||||
case *ssa.Global:
|
||||
g := &Global{program: p, Global: member}
|
||||
doc := p.comments[g.RelString(nil)]
|
||||
if doc != nil {
|
||||
g.parsePragmas(doc)
|
||||
}
|
||||
p.Globals = append(p.Globals, g)
|
||||
p.globalMap[member] = g
|
||||
// Ignore. Globals are not handled here.
|
||||
case *ssa.NamedConst:
|
||||
// Ignore: these are already resolved.
|
||||
default:
|
||||
@@ -263,10 +202,6 @@ func (p *Program) GetFunction(ssaFn *ssa.Function) *Function {
|
||||
return p.functionMap[ssaFn]
|
||||
}
|
||||
|
||||
func (p *Program) GetGlobal(ssaGlobal *ssa.Global) *Global {
|
||||
return p.globalMap[ssaGlobal]
|
||||
}
|
||||
|
||||
func (p *Program) MainPkg() *ssa.Package {
|
||||
return p.mainPkg
|
||||
}
|
||||
@@ -295,8 +230,16 @@ 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":
|
||||
f.inline = InlineNone
|
||||
case "//go:interrupt":
|
||||
if len(parts) != 2 {
|
||||
continue
|
||||
@@ -355,6 +298,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 != "" {
|
||||
@@ -389,72 +337,6 @@ func (f *Function) CName() string {
|
||||
return ""
|
||||
}
|
||||
|
||||
// Parse //go: pragma comments from the source.
|
||||
func (g *Global) parsePragmas(doc *ast.CommentGroup) {
|
||||
for _, comment := range doc.List {
|
||||
if !strings.HasPrefix(comment.Text, "//go:") {
|
||||
continue
|
||||
}
|
||||
parts := strings.Fields(comment.Text)
|
||||
switch parts[0] {
|
||||
case "//go:extern":
|
||||
g.extern = true
|
||||
if len(parts) == 2 {
|
||||
g.linkName = parts[1]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Return the link name for this global.
|
||||
func (g *Global) LinkName() string {
|
||||
if g.linkName != "" {
|
||||
return g.linkName
|
||||
}
|
||||
if name := g.CName(); name != "" {
|
||||
return name
|
||||
}
|
||||
return g.RelString(nil)
|
||||
}
|
||||
|
||||
func (g *Global) IsExtern() bool {
|
||||
return g.extern || g.CName() != ""
|
||||
}
|
||||
|
||||
// Return the name of the C global if this is a CGo wrapper. Otherwise, return a
|
||||
// zero-length string.
|
||||
func (g *Global) CName() string {
|
||||
name := g.Name()
|
||||
if strings.HasPrefix(name, "C.") {
|
||||
// created by ../loader/cgo.go
|
||||
return name[2:]
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// Return true if this named type is annotated with the //go:volatile pragma,
|
||||
// for volatile loads and stores.
|
||||
func (p *Program) IsVolatile(t types.Type) bool {
|
||||
if t, ok := t.(*types.Named); !ok {
|
||||
return false
|
||||
} else {
|
||||
if t.Obj().Pkg() == nil {
|
||||
return false
|
||||
}
|
||||
id := t.Obj().Pkg().Path() + "." + t.Obj().Name()
|
||||
doc := p.comments[id]
|
||||
if doc == nil {
|
||||
return false
|
||||
}
|
||||
for _, line := range doc.List {
|
||||
if strings.TrimSpace(line.Text) == "//go:volatile" {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// Get all methods of a type.
|
||||
func getAllMethods(prog *ssa.Program, typ types.Type) []*types.Selection {
|
||||
ms := prog.MethodSets.MethodSet(typ)
|
||||
|
||||
+1
-1
Submodule lib/cmsis-svd updated: b6f0a65ac3...5910f3d115
@@ -63,6 +63,7 @@ func Link(linker string, flags ...string) error {
|
||||
cmd := exec.Command(linker, flags...)
|
||||
cmd.Stdout = os.Stdout
|
||||
cmd.Stderr = os.Stderr
|
||||
cmd.Dir = sourceDir()
|
||||
return cmd.Run()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,5 +20,6 @@ func Link(linker string, flags ...string) error {
|
||||
cmd := exec.Command(linker, flags...)
|
||||
cmd.Stdout = os.Stdout
|
||||
cmd.Stderr = os.Stderr
|
||||
cmd.Dir = sourceDir()
|
||||
return cmd.Run()
|
||||
}
|
||||
|
||||
+144
-29
@@ -1,6 +1,7 @@
|
||||
package loader
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"go/ast"
|
||||
"go/build"
|
||||
@@ -10,22 +11,26 @@ import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
"strings"
|
||||
"text/template"
|
||||
|
||||
"github.com/tinygo-org/tinygo/cgo"
|
||||
)
|
||||
|
||||
// Program holds all packages and some metadata about the program as a whole.
|
||||
type Program struct {
|
||||
Build *build.Context
|
||||
OverlayBuild *build.Context
|
||||
ShouldOverlay func(path string) bool
|
||||
Packages map[string]*Package
|
||||
sorted []*Package
|
||||
fset *token.FileSet
|
||||
TypeChecker types.Config
|
||||
Dir string // current working directory (for error reporting)
|
||||
TINYGOROOT string // root of the TinyGo installation or root of the source code
|
||||
CFlags []string
|
||||
mainPkg string
|
||||
Build *build.Context
|
||||
OverlayBuild *build.Context
|
||||
OverlayPath func(path string) string
|
||||
Packages map[string]*Package
|
||||
sorted []*Package
|
||||
fset *token.FileSet
|
||||
TypeChecker types.Config
|
||||
Dir string // current working directory (for error reporting)
|
||||
TINYGOROOT string // root of the TinyGo installation or root of the source code
|
||||
CFlags []string
|
||||
ClangHeaders string
|
||||
}
|
||||
|
||||
// Package holds a loaded package, its imports, and its parsed files.
|
||||
@@ -48,8 +53,9 @@ func (p *Program) Import(path, srcDir string) (*Package, error) {
|
||||
|
||||
// Load this package.
|
||||
ctx := p.Build
|
||||
if p.ShouldOverlay(path) {
|
||||
if newPath := p.OverlayPath(path); newPath != "" {
|
||||
ctx = p.OverlayBuild
|
||||
path = newPath
|
||||
}
|
||||
buildPkg, err := ctx.Import(path, srcDir, build.ImportComment)
|
||||
if err != nil {
|
||||
@@ -62,6 +68,11 @@ func (p *Program) Import(path, srcDir string) (*Package, error) {
|
||||
p.sorted = nil // invalidate the sorted order of packages
|
||||
pkg := p.newPackage(buildPkg)
|
||||
p.Packages[buildPkg.ImportPath] = pkg
|
||||
|
||||
if p.mainPkg == "" {
|
||||
p.mainPkg = buildPkg.ImportPath
|
||||
}
|
||||
|
||||
return pkg, nil
|
||||
}
|
||||
|
||||
@@ -91,6 +102,11 @@ func (p *Program) ImportFile(path string) (*Package, error) {
|
||||
p.sorted = nil // invalidate the sorted order of packages
|
||||
pkg := p.newPackage(buildPkg)
|
||||
p.Packages[buildPkg.ImportPath] = pkg
|
||||
|
||||
if p.mainPkg == "" {
|
||||
p.mainPkg = buildPkg.ImportPath
|
||||
}
|
||||
|
||||
return pkg, nil
|
||||
}
|
||||
|
||||
@@ -169,10 +185,12 @@ func (p *Program) sort() {
|
||||
// The returned error may be an Errors error, which contains a list of errors.
|
||||
//
|
||||
// Idempotent.
|
||||
func (p *Program) Parse() error {
|
||||
func (p *Program) Parse(compileTestBinary bool) error {
|
||||
includeTests := compileTestBinary
|
||||
|
||||
// Load all imports
|
||||
for _, pkg := range p.Sorted() {
|
||||
err := pkg.importRecursively()
|
||||
err := pkg.importRecursively(includeTests)
|
||||
if err != nil {
|
||||
if err, ok := err.(*ImportCycleError); ok {
|
||||
if pkg.ImportPath != err.Packages[0] {
|
||||
@@ -185,7 +203,14 @@ func (p *Program) Parse() error {
|
||||
|
||||
// Parse all packages.
|
||||
for _, pkg := range p.Sorted() {
|
||||
err := pkg.Parse()
|
||||
err := pkg.Parse(includeTests)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if compileTestBinary {
|
||||
err := p.SwapTestMain()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -202,6 +227,83 @@ func (p *Program) Parse() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *Program) SwapTestMain() error {
|
||||
var tests []string
|
||||
|
||||
isTestFunc := func(f *ast.FuncDecl) bool {
|
||||
// TODO: improve signature check
|
||||
if strings.HasPrefix(f.Name.Name, "Test") && f.Name.Name != "TestMain" {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
mainPkg := p.Packages[p.mainPkg]
|
||||
for _, f := range mainPkg.Files {
|
||||
for i, d := range f.Decls {
|
||||
switch v := d.(type) {
|
||||
case *ast.FuncDecl:
|
||||
if isTestFunc(v) {
|
||||
tests = append(tests, v.Name.Name)
|
||||
}
|
||||
if v.Name.Name == "main" {
|
||||
// Remove main
|
||||
if len(f.Decls) == 1 {
|
||||
f.Decls = make([]ast.Decl, 0)
|
||||
} else {
|
||||
f.Decls[i] = f.Decls[len(f.Decls)-1]
|
||||
f.Decls = f.Decls[:len(f.Decls)-1]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Check if they defined a TestMain and call it instead of testing.TestMain
|
||||
const mainBody = `package main
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func main () {
|
||||
m := &testing.M{
|
||||
Tests: []testing.TestToCall{
|
||||
{{range .TestFunctions}}
|
||||
{Name: "{{.}}", Func: {{.}}},
|
||||
{{end}}
|
||||
},
|
||||
}
|
||||
|
||||
testing.TestMain(m)
|
||||
}
|
||||
`
|
||||
tmpl := template.Must(template.New("testmain").Parse(mainBody))
|
||||
b := bytes.Buffer{}
|
||||
tmplData := struct {
|
||||
TestFunctions []string
|
||||
}{
|
||||
TestFunctions: tests,
|
||||
}
|
||||
|
||||
err := tmpl.Execute(&b, tmplData)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
path := filepath.Join(p.mainPkg, "$testmain.go")
|
||||
|
||||
if p.fset == nil {
|
||||
p.fset = token.NewFileSet()
|
||||
}
|
||||
|
||||
newMain, err := parser.ParseFile(p.fset, path, b.Bytes(), parser.AllErrors)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
mainPkg.Files = append(mainPkg.Files, newMain)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// parseFile is a wrapper around parser.ParseFile.
|
||||
func (p *Program) parseFile(path string, mode parser.Mode) (*ast.File, error) {
|
||||
if p.fset == nil {
|
||||
@@ -226,7 +328,7 @@ func (p *Program) parseFile(path string, mode parser.Mode) (*ast.File, error) {
|
||||
// Parse parses and typechecks this package.
|
||||
//
|
||||
// Idempotent.
|
||||
func (p *Package) Parse() error {
|
||||
func (p *Package) Parse(includeTests bool) error {
|
||||
if len(p.Files) != 0 {
|
||||
return nil
|
||||
}
|
||||
@@ -240,7 +342,7 @@ func (p *Package) Parse() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
files, err := p.parseFiles()
|
||||
files, err := p.parseFiles(includeTests)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -279,11 +381,21 @@ func (p *Package) Check() error {
|
||||
}
|
||||
|
||||
// parseFiles parses the loaded list of files and returns this list.
|
||||
func (p *Package) parseFiles() ([]*ast.File, error) {
|
||||
func (p *Package) parseFiles(includeTests bool) ([]*ast.File, error) {
|
||||
// TODO: do this concurrently.
|
||||
var files []*ast.File
|
||||
var fileErrs []error
|
||||
for _, file := range p.GoFiles {
|
||||
|
||||
var gofiles []string
|
||||
if includeTests {
|
||||
gofiles = make([]string, 0, len(p.GoFiles)+len(p.TestGoFiles))
|
||||
gofiles = append(gofiles, p.GoFiles...)
|
||||
gofiles = append(gofiles, p.TestGoFiles...)
|
||||
} else {
|
||||
gofiles = p.GoFiles
|
||||
}
|
||||
|
||||
for _, file := range gofiles {
|
||||
f, err := p.parseFile(filepath.Join(p.Package.Dir, file), parser.ParseComments)
|
||||
if err != nil {
|
||||
fileErrs = append(fileErrs, err)
|
||||
@@ -305,15 +417,11 @@ func (p *Package) parseFiles() ([]*ast.File, error) {
|
||||
files = append(files, f)
|
||||
}
|
||||
if len(p.CgoFiles) != 0 {
|
||||
clangIncludes := ""
|
||||
if _, err := os.Stat(filepath.Join(p.TINYGOROOT, "llvm", "tools", "clang", "lib", "Headers")); !os.IsNotExist(err) {
|
||||
// Running from the source directory.
|
||||
clangIncludes = filepath.Join(p.TINYGOROOT, "llvm", "tools", "clang", "lib", "Headers")
|
||||
} else {
|
||||
// Running from the installation directory.
|
||||
clangIncludes = filepath.Join(p.TINYGOROOT, "lib", "clang", "include")
|
||||
cflags := append(p.CFlags, "-I"+p.Package.Dir)
|
||||
if p.ClangHeaders != "" {
|
||||
cflags = append(cflags, "-I"+p.ClangHeaders)
|
||||
}
|
||||
generated, errs := cgo.Process(files, p.Program.Dir, p.fset, append(p.CFlags, "-I"+p.Package.Dir, "-I"+clangIncludes))
|
||||
generated, errs := cgo.Process(files, p.Program.Dir, p.fset, cflags)
|
||||
if errs != nil {
|
||||
fileErrs = append(fileErrs, errs...)
|
||||
}
|
||||
@@ -322,6 +430,7 @@ func (p *Package) parseFiles() ([]*ast.File, error) {
|
||||
if len(fileErrs) != 0 {
|
||||
return nil, Errors{p, fileErrs}
|
||||
}
|
||||
|
||||
return files, nil
|
||||
}
|
||||
|
||||
@@ -342,9 +451,15 @@ func (p *Package) Import(to string) (*types.Package, error) {
|
||||
// importRecursively() on the imported packages as well.
|
||||
//
|
||||
// Idempotent.
|
||||
func (p *Package) importRecursively() error {
|
||||
func (p *Package) importRecursively(includeTests bool) error {
|
||||
p.Importing = true
|
||||
for _, to := range p.Package.Imports {
|
||||
|
||||
imports := p.Package.Imports
|
||||
if includeTests {
|
||||
imports = append(imports, p.Package.TestImports...)
|
||||
}
|
||||
|
||||
for _, to := range imports {
|
||||
if to == "C" {
|
||||
// Do CGo processing in a later stage.
|
||||
continue
|
||||
@@ -362,7 +477,7 @@ func (p *Package) importRecursively() error {
|
||||
if importedPkg.Importing {
|
||||
return &ImportCycleError{[]string{p.ImportPath, importedPkg.ImportPath}, p.ImportPos[to]}
|
||||
}
|
||||
err = importedPkg.importRecursively()
|
||||
err = importedPkg.importRecursively(false)
|
||||
if err != nil {
|
||||
if err, ok := err.(*ImportCycleError); ok {
|
||||
err.Packages = append([]string{p.ImportPath}, err.Packages...)
|
||||
|
||||
@@ -47,13 +47,18 @@ type BuildConfig struct {
|
||||
opt string
|
||||
gc string
|
||||
panicStrategy string
|
||||
scheduler string
|
||||
printIR bool
|
||||
dumpSSA bool
|
||||
verifyIR bool
|
||||
debug bool
|
||||
printSizes string
|
||||
cFlags []string
|
||||
ldFlags []string
|
||||
tags string
|
||||
wasmAbi string
|
||||
heapSize int64
|
||||
testConfig compiler.TestConfig
|
||||
}
|
||||
|
||||
// Helper function for Compiler object.
|
||||
@@ -81,16 +86,23 @@ func Compile(pkgName, outpath string, spec *TargetSpec, config *BuildConfig, act
|
||||
return errors.New("cannot locate $GOROOT, please set it manually")
|
||||
}
|
||||
tags := spec.BuildTags
|
||||
major, minor := getGorootVersion(goroot)
|
||||
if major != 1 {
|
||||
if major == 0 {
|
||||
return errors.New("could not read version from GOROOT: " + goroot)
|
||||
}
|
||||
return fmt.Errorf("expected major version 1, got go%d.%d", major, minor)
|
||||
major, minor, err := getGorootVersion(goroot)
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not read version from GOROOT (%v): %v", goroot, err)
|
||||
}
|
||||
if major != 1 || (minor != 11 && minor != 12) {
|
||||
return fmt.Errorf("requires go version 1.11 or 1.12, got go%d.%d", major, minor)
|
||||
}
|
||||
for i := 1; i <= minor; i++ {
|
||||
tags = append(tags, fmt.Sprintf("go1.%d", i))
|
||||
}
|
||||
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,
|
||||
@@ -99,14 +111,18 @@ 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(),
|
||||
BuildTags: tags,
|
||||
TestConfig: config.testConfig,
|
||||
}
|
||||
c, err := compiler.NewCompiler(pkgName, compilerConfig)
|
||||
if err != nil {
|
||||
@@ -140,9 +156,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
|
||||
@@ -154,9 +167,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
|
||||
@@ -233,10 +243,16 @@ func Compile(pkgName, outpath string, spec *TargetSpec, config *BuildConfig, act
|
||||
// Prepare link command.
|
||||
executable := filepath.Join(dir, "main")
|
||||
tmppath := executable // final file
|
||||
ldflags := append(ldflags, "-o", executable, objfile, "-L", root)
|
||||
ldflags = append(ldflags, "-o", executable, objfile, "-L", root)
|
||||
if spec.RTLib == "compiler-rt" {
|
||||
ldflags = append(ldflags, librt)
|
||||
}
|
||||
if spec.GOARCH == "wasm" {
|
||||
// Round heap size to next multiple of 65536 (the WebAssembly page
|
||||
// size).
|
||||
heapSize := (config.heapSize + (65536 - 1)) &^ (65536 - 1)
|
||||
ldflags = append(ldflags, "--initial-memory="+strconv.FormatInt(heapSize, 10))
|
||||
}
|
||||
|
||||
// Compile extra files.
|
||||
for i, path := range spec.ExtraFiles {
|
||||
@@ -348,6 +364,33 @@ func Build(pkgName, outpath, target string, config *BuildConfig) error {
|
||||
})
|
||||
}
|
||||
|
||||
func Test(pkgName, target string, config *BuildConfig) error {
|
||||
spec, err := LoadTarget(target)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
spec.BuildTags = append(spec.BuildTags, "test")
|
||||
config.testConfig.CompileTestBinary = true
|
||||
return Compile(pkgName, ".elf", spec, config, func(tmppath string) error {
|
||||
cmd := exec.Command(tmppath)
|
||||
cmd.Stdout = os.Stdout
|
||||
cmd.Stderr = os.Stderr
|
||||
err := cmd.Run()
|
||||
if err != nil {
|
||||
// Propagate the exit code
|
||||
if err, ok := err.(*exec.ExitError); ok {
|
||||
if status, ok := err.Sys().(syscall.WaitStatus); ok {
|
||||
os.Exit(status.ExitStatus())
|
||||
}
|
||||
os.Exit(1)
|
||||
}
|
||||
return &commandError{"failed to run compiled binary", tmppath, err}
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
func Flash(pkgName, target, port string, config *BuildConfig) error {
|
||||
spec, err := LoadTarget(target)
|
||||
if err != nil {
|
||||
@@ -506,6 +549,30 @@ func Run(pkgName, target string, config *BuildConfig) error {
|
||||
})
|
||||
}
|
||||
|
||||
// parseSize converts a human-readable size (with k/m/g suffix) into a plain
|
||||
// number.
|
||||
func parseSize(s string) (int64, error) {
|
||||
s = strings.ToLower(strings.TrimSpace(s))
|
||||
if len(s) == 0 {
|
||||
return 0, errors.New("no size provided")
|
||||
}
|
||||
multiply := int64(1)
|
||||
switch s[len(s)-1] {
|
||||
case 'k':
|
||||
multiply = 1 << 10
|
||||
case 'm':
|
||||
multiply = 1 << 20
|
||||
case 'g':
|
||||
multiply = 1 << 30
|
||||
}
|
||||
if multiply != 1 {
|
||||
s = s[:len(s)-1]
|
||||
}
|
||||
n, err := strconv.ParseInt(s, 0, 64)
|
||||
n *= multiply
|
||||
return n, err
|
||||
}
|
||||
|
||||
func usage() {
|
||||
fmt.Fprintln(os.Stderr, "TinyGo is a Go compiler for small places.")
|
||||
fmt.Fprintln(os.Stderr, "version:", version)
|
||||
@@ -513,6 +580,7 @@ func usage() {
|
||||
fmt.Fprintln(os.Stderr, "\ncommands:")
|
||||
fmt.Fprintln(os.Stderr, " build: compile packages and dependencies")
|
||||
fmt.Fprintln(os.Stderr, " run: compile and run immediately")
|
||||
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()+")")
|
||||
@@ -523,23 +591,24 @@ func usage() {
|
||||
|
||||
func handleCompilerError(err error) {
|
||||
if err != nil {
|
||||
if errUnsupported, ok := err.(*interp.Unsupported); ok {
|
||||
switch err := err.(type) {
|
||||
case *interp.Unsupported:
|
||||
// hit an unknown/unsupported instruction
|
||||
fmt.Fprintln(os.Stderr, "unsupported instruction during init evaluation:")
|
||||
errUnsupported.Inst.Dump()
|
||||
err.Inst.Dump()
|
||||
fmt.Fprintln(os.Stderr)
|
||||
} else if errCompiler, ok := err.(types.Error); ok {
|
||||
fmt.Fprintln(os.Stderr, errCompiler)
|
||||
} else if errLoader, ok := err.(loader.Errors); ok {
|
||||
fmt.Fprintln(os.Stderr, "#", errLoader.Pkg.ImportPath)
|
||||
for _, err := range errLoader.Errs {
|
||||
case types.Error:
|
||||
fmt.Fprintln(os.Stderr, err)
|
||||
case loader.Errors:
|
||||
fmt.Fprintln(os.Stderr, "#", err.Pkg.ImportPath)
|
||||
for _, err := range err.Errs {
|
||||
fmt.Fprintln(os.Stderr, err)
|
||||
}
|
||||
} else if errMulti, ok := err.(*multiError); ok {
|
||||
for _, err := range errMulti.Errs {
|
||||
case *multiError:
|
||||
for _, err := range err.Errs {
|
||||
fmt.Fprintln(os.Stderr, err)
|
||||
}
|
||||
} else {
|
||||
default:
|
||||
fmt.Fprintln(os.Stderr, "error:", err)
|
||||
}
|
||||
os.Exit(1)
|
||||
@@ -549,11 +618,14 @@ func handleCompilerError(err error) {
|
||||
func main() {
|
||||
outpath := flag.String("o", "", "output filename")
|
||||
opt := flag.String("opt", "z", "optimization level: 0, 1, 2, s, z")
|
||||
gc := flag.String("gc", "", "garbage collector to use (none, dumb, marksweep)")
|
||||
panicStrategy := flag.String("panic", "print", "panic strategy (abort, trap)")
|
||||
gc := flag.String("gc", "", "garbage collector to use (none, leaking, conservative)")
|
||||
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")
|
||||
target := flag.String("target", "", "LLVM target")
|
||||
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)")
|
||||
nodebug := flag.Bool("no-debug", false, "disable DWARF debug symbol generation")
|
||||
ocdOutput := flag.Bool("ocd-output", false, "print OCD daemon output during debug")
|
||||
@@ -561,6 +633,7 @@ func main() {
|
||||
cFlags := flag.String("cflags", "", "additional cflags for compiler")
|
||||
ldFlags := flag.String("ldflags", "", "additional ldflags for linker")
|
||||
wasmAbi := flag.String("wasm-abi", "js", "WebAssembly ABI conventions: js (no i64 params) or generic")
|
||||
heapSize := flag.String("heap-size", "1M", "default heap size in bytes (only supported by WebAssembly)")
|
||||
|
||||
if len(os.Args) < 2 {
|
||||
fmt.Fprintln(os.Stderr, "No command-line arguments supplied.")
|
||||
@@ -574,10 +647,13 @@ func main() {
|
||||
opt: *opt,
|
||||
gc: *gc,
|
||||
panicStrategy: *panicStrategy,
|
||||
scheduler: *scheduler,
|
||||
printIR: *printIR,
|
||||
dumpSSA: *dumpSSA,
|
||||
verifyIR: *verifyIR,
|
||||
debug: !*nodebug,
|
||||
printSizes: *printSize,
|
||||
tags: *tags,
|
||||
wasmAbi: *wasmAbi,
|
||||
}
|
||||
|
||||
@@ -595,6 +671,13 @@ func main() {
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
var err error
|
||||
if config.heapSize, err = parseSize(*heapSize); err != nil {
|
||||
fmt.Fprintln(os.Stderr, "Could not read heap size:", *heapSize)
|
||||
usage()
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
os.Setenv("CC", "clang -target="+*target)
|
||||
|
||||
switch command {
|
||||
@@ -604,8 +687,11 @@ func main() {
|
||||
usage()
|
||||
os.Exit(1)
|
||||
}
|
||||
if flag.NArg() != 1 {
|
||||
fmt.Fprintln(os.Stderr, "No package specified.")
|
||||
pkgName := "."
|
||||
if flag.NArg() == 1 {
|
||||
pkgName = flag.Arg(0)
|
||||
} else if flag.NArg() > 1 {
|
||||
fmt.Fprintln(os.Stderr, "build only accepts a single positional argument: package name, but multiple were specified")
|
||||
usage()
|
||||
os.Exit(1)
|
||||
}
|
||||
@@ -613,7 +699,7 @@ func main() {
|
||||
if target == "" && filepath.Ext(*outpath) == ".wasm" {
|
||||
target = "wasm"
|
||||
}
|
||||
err := Build(flag.Arg(0), *outpath, target, config)
|
||||
err := Build(pkgName, *outpath, target, config)
|
||||
handleCompilerError(err)
|
||||
case "build-builtins":
|
||||
// Note: this command is only meant to be used while making a release!
|
||||
@@ -655,6 +741,17 @@ func main() {
|
||||
}
|
||||
err := Run(flag.Arg(0), *target, config)
|
||||
handleCompilerError(err)
|
||||
case "test":
|
||||
pkgName := "."
|
||||
if flag.NArg() == 1 {
|
||||
pkgName = flag.Arg(0)
|
||||
} else if flag.NArg() > 1 {
|
||||
fmt.Fprintln(os.Stderr, "test only accepts a single positional argument: package name, but multiple were specified")
|
||||
usage()
|
||||
os.Exit(1)
|
||||
}
|
||||
err := Test(pkgName, *target, config)
|
||||
handleCompilerError(err)
|
||||
case "clean":
|
||||
// remove cache directory
|
||||
dir := cacheDir()
|
||||
|
||||
@@ -116,6 +116,7 @@ func runTest(path, tmpdir string, target string, t *testing.T) {
|
||||
opt: "z",
|
||||
printIR: false,
|
||||
dumpSSA: false,
|
||||
verifyIR: true,
|
||||
debug: false,
|
||||
printSizes: "",
|
||||
wasmAbi: "js",
|
||||
|
||||
+44
-11
@@ -30,6 +30,7 @@
|
||||
package arm
|
||||
|
||||
import (
|
||||
"runtime/volatile"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
@@ -69,37 +70,58 @@ func SVCall3(num uintptr, a1, a2, a3 interface{}) uintptr
|
||||
// Run the following system call (SVCall) with 4 arguments.
|
||||
func SVCall4(num uintptr, a1, a2, a3, a4 interface{}) uintptr
|
||||
|
||||
//go:volatile
|
||||
type RegValue uint32
|
||||
|
||||
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:
|
||||
// http://infocenter.arm.com/help/index.jsp?topic=/com.arm.doc.dui0553a/CIHIGCIF.html
|
||||
type NVIC_Type struct {
|
||||
ISER [8]RegValue // Interrupt Set-enable Registers
|
||||
ISER [8]volatile.Register32 // Interrupt Set-enable Registers
|
||||
_ [24]uint32
|
||||
ICER [8]RegValue // Interrupt Clear-enable Registers
|
||||
ICER [8]volatile.Register32 // Interrupt Clear-enable Registers
|
||||
_ [24]uint32
|
||||
ISPR [8]RegValue // Interrupt Set-pending Registers
|
||||
ISPR [8]volatile.Register32 // Interrupt Set-pending Registers
|
||||
_ [24]uint32
|
||||
ICPR [8]RegValue // Interrupt Clear-pending Registers
|
||||
ICPR [8]volatile.Register32 // Interrupt Clear-pending Registers
|
||||
_ [24]uint32
|
||||
IABR [8]RegValue // Interrupt Active Bit Registers
|
||||
IABR [8]volatile.Register32 // Interrupt Active Bit Registers
|
||||
_ [56]uint32
|
||||
IPR [60]RegValue // Interrupt Priority Registers
|
||||
IPR [60]volatile.Register32 // Interrupt Priority Registers
|
||||
}
|
||||
|
||||
var NVIC = (*NVIC_Type)(unsafe.Pointer(uintptr(NVIC_BASE)))
|
||||
|
||||
// Enable the given interrupt number.
|
||||
func EnableIRQ(irq uint32) {
|
||||
NVIC.ISER[irq>>5] = 1 << (irq & 0x1F)
|
||||
NVIC.ISER[irq>>5].Set(1 << (irq & 0x1F))
|
||||
}
|
||||
|
||||
// Set the priority of the given interrupt number.
|
||||
@@ -116,7 +138,7 @@ func SetPriority(irq uint32, priority uint32) {
|
||||
regpos := irq % 4
|
||||
mask := uint32(0xff) << (regpos * 8) // bits to clear
|
||||
priority = priority << (regpos * 8) // bits to set
|
||||
NVIC.IPR[regnum] = RegValue((uint32(NVIC.IPR[regnum]) &^ mask) | priority)
|
||||
NVIC.IPR[regnum].Set((uint32(NVIC.IPR[regnum].Get()) &^ mask) | priority)
|
||||
}
|
||||
|
||||
// DisableInterrupts disables all interrupts, and returns the old state.
|
||||
@@ -136,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,10 @@
|
||||
package riscv
|
||||
|
||||
// Run the given assembly code. The code will be marked as having side effects,
|
||||
// as it doesn't produce output and thus would normally be eliminated by the
|
||||
// optimizer.
|
||||
func Asm(asm string)
|
||||
|
||||
// ReadRegister returns the contents of the specified register. The register
|
||||
// must be a processor register, reachable with the "mov" instruction.
|
||||
func ReadRegister(name string) uintptr
|
||||
@@ -0,0 +1,13 @@
|
||||
.section .init
|
||||
.global _start
|
||||
.type _start,@function
|
||||
|
||||
_start:
|
||||
// Workaround for missing support of the la pseudo-instruction in Clang 8:
|
||||
// https://reviews.llvm.org/D55325
|
||||
lui sp, %hi(_stack_top)
|
||||
addi sp, sp, %lo(_stack_top)
|
||||
// see https://gnu-mcu-eclipse.github.io/arch/riscv/programmer/#the-gp-global-pointer-register
|
||||
lui gp, %hi(__global_pointer$)
|
||||
addi gp, gp, %lo(__global_pointer$)
|
||||
call main
|
||||
@@ -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();
|
||||
@@ -1,4 +1,4 @@
|
||||
// +build avr,arduino
|
||||
// +build arduino
|
||||
|
||||
package machine
|
||||
|
||||
|
||||
@@ -0,0 +1,147 @@
|
||||
// +build sam,atsamd21,arduino_nano33
|
||||
|
||||
// This contains the pin mappings for the Arduino Nano33 IoT board.
|
||||
//
|
||||
// For more information, see: https://store.arduino.cc/nano-33-iot
|
||||
//
|
||||
package machine
|
||||
|
||||
import "device/sam"
|
||||
|
||||
// used to reset into bootloader
|
||||
const RESET_MAGIC_VALUE = 0x07738135
|
||||
|
||||
// GPIO Pins
|
||||
const (
|
||||
RX0 Pin = PB23 // UART2 RX
|
||||
TX1 Pin = PB22 // UART2 TX
|
||||
|
||||
D2 Pin = PB10 // PWM available
|
||||
D3 Pin = PB11 // PWM available
|
||||
D4 Pin = PA07
|
||||
D5 Pin = PA05 // PWM available
|
||||
D6 Pin = PA04 // PWM available
|
||||
D7 Pin = PA06
|
||||
|
||||
D8 Pin = PA18
|
||||
D9 Pin = PA20 // PWM available
|
||||
D10 Pin = PA21 // PWM available
|
||||
D11 Pin = PA16 // PWM available
|
||||
D12 Pin = PA19 // PWM available
|
||||
|
||||
D13 Pin = PA17
|
||||
)
|
||||
|
||||
// Analog pins
|
||||
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],
|
||||
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]
|
||||
A7 Pin = PB03 // ADC/AIN[11]
|
||||
)
|
||||
|
||||
const (
|
||||
LED = D13
|
||||
)
|
||||
|
||||
// NINA-W102 Pins
|
||||
|
||||
const (
|
||||
NINA_MOSI Pin = PA12
|
||||
NINA_MISO Pin = PA13
|
||||
NINA_CS Pin = PA14
|
||||
NINA_SCK Pin = PA15
|
||||
NINA_GPIO0 Pin = PA27
|
||||
NINA_RESETN Pin = PA08
|
||||
NINA_ACK Pin = PA28
|
||||
)
|
||||
|
||||
// UART0 aka USBCDC pins
|
||||
const (
|
||||
USBCDC_DM_PIN Pin = PA24
|
||||
USBCDC_DP_PIN Pin = PA25
|
||||
)
|
||||
|
||||
// UART1 on the Arduino Nano 33 connects to the onboard NINA-W102 WiFi chip.
|
||||
var (
|
||||
UART1 = UART{Bus: sam.SERCOM5_USART,
|
||||
Buffer: NewRingBuffer(),
|
||||
Mode: PinSERCOMAlt,
|
||||
IRQVal: sam.IRQ_SERCOM5,
|
||||
}
|
||||
)
|
||||
|
||||
// UART1 pins
|
||||
const (
|
||||
UART_TX_PIN Pin = PA22
|
||||
UART_RX_PIN Pin = PA23
|
||||
)
|
||||
|
||||
//go:export SERCOM5_IRQHandler
|
||||
func handleUART1() {
|
||||
defaultUART1Handler()
|
||||
}
|
||||
|
||||
// UART2 on the Arduino Nano 33 connects to the normal TX/RX pins.
|
||||
var (
|
||||
UART2 = UART{Bus: sam.SERCOM3_USART,
|
||||
Buffer: NewRingBuffer(),
|
||||
Mode: PinSERCOMAlt,
|
||||
IRQVal: sam.IRQ_SERCOM3,
|
||||
}
|
||||
)
|
||||
|
||||
//go:export SERCOM3_IRQHandler
|
||||
func handleUART2() {
|
||||
// should reset IRQ
|
||||
UART2.Receive(byte((UART2.Bus.DATA.Get() & 0xFF)))
|
||||
UART2.Bus.INTFLAG.SetBits(sam.SERCOM_USART_INTFLAG_RXC)
|
||||
}
|
||||
|
||||
// I2C pins
|
||||
const (
|
||||
SDA_PIN Pin = A4 // SDA: SERCOM4/PAD[1]
|
||||
SCL_PIN Pin = A5 // SCL: SERCOM4/PAD[1]
|
||||
)
|
||||
|
||||
// I2C on the Arduino Nano 33.
|
||||
var (
|
||||
I2C0 = I2C{Bus: sam.SERCOM4_I2CM,
|
||||
SDA: SDA_PIN,
|
||||
SCL: SCL_PIN,
|
||||
PinMode: PinSERCOMAlt}
|
||||
)
|
||||
|
||||
// SPI pins
|
||||
const (
|
||||
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.SERCOM0_SPI,
|
||||
SCK: SPI0_SCK_PIN,
|
||||
MOSI: SPI0_MOSI_PIN,
|
||||
MISO: SPI0_MISO_PIN,
|
||||
DOpad: spiTXPad2SCK3,
|
||||
DIpad: sercomRXPad0,
|
||||
PinMode: PinSERCOM}
|
||||
)
|
||||
|
||||
// I2S pins
|
||||
const (
|
||||
I2S_SCK_PIN Pin = PA10
|
||||
I2S_SD_PIN Pin = PA08
|
||||
I2S_WS_PIN = NoPin // TODO: figure out what this is on Arduino Nano 33.
|
||||
)
|
||||
|
||||
// I2S on the Arduino Nano 33.
|
||||
var (
|
||||
I2S0 = I2S{Bus: sam.I2S}
|
||||
)
|
||||
@@ -1,7 +1,9 @@
|
||||
// +build stm32,bluepill
|
||||
// +build bluepill
|
||||
|
||||
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
|
||||
@@ -65,6 +68,20 @@ const (
|
||||
UART_RX_PIN = PB09 // PORTB
|
||||
)
|
||||
|
||||
// UART1 on the Circuit Playground Express.
|
||||
var (
|
||||
UART1 = UART{Bus: sam.SERCOM1_USART,
|
||||
Buffer: NewRingBuffer(),
|
||||
Mode: PinSERCOM,
|
||||
IRQVal: sam.IRQ_SERCOM1,
|
||||
}
|
||||
)
|
||||
|
||||
//go:export SERCOM1_IRQHandler
|
||||
func handleUART1() {
|
||||
defaultUART1Handler()
|
||||
}
|
||||
|
||||
// I2C pins
|
||||
const (
|
||||
SDA_PIN = PB02 // I2C0 external
|
||||
@@ -97,7 +114,13 @@ const (
|
||||
|
||||
// SPI on the Circuit Playground Express.
|
||||
var (
|
||||
SPI0 = SPI{Bus: sam.SERCOM3_SPI}
|
||||
SPI0 = SPI{Bus: sam.SERCOM3_SPI,
|
||||
SCK: SPI0_SCK_PIN,
|
||||
MOSI: SPI0_MOSI_PIN,
|
||||
MISO: SPI0_MISO_PIN,
|
||||
DOpad: spiTXPad2SCK3,
|
||||
DIpad: sercomRXPad0,
|
||||
PinMode: PinSERCOMAlt}
|
||||
)
|
||||
|
||||
// I2S pins
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// +build attiny85,digispark
|
||||
// +build digispark
|
||||
|
||||
package machine
|
||||
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
// +build hifive1b
|
||||
|
||||
package machine
|
||||
|
||||
const (
|
||||
P00 Pin = 0
|
||||
P01 Pin = 1
|
||||
P02 Pin = 2
|
||||
P03 Pin = 3
|
||||
P04 Pin = 4
|
||||
P05 Pin = 5
|
||||
P06 Pin = 6
|
||||
P07 Pin = 7
|
||||
P08 Pin = 8
|
||||
P09 Pin = 9
|
||||
P10 Pin = 10
|
||||
P11 Pin = 11
|
||||
P12 Pin = 12
|
||||
P13 Pin = 13
|
||||
P14 Pin = 14
|
||||
P15 Pin = 15
|
||||
P16 Pin = 16
|
||||
P17 Pin = 17
|
||||
P18 Pin = 18
|
||||
P19 Pin = 19
|
||||
P20 Pin = 20
|
||||
P21 Pin = 21
|
||||
P22 Pin = 22
|
||||
P23 Pin = 23
|
||||
P24 Pin = 24
|
||||
P25 Pin = 25
|
||||
P26 Pin = 26
|
||||
P27 Pin = 27
|
||||
P28 Pin = 28
|
||||
P29 Pin = 29
|
||||
P30 Pin = 30
|
||||
P31 Pin = 31
|
||||
)
|
||||
@@ -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
|
||||
@@ -48,6 +51,20 @@ const (
|
||||
UART_RX_PIN = D11
|
||||
)
|
||||
|
||||
// UART1 on the Feather M0.
|
||||
var (
|
||||
UART1 = UART{Bus: sam.SERCOM1_USART,
|
||||
Buffer: NewRingBuffer(),
|
||||
Mode: PinSERCOM,
|
||||
IRQVal: sam.IRQ_SERCOM1,
|
||||
}
|
||||
)
|
||||
|
||||
//go:export SERCOM1_IRQHandler
|
||||
func handleUART1() {
|
||||
defaultUART1Handler()
|
||||
}
|
||||
|
||||
// I2C pins
|
||||
const (
|
||||
SDA_PIN = PA22 // SDA: SERCOM3/PAD[0]
|
||||
@@ -71,7 +88,13 @@ const (
|
||||
|
||||
// SPI on the Feather M0.
|
||||
var (
|
||||
SPI0 = SPI{Bus: sam.SERCOM4_SPI}
|
||||
SPI0 = SPI{Bus: sam.SERCOM4_SPI,
|
||||
SCK: SPI0_SCK_PIN,
|
||||
MOSI: SPI0_MOSI_PIN,
|
||||
MISO: SPI0_MISO_PIN,
|
||||
DOpad: spiTXPad2SCK3,
|
||||
DIpad: sercomRXPad0,
|
||||
PinMode: PinSERCOMAlt}
|
||||
)
|
||||
|
||||
// I2S pins
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
// +build hifive1b
|
||||
|
||||
package machine
|
||||
|
||||
const (
|
||||
LED = LED1
|
||||
LED1 = LED_RED
|
||||
LED2 = LED_GREEN
|
||||
LED3 = LED_BLUE
|
||||
LED_RED = P22
|
||||
LED_GREEN = P19
|
||||
LED_BLUE = P21
|
||||
)
|
||||
|
||||
const (
|
||||
// TODO: figure out the pin numbers for these.
|
||||
UART_TX_PIN = NoPin
|
||||
UART_RX_PIN = NoPin
|
||||
)
|
||||
@@ -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 (
|
||||
@@ -48,6 +53,20 @@ const (
|
||||
UART_RX_PIN = D11
|
||||
)
|
||||
|
||||
// UART1 on the ItsyBitsy M0.
|
||||
var (
|
||||
UART1 = UART{Bus: sam.SERCOM1_USART,
|
||||
Buffer: NewRingBuffer(),
|
||||
Mode: PinSERCOM,
|
||||
IRQVal: sam.IRQ_SERCOM1,
|
||||
}
|
||||
)
|
||||
|
||||
//go:export SERCOM1_IRQHandler
|
||||
func handleUART1() {
|
||||
defaultUART1Handler()
|
||||
}
|
||||
|
||||
// I2C pins
|
||||
const (
|
||||
SDA_PIN = PA22 // SDA: SERCOM3/PAD[0]
|
||||
@@ -71,7 +90,32 @@ const (
|
||||
|
||||
// SPI on the ItsyBitsy M0.
|
||||
var (
|
||||
SPI0 = SPI{Bus: sam.SERCOM4_SPI}
|
||||
SPI0 = SPI{Bus: sam.SERCOM4_SPI,
|
||||
SCK: SPI0_SCK_PIN,
|
||||
MOSI: SPI0_MOSI_PIN,
|
||||
MISO: SPI0_MISO_PIN,
|
||||
DOpad: spiTXPad2SCK3,
|
||||
DIpad: sercomRXPad0,
|
||||
PinMode: PinSERCOMAlt}
|
||||
)
|
||||
|
||||
// "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,
|
||||
SCK: SPI1_SCK_PIN,
|
||||
MOSI: SPI1_MOSI_PIN,
|
||||
MISO: SPI1_MISO_PIN,
|
||||
DOpad: spiTXPad2SCK3,
|
||||
DIpad: sercomRXPad1,
|
||||
PinMode: PinSERCOMAlt}
|
||||
)
|
||||
|
||||
// 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_SPI,
|
||||
SCK: SPI0_SCK_PIN,
|
||||
MOSI: SPI0_MOSI_PIN,
|
||||
MISO: SPI0_MISO_PIN,
|
||||
DOpad: spiTXPad2SCK3,
|
||||
DIpad: sercomRXPad0}
|
||||
)
|
||||
@@ -1,4 +1,4 @@
|
||||
// +build nrf51,microbit
|
||||
// +build microbit
|
||||
|
||||
package machine
|
||||
|
||||
|
||||
@@ -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 nrf51,pca10031
|
||||
// +build pca10031
|
||||
|
||||
// pca10031 is a nrf51 based dongle, intended for use in wireless applications.
|
||||
//
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// +build nrf,pca10040
|
||||
// +build pca10040
|
||||
|
||||
package machine
|
||||
|
||||
|
||||
@@ -4,17 +4,24 @@ package machine
|
||||
|
||||
const HasLowFrequencyCrystal = true
|
||||
|
||||
// LEDs on the reel board
|
||||
// Pins on the reel board
|
||||
const (
|
||||
LED Pin = LED1
|
||||
LED1 Pin = LED_YELLOW
|
||||
LED2 Pin = LED_RED
|
||||
LED3 Pin = LED_GREEN
|
||||
LED4 Pin = LED_BLUE
|
||||
LED_RED Pin = 11
|
||||
LED_GREEN Pin = 12
|
||||
LED_BLUE Pin = 41
|
||||
LED_YELLOW Pin = 13
|
||||
LED Pin = LED1
|
||||
LED1 Pin = LED_YELLOW
|
||||
LED2 Pin = LED_RED
|
||||
LED3 Pin = LED_GREEN
|
||||
LED4 Pin = LED_BLUE
|
||||
LED_RED Pin = 11
|
||||
LED_GREEN Pin = 12
|
||||
LED_BLUE Pin = 41
|
||||
LED_YELLOW Pin = 13
|
||||
EPD_BUSY_PIN Pin = 14
|
||||
EPD_RESET_PIN Pin = 15
|
||||
EPD_DC_PIN Pin = 16
|
||||
EPD_CS_PIN Pin = 17
|
||||
EPD_SCK_PIN Pin = 19
|
||||
EPD_MOSI_PIN Pin = 20
|
||||
POWER_SUPPLY_PIN Pin = 32
|
||||
)
|
||||
|
||||
// User "a" button on the reel board
|
||||
@@ -40,3 +47,15 @@ const (
|
||||
SPI0_MOSI_PIN Pin = 45
|
||||
SPI0_MISO_PIN Pin = 46
|
||||
)
|
||||
|
||||
// PowerSupplyActive enables the supply voltages for nRF52840 and peripherals (true) or only for nRF52840 (false)
|
||||
// This controls the TPS610981 boost converter. You must turn the power supply active in order to use the EPD and
|
||||
// other onboard peripherals.
|
||||
func PowerSupplyActive(active bool) {
|
||||
POWER_SUPPLY_PIN.Configure(PinConfig{Mode: PinOutput})
|
||||
if active {
|
||||
POWER_SUPPLY_PIN.High()
|
||||
} else {
|
||||
POWER_SUPPLY_PIN.Low()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
// +build bluepill nucleof103rb stm32f4disco
|
||||
|
||||
package machine
|
||||
|
||||
// Peripheral abstraction layer for the stm32.
|
||||
|
||||
const (
|
||||
portA Pin = iota * 16
|
||||
portB
|
||||
portC
|
||||
portD
|
||||
portE
|
||||
portF
|
||||
portG
|
||||
portH
|
||||
)
|
||||
@@ -1,4 +1,4 @@
|
||||
// +build stm32,stm32f4disco
|
||||
// +build 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
|
||||
@@ -39,6 +42,20 @@ const (
|
||||
UART_RX_PIN = D3
|
||||
)
|
||||
|
||||
// UART1 on the Trinket M0.
|
||||
var (
|
||||
UART1 = UART{Bus: sam.SERCOM1_USART,
|
||||
Buffer: NewRingBuffer(),
|
||||
Mode: PinSERCOM,
|
||||
IRQVal: sam.IRQ_SERCOM1,
|
||||
}
|
||||
)
|
||||
|
||||
//go:export SERCOM1_IRQHandler
|
||||
func handleUART1() {
|
||||
defaultUART1Handler()
|
||||
}
|
||||
|
||||
// SPI pins
|
||||
const (
|
||||
SPI0_SCK_PIN = D3
|
||||
@@ -48,7 +65,13 @@ const (
|
||||
|
||||
// SPI on the Trinket M0.
|
||||
var (
|
||||
SPI0 = SPI{Bus: sam.SERCOM0_SPI}
|
||||
SPI0 = SPI{Bus: sam.SERCOM0_SPI,
|
||||
SCK: SPI0_SCK_PIN,
|
||||
MOSI: SPI0_MOSI_PIN,
|
||||
MISO: SPI0_MISO_PIN,
|
||||
DOpad: spiTXPad2SCK3,
|
||||
DIpad: sercomRXPad0,
|
||||
PinMode: PinSERCOMAlt}
|
||||
)
|
||||
|
||||
// I2C pins
|
||||
|
||||
+12
-11
@@ -1,9 +1,10 @@
|
||||
package machine
|
||||
|
||||
const bufferSize = 128
|
||||
import (
|
||||
"runtime/volatile"
|
||||
)
|
||||
|
||||
//go:volatile
|
||||
type volatileByte byte
|
||||
const bufferSize = 128
|
||||
|
||||
// RingBuffer is ring buffer implementation inspired by post at
|
||||
// https://www.embeddedrelated.com/showthread/comp.arch.embedded/77084-1.php
|
||||
@@ -12,9 +13,9 @@ type volatileByte byte
|
||||
// members of a struct are not compiled correctly by TinyGo.
|
||||
// See https://github.com/tinygo-org/tinygo/issues/151 for details.
|
||||
type RingBuffer struct {
|
||||
rxbuffer [bufferSize]volatileByte
|
||||
head volatileByte
|
||||
tail volatileByte
|
||||
rxbuffer [bufferSize]volatile.Register8
|
||||
head volatile.Register8
|
||||
tail volatile.Register8
|
||||
}
|
||||
|
||||
// NewRingBuffer returns a new ring buffer.
|
||||
@@ -24,15 +25,15 @@ func NewRingBuffer() *RingBuffer {
|
||||
|
||||
// Used returns how many bytes in buffer have been used.
|
||||
func (rb *RingBuffer) Used() uint8 {
|
||||
return uint8(rb.head - rb.tail)
|
||||
return uint8(rb.head.Get() - rb.tail.Get())
|
||||
}
|
||||
|
||||
// Put stores a byte in the buffer. If the buffer is already
|
||||
// full, the method will return false.
|
||||
func (rb *RingBuffer) Put(val byte) bool {
|
||||
if rb.Used() != bufferSize {
|
||||
rb.head++
|
||||
rb.rxbuffer[rb.head%bufferSize] = volatileByte(val)
|
||||
rb.head.Set(rb.head.Get() + 1)
|
||||
rb.rxbuffer[rb.head.Get()%bufferSize].Set(val)
|
||||
return true
|
||||
}
|
||||
return false
|
||||
@@ -42,8 +43,8 @@ func (rb *RingBuffer) Put(val byte) bool {
|
||||
// the method will return a false as the second value.
|
||||
func (rb *RingBuffer) Get() (byte, bool) {
|
||||
if rb.Used() != 0 {
|
||||
rb.tail++
|
||||
return byte(rb.rxbuffer[rb.tail%bufferSize]), true
|
||||
rb.tail.Set(rb.tail.Get() + 1)
|
||||
return rb.rxbuffer[rb.tail.Get()%bufferSize].Get(), true
|
||||
}
|
||||
return 0, false
|
||||
}
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
// +build avr nrf sam stm32f103xx
|
||||
// +build avr nrf sam stm32,!stm32f4disco
|
||||
|
||||
package machine
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@ package machine
|
||||
|
||||
import (
|
||||
"device/avr"
|
||||
"runtime/volatile"
|
||||
)
|
||||
|
||||
// Configure sets the pin to input or output.
|
||||
@@ -34,7 +35,7 @@ func (p Pin) Get() bool {
|
||||
}
|
||||
}
|
||||
|
||||
func (p Pin) getPortMask() (*avr.Register8, uint8) {
|
||||
func (p Pin) getPortMask() (*volatile.Register8, uint8) {
|
||||
if p < 8 {
|
||||
return avr.PORTD, 1 << uint8(p)
|
||||
} else {
|
||||
|
||||
+124
-75
@@ -192,6 +192,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,14 +255,13 @@ func waitADCSync() {
|
||||
type UART struct {
|
||||
Buffer *RingBuffer
|
||||
Bus *sam.SERCOM_USART_Type
|
||||
Mode PinMode
|
||||
IRQVal uint32
|
||||
}
|
||||
|
||||
var (
|
||||
// UART0 is actually a USB CDC interface.
|
||||
UART0 = USBCDC{Buffer: NewRingBuffer()}
|
||||
|
||||
// The first hardware serial port on the SAMD21. Uses the SERCOM0 interface.
|
||||
UART1 = UART{Bus: sam.SERCOM1_USART, Buffer: NewRingBuffer()}
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -300,6 +304,8 @@ func (uart UART) Configure(config UARTConfig) {
|
||||
txpad = sercomTXPad2
|
||||
case PA16:
|
||||
txpad = sercomTXPad0
|
||||
case PA22:
|
||||
txpad = sercomTXPad0
|
||||
default:
|
||||
panic("Invalid TX pin for UART")
|
||||
}
|
||||
@@ -315,13 +321,15 @@ func (uart UART) Configure(config UARTConfig) {
|
||||
rxpad = sercomRXPad3
|
||||
case PA17:
|
||||
rxpad = sercomRXPad1
|
||||
case PA23:
|
||||
rxpad = sercomRXPad1
|
||||
default:
|
||||
panic("Invalid RX pin for UART")
|
||||
}
|
||||
|
||||
// configure pins
|
||||
config.TX.Configure(PinConfig{Mode: PinSERCOM})
|
||||
config.RX.Configure(PinConfig{Mode: PinSERCOM})
|
||||
config.TX.Configure(PinConfig{Mode: uart.Mode})
|
||||
config.RX.Configure(PinConfig{Mode: uart.Mode})
|
||||
|
||||
// reset SERCOM0
|
||||
uart.Bus.CTRLA.SetBits(sam.SERCOM_USART_CTRLA_SWRST)
|
||||
@@ -372,13 +380,7 @@ func (uart UART) Configure(config UARTConfig) {
|
||||
uart.Bus.INTENSET.Set(sam.SERCOM_USART_INTENSET_RXC)
|
||||
|
||||
// Enable RX IRQ.
|
||||
if config.TX == PA10 {
|
||||
// UART0
|
||||
arm.EnableIRQ(sam.IRQ_SERCOM0)
|
||||
} else {
|
||||
// UART1 which is the normal default, since UART0 is used for USBCDC.
|
||||
arm.EnableIRQ(sam.IRQ_SERCOM1)
|
||||
}
|
||||
arm.EnableIRQ(uart.IRQVal)
|
||||
}
|
||||
|
||||
// SetBaudRate sets the communication speed for the UART.
|
||||
@@ -404,8 +406,8 @@ func (uart UART) WriteByte(c byte) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
//go:export SERCOM1_IRQHandler
|
||||
func handleUART1() {
|
||||
// defaultUART1Handler handles the UART1 IRQ.
|
||||
func defaultUART1Handler() {
|
||||
// should reset IRQ
|
||||
UART1.Receive(byte((UART1.Bus.DATA.Get() & 0xFF)))
|
||||
UART1.Bus.INTFLAG.SetBits(sam.SERCOM_USART_INTFLAG_RXC)
|
||||
@@ -874,7 +876,13 @@ func waitForSync() {
|
||||
|
||||
// SPI
|
||||
type SPI struct {
|
||||
Bus *sam.SERCOM_SPI_Type
|
||||
Bus *sam.SERCOM_SPI_Type
|
||||
SCK Pin
|
||||
MOSI Pin
|
||||
MISO Pin
|
||||
DOpad int
|
||||
DIpad int
|
||||
PinMode PinMode
|
||||
}
|
||||
|
||||
// SPIConfig is used to store config info for SPI.
|
||||
@@ -889,12 +897,14 @@ 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
|
||||
config.SCK = spi.SCK
|
||||
config.MOSI = spi.MOSI
|
||||
config.MISO = spi.MISO
|
||||
|
||||
doPad := spiTXPad2SCK3
|
||||
diPad := sercomRXPad0
|
||||
doPad := spi.DOpad
|
||||
diPad := spi.DIpad
|
||||
|
||||
pinMode := spi.PinMode
|
||||
|
||||
// set default frequency
|
||||
if config.Frequency == 0 {
|
||||
@@ -907,9 +917,9 @@ func (spi SPI) Configure(config SPIConfig) {
|
||||
}
|
||||
|
||||
// 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: pinMode})
|
||||
config.MOSI.Configure(PinConfig{Mode: pinMode})
|
||||
config.MISO.Configure(PinConfig{Mode: pinMode})
|
||||
|
||||
// reset SERCOM
|
||||
spi.Bus.CTRLA.SetBits(sam.SERCOM_SPI_CTRLA_SWRST)
|
||||
@@ -1182,23 +1192,35 @@ func (usbcdc USBCDC) WriteByte(c byte) error {
|
||||
usbEndpointDescriptors[usb_CDC_ENDPOINT_IN].DeviceDescBank[1].PCKSIZE.ClearBits(usb_DEVICE_PCKSIZE_MULTI_PACKET_SIZE_Mask << usb_DEVICE_PCKSIZE_MULTI_PACKET_SIZE_Pos)
|
||||
|
||||
// set count of bytes to be sent
|
||||
usbEndpointDescriptors[usb_CDC_ENDPOINT_IN].DeviceDescBank[1].PCKSIZE.SetBits((1&usb_DEVICE_PCKSIZE_BYTE_COUNT_Mask)<<usb_DEVICE_PCKSIZE_BYTE_COUNT_Pos |
|
||||
(epPacketSize(64) << usb_DEVICE_PCKSIZE_SIZE_Pos))
|
||||
usbEndpointDescriptors[usb_CDC_ENDPOINT_IN].DeviceDescBank[1].PCKSIZE.SetBits((1 & usb_DEVICE_PCKSIZE_BYTE_COUNT_Mask) << usb_DEVICE_PCKSIZE_BYTE_COUNT_Pos)
|
||||
|
||||
// ack transfer complete flag
|
||||
// clear transfer complete flag
|
||||
setEPINTFLAG(usb_CDC_ENDPOINT_IN, sam.USB_DEVICE_EPINTFLAG_TRCPT1)
|
||||
|
||||
// send data by setting bank ready
|
||||
setEPSTATUSSET(usb_CDC_ENDPOINT_IN, sam.USB_DEVICE_EPSTATUSSET_BK1RDY)
|
||||
|
||||
// wait for transfer to complete
|
||||
timeout := 3000
|
||||
for (getEPINTFLAG(usb_CDC_ENDPOINT_IN) & sam.USB_DEVICE_EPINTFLAG_TRCPT1) == 0 {
|
||||
timeout--
|
||||
if timeout == 0 {
|
||||
return errors.New("USBCDC write byte timeout")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (usbcdc USBCDC) DTR() bool {
|
||||
return (usbLineInfo.lineState & usb_CDC_LINESTATE_DTR) > 0
|
||||
}
|
||||
|
||||
func (usbcdc USBCDC) RTS() bool {
|
||||
return (usbLineInfo.lineState & usb_CDC_LINESTATE_RTS) > 0
|
||||
}
|
||||
|
||||
const (
|
||||
// these are SAMD21 specific.
|
||||
usb_DEVICE_PCKSIZE_BYTE_COUNT_Pos = 0
|
||||
@@ -1346,6 +1368,7 @@ func handleUSB() {
|
||||
|
||||
// Clear the Bank 0 ready flag on Control OUT
|
||||
setEPSTATUSCLR(0, sam.USB_DEVICE_EPSTATUSCLR_BK0RDY)
|
||||
usbEndpointDescriptors[0].DeviceDescBank[0].PCKSIZE.ClearBits(usb_DEVICE_PCKSIZE_BYTE_COUNT_Mask << usb_DEVICE_PCKSIZE_BYTE_COUNT_Pos)
|
||||
|
||||
ok := false
|
||||
if (setup.bmRequestType & usb_REQUEST_TYPE) == usb_REQUEST_STANDARD {
|
||||
@@ -1375,19 +1398,20 @@ func handleUSB() {
|
||||
}
|
||||
}
|
||||
|
||||
// Now the actual transfer handlers
|
||||
eptInts := sam.USB_DEVICE.EPINTSMRY.Get() & 0xFE // Remove endpoint number 0 (setup)
|
||||
// Now the actual transfer handlers, ignore endpoint number 0 (setup)
|
||||
var i uint32
|
||||
for i = 1; i < uint32(len(endPoints)); i++ {
|
||||
// Check if endpoint has a pending interrupt
|
||||
if eptInts&(1<<i) > 0 {
|
||||
// yes, so handle flags
|
||||
epFlags := getEPINTFLAG(i)
|
||||
setEPINTFLAG(i, epFlags)
|
||||
|
||||
// Endpoint Transfer Complete Interrupt
|
||||
if (epFlags & sam.USB_DEVICE_EPINTFLAG_TRCPT0) > 0 {
|
||||
epFlags := getEPINTFLAG(i)
|
||||
if (epFlags&sam.USB_DEVICE_EPINTFLAG_TRCPT0) > 0 ||
|
||||
(epFlags&sam.USB_DEVICE_EPINTFLAG_TRCPT1) > 0 {
|
||||
switch i {
|
||||
case usb_CDC_ENDPOINT_OUT:
|
||||
handleEndpoint(i)
|
||||
setEPINTFLAG(i, epFlags)
|
||||
case usb_CDC_ENDPOINT_IN, usb_CDC_ENDPOINT_ACM:
|
||||
setEPSTATUSCLR(i, sam.USB_DEVICE_EPSTATUSCLR_BK1RDY)
|
||||
setEPINTFLAG(i, sam.USB_DEVICE_EPINTFLAG_TRCPT1)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1403,7 +1427,7 @@ func initEndpoint(ep, config uint32) {
|
||||
usbEndpointDescriptors[ep].DeviceDescBank[1].ADDR.Set(uint32(uintptr(unsafe.Pointer(&udd_ep_in_cache_buffer[ep]))))
|
||||
|
||||
// set endpoint type
|
||||
setEPCFG(ep, getEPCFG(ep)|((usb_ENDPOINT_TYPE_INTERRUPT+1)<<sam.USB_DEVICE_EPCFG_EPTYPE1_Pos))
|
||||
setEPCFG(ep, ((usb_ENDPOINT_TYPE_INTERRUPT + 1) << sam.USB_DEVICE_EPCFG_EPTYPE1_Pos))
|
||||
|
||||
case usb_ENDPOINT_TYPE_BULK | usbEndpointOut:
|
||||
// set packet size
|
||||
@@ -1413,11 +1437,14 @@ func initEndpoint(ep, config uint32) {
|
||||
usbEndpointDescriptors[ep].DeviceDescBank[0].ADDR.Set(uint32(uintptr(unsafe.Pointer(&udd_ep_out_cache_buffer[ep]))))
|
||||
|
||||
// set endpoint type
|
||||
setEPCFG(ep, getEPCFG(ep)|((usb_ENDPOINT_TYPE_BULK+1)<<sam.USB_DEVICE_EPCFG_EPTYPE0_Pos))
|
||||
setEPCFG(ep, ((usb_ENDPOINT_TYPE_BULK + 1) << sam.USB_DEVICE_EPCFG_EPTYPE0_Pos))
|
||||
|
||||
// ack the current transfer
|
||||
// receive interrupts when current transfer complete
|
||||
setEPINTENSET(ep, sam.USB_DEVICE_EPINTENSET_TRCPT0)
|
||||
|
||||
// set byte count to zero, we have not received anything yet
|
||||
usbEndpointDescriptors[ep].DeviceDescBank[0].PCKSIZE.ClearBits(usb_DEVICE_PCKSIZE_BYTE_COUNT_Mask << usb_DEVICE_PCKSIZE_BYTE_COUNT_Pos)
|
||||
|
||||
// ready for next transfer
|
||||
setEPSTATUSCLR(ep, sam.USB_DEVICE_EPSTATUSCLR_BK0RDY)
|
||||
|
||||
@@ -1432,7 +1459,7 @@ func initEndpoint(ep, config uint32) {
|
||||
usbEndpointDescriptors[ep].DeviceDescBank[1].ADDR.Set(uint32(uintptr(unsafe.Pointer(&udd_ep_in_cache_buffer[ep]))))
|
||||
|
||||
// set endpoint type
|
||||
setEPCFG(ep, getEPCFG(ep)|((usb_ENDPOINT_TYPE_BULK+1)<<sam.USB_DEVICE_EPCFG_EPTYPE1_Pos))
|
||||
setEPCFG(ep, ((usb_ENDPOINT_TYPE_BULK + 1) << sam.USB_DEVICE_EPCFG_EPTYPE1_Pos))
|
||||
|
||||
// NAK on endpoint IN, the bank is not yet filled in.
|
||||
setEPSTATUSCLR(ep, sam.USB_DEVICE_EPSTATUSCLR_BK1RDY)
|
||||
@@ -1515,7 +1542,12 @@ func handleStandardSetup(setup usbSetup) bool {
|
||||
setEPSTATUSSET(0, sam.USB_DEVICE_EPSTATUSSET_BK1RDY)
|
||||
|
||||
// wait for transfer to complete
|
||||
timeout := 3000
|
||||
for (getEPINTFLAG(0) & sam.USB_DEVICE_EPINTFLAG_TRCPT1) == 0 {
|
||||
timeout--
|
||||
if timeout == 0 {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
// last, set the device address to that requested by host
|
||||
@@ -1601,17 +1633,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
|
||||
}
|
||||
@@ -1632,43 +1666,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.
|
||||
for (getEPSTATUS(ep) & sam.USB_DEVICE_EPSTATUS_BK0RDY) == 0 {
|
||||
timeout := 3000
|
||||
for (getEPSTATUS(0) & sam.USB_DEVICE_EPSTATUS_BK0RDY) == 0 {
|
||||
timeout--
|
||||
if timeout == 0 {
|
||||
return []byte{}
|
||||
}
|
||||
}
|
||||
|
||||
// Wait until OUT transfer is completed.
|
||||
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 []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
|
||||
@@ -1764,9 +1796,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,
|
||||
@@ -1776,8 +1808,8 @@ func sendConfiguration(setup usbSetup) {
|
||||
callManagement,
|
||||
cifin,
|
||||
dif,
|
||||
in,
|
||||
out)
|
||||
out,
|
||||
in)
|
||||
|
||||
sz := uint16(configDescriptorSize + cdcSize)
|
||||
config := NewConfigDescriptor(sz, 2)
|
||||
@@ -1800,9 +1832,14 @@ func handleEndpoint(ep uint32) {
|
||||
UART0.Receive(byte((udd_ep_out_cache_buffer[ep][i] & 0xFF)))
|
||||
}
|
||||
|
||||
// set byte count to zero
|
||||
usbEndpointDescriptors[ep].DeviceDescBank[0].PCKSIZE.ClearBits(usb_DEVICE_PCKSIZE_BYTE_COUNT_Mask << usb_DEVICE_PCKSIZE_BYTE_COUNT_Pos)
|
||||
|
||||
// set multi packet size to 64
|
||||
usbEndpointDescriptors[ep].DeviceDescBank[0].PCKSIZE.SetBits(64 << usb_DEVICE_PCKSIZE_MULTI_PACKET_SIZE_Pos)
|
||||
|
||||
// set ready for next data
|
||||
setEPSTATUSCLR(ep, sam.USB_DEVICE_EPSTATUSCLR_BK0RDY)
|
||||
|
||||
}
|
||||
|
||||
func sendZlp(ep uint32) {
|
||||
@@ -2038,3 +2075,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,569 @@
|
||||
// +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.OUTSET0.Reg, 1 << uint8(p)
|
||||
} else {
|
||||
return &sam.PORT.OUTSET1.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.OUTCLR0.Reg, 1 << uint8(p)
|
||||
} else {
|
||||
return &sam.PORT.OUTCLR1.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.OUTSET0.Set(1 << uint8(p))
|
||||
} else {
|
||||
sam.PORT.OUTCLR0.Set(1 << uint8(p))
|
||||
}
|
||||
} else {
|
||||
if high {
|
||||
sam.PORT.OUTSET1.Set(1 << uint8(p-32))
|
||||
} else {
|
||||
sam.PORT.OUTCLR1.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.IN0.Get()>>uint8(p))&1 > 0
|
||||
} else {
|
||||
return (sam.PORT.IN1.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.DIRSET0.Set(1 << uint8(p))
|
||||
// output is also set to input enable so pin can read back its own value
|
||||
p.setPinCfg(sam.PORT_PINCFG0_INEN)
|
||||
} else {
|
||||
sam.PORT.DIRSET1.Set(1 << uint8(p-32))
|
||||
// output is also set to input enable so pin can read back its own value
|
||||
p.setPinCfg(sam.PORT_PINCFG0_INEN)
|
||||
}
|
||||
|
||||
case PinInput:
|
||||
if p < 32 {
|
||||
sam.PORT.DIRCLR0.Set(1 << uint8(p))
|
||||
p.setPinCfg(sam.PORT_PINCFG0_INEN)
|
||||
} else {
|
||||
sam.PORT.DIRCLR1.Set(1<<uint8(p) - 32)
|
||||
p.setPinCfg(sam.PORT_PINCFG0_INEN)
|
||||
}
|
||||
|
||||
case PinInputPulldown:
|
||||
if p < 32 {
|
||||
sam.PORT.DIRCLR0.Set(1 << uint8(p))
|
||||
sam.PORT.OUTCLR0.Set(1 << uint8(p))
|
||||
p.setPinCfg(sam.PORT_PINCFG0_INEN | sam.PORT_PINCFG0_PULLEN)
|
||||
} else {
|
||||
sam.PORT.DIRCLR1.Set(1<<uint8(p) - 32)
|
||||
sam.PORT.OUTCLR1.Set(1<<uint8(p) - 32)
|
||||
p.setPinCfg(sam.PORT_PINCFG0_INEN | sam.PORT_PINCFG0_PULLEN)
|
||||
}
|
||||
|
||||
case PinInputPullup:
|
||||
if p < 32 {
|
||||
sam.PORT.DIRCLR0.Set(1 << uint8(p))
|
||||
sam.PORT.OUTSET0.Set(1 << uint8(p))
|
||||
p.setPinCfg(sam.PORT_PINCFG0_INEN | sam.PORT_PINCFG0_PULLEN)
|
||||
} else {
|
||||
sam.PORT.DIRCLR1.Set(1<<uint8(p) - 32)
|
||||
sam.PORT.OUTSET1.Set(1<<uint8(p) - 32)
|
||||
p.setPinCfg(sam.PORT_PINCFG0_INEN | sam.PORT_PINCFG0_PULLEN)
|
||||
}
|
||||
|
||||
case PinSERCOM:
|
||||
if p&1 > 0 {
|
||||
// odd pin, so save the even pins
|
||||
val := p.getPMux() & sam.PORT_PMUX0_PMUXE_Msk
|
||||
p.setPMux(val | (uint8(PinSERCOM) << sam.PORT_PMUX0_PMUXO_Pos))
|
||||
} else {
|
||||
// even pin, so save the odd pins
|
||||
val := p.getPMux() & sam.PORT_PMUX0_PMUXO_Msk
|
||||
p.setPMux(val | (uint8(PinSERCOM) << sam.PORT_PMUX0_PMUXE_Pos))
|
||||
}
|
||||
// enable port config
|
||||
p.setPinCfg(sam.PORT_PINCFG0_PMUXEN | sam.PORT_PINCFG0_DRVSTR | sam.PORT_PINCFG0_INEN)
|
||||
|
||||
case PinSERCOMAlt:
|
||||
if p&1 > 0 {
|
||||
// odd pin, so save the even pins
|
||||
val := p.getPMux() & sam.PORT_PMUX0_PMUXE_Msk
|
||||
p.setPMux(val | (uint8(PinSERCOMAlt) << sam.PORT_PMUX0_PMUXO_Pos))
|
||||
} else {
|
||||
// even pin, so save the odd pins
|
||||
val := p.getPMux() & sam.PORT_PMUX0_PMUXO_Msk
|
||||
p.setPMux(val | (uint8(PinSERCOMAlt) << sam.PORT_PMUX0_PMUXE_Pos))
|
||||
}
|
||||
// enable port config
|
||||
p.setPinCfg(sam.PORT_PINCFG0_PMUXEN | sam.PORT_PINCFG0_DRVSTR)
|
||||
|
||||
case PinCom:
|
||||
if p&1 > 0 {
|
||||
// odd pin, so save the even pins
|
||||
val := p.getPMux() & sam.PORT_PMUX0_PMUXE_Msk
|
||||
p.setPMux(val | (uint8(PinCom) << sam.PORT_PMUX0_PMUXO_Pos))
|
||||
} else {
|
||||
// even pin, so save the odd pins
|
||||
val := p.getPMux() & sam.PORT_PMUX0_PMUXO_Msk
|
||||
p.setPMux(val | (uint8(PinCom) << sam.PORT_PMUX0_PMUXE_Pos))
|
||||
}
|
||||
// enable port config
|
||||
p.setPinCfg(sam.PORT_PINCFG0_PMUXEN)
|
||||
case PinAnalog:
|
||||
if p&1 > 0 {
|
||||
// odd pin, so save the even pins
|
||||
val := p.getPMux() & sam.PORT_PMUX0_PMUXE_Msk
|
||||
p.setPMux(val | (uint8(PinAnalog) << sam.PORT_PMUX0_PMUXO_Pos))
|
||||
} else {
|
||||
// even pin, so save the odd pins
|
||||
val := p.getPMux() & sam.PORT_PMUX0_PMUXO_Msk
|
||||
p.setPMux(val | (uint8(PinAnalog) << sam.PORT_PMUX0_PMUXE_Pos))
|
||||
}
|
||||
// enable port config
|
||||
p.setPinCfg(sam.PORT_PINCFG0_PMUXEN | sam.PORT_PINCFG0_DRVSTR)
|
||||
}
|
||||
}
|
||||
|
||||
// getPMux returns the value for the correct PMUX register for this pin.
|
||||
func (p Pin) getPMux() uint8 {
|
||||
switch uint8(p) >> 1 {
|
||||
case 0:
|
||||
return sam.PORT.PMUX0_0.Get()
|
||||
case 1:
|
||||
return sam.PORT.PMUX0_1.Get()
|
||||
case 2:
|
||||
return sam.PORT.PMUX0_2.Get()
|
||||
case 3:
|
||||
return sam.PORT.PMUX0_3.Get()
|
||||
case 4:
|
||||
return sam.PORT.PMUX0_4.Get()
|
||||
case 5:
|
||||
return sam.PORT.PMUX0_5.Get()
|
||||
case 6:
|
||||
return sam.PORT.PMUX0_6.Get()
|
||||
case 7:
|
||||
return sam.PORT.PMUX0_7.Get()
|
||||
case 8:
|
||||
return sam.PORT.PMUX0_8.Get()
|
||||
case 9:
|
||||
return sam.PORT.PMUX0_9.Get()
|
||||
case 10:
|
||||
return sam.PORT.PMUX0_10.Get()
|
||||
case 11:
|
||||
return sam.PORT.PMUX0_11.Get()
|
||||
case 12:
|
||||
return sam.PORT.PMUX0_12.Get()
|
||||
case 13:
|
||||
return sam.PORT.PMUX0_13.Get()
|
||||
case 14:
|
||||
return sam.PORT.PMUX0_14.Get()
|
||||
case 15:
|
||||
return sam.PORT.PMUX0_15.Get()
|
||||
case 16:
|
||||
return uint8(sam.PORT.PMUX1_0.Get()>>0) & 0xff
|
||||
case 17:
|
||||
return uint8(sam.PORT.PMUX1_0.Get()>>8) & 0xff
|
||||
case 18:
|
||||
return uint8(sam.PORT.PMUX1_0.Get()>>16) & 0xff
|
||||
case 19:
|
||||
return uint8(sam.PORT.PMUX1_0.Get()>>24) & 0xff
|
||||
case 20:
|
||||
return uint8(sam.PORT.PMUX1_4.Get()>>0) & 0xff
|
||||
case 21:
|
||||
return uint8(sam.PORT.PMUX1_4.Get()>>8) & 0xff
|
||||
case 22:
|
||||
return uint8(sam.PORT.PMUX1_4.Get()>>16) & 0xff
|
||||
case 23:
|
||||
return uint8(sam.PORT.PMUX1_4.Get()>>24) & 0xff
|
||||
case 24:
|
||||
return uint8(sam.PORT.PMUX1_8.Get()>>0) & 0xff
|
||||
case 25:
|
||||
return uint8(sam.PORT.PMUX1_8.Get()>>8) & 0xff
|
||||
case 26:
|
||||
return uint8(sam.PORT.PMUX1_8.Get()>>16) & 0xff
|
||||
case 27:
|
||||
return uint8(sam.PORT.PMUX1_8.Get()>>24) & 0xff
|
||||
case 28:
|
||||
return uint8(sam.PORT.PMUX1_12.Get()>>0) & 0xff
|
||||
case 29:
|
||||
return uint8(sam.PORT.PMUX1_12.Get()>>8) & 0xff
|
||||
case 30:
|
||||
return uint8(sam.PORT.PMUX1_12.Get()>>16) & 0xff
|
||||
case 31:
|
||||
return uint8(sam.PORT.PMUX1_12.Get()>>24) & 0xff
|
||||
default:
|
||||
return 0
|
||||
}
|
||||
}
|
||||
|
||||
// setPMux sets the value for the correct PMUX register for this pin.
|
||||
func (p Pin) setPMux(val uint8) {
|
||||
switch uint8(p) >> 1 {
|
||||
case 0:
|
||||
sam.PORT.PMUX0_0.Set(val)
|
||||
case 1:
|
||||
sam.PORT.PMUX0_1.Set(val)
|
||||
case 2:
|
||||
sam.PORT.PMUX0_2.Set(val)
|
||||
case 3:
|
||||
sam.PORT.PMUX0_3.Set(val)
|
||||
case 4:
|
||||
sam.PORT.PMUX0_4.Set(val)
|
||||
case 5:
|
||||
sam.PORT.PMUX0_5.Set(val)
|
||||
case 6:
|
||||
sam.PORT.PMUX0_6.Set(val)
|
||||
case 7:
|
||||
sam.PORT.PMUX0_7.Set(val)
|
||||
case 8:
|
||||
sam.PORT.PMUX0_8.Set(val)
|
||||
case 9:
|
||||
sam.PORT.PMUX0_9.Set(val)
|
||||
case 10:
|
||||
sam.PORT.PMUX0_10.Set(val)
|
||||
case 11:
|
||||
sam.PORT.PMUX0_11.Set(val)
|
||||
case 12:
|
||||
sam.PORT.PMUX0_12.Set(val)
|
||||
case 13:
|
||||
sam.PORT.PMUX0_13.Set(val)
|
||||
case 14:
|
||||
sam.PORT.PMUX0_14.Set(val)
|
||||
case 15:
|
||||
sam.PORT.PMUX0_15.Set(val)
|
||||
case 16:
|
||||
sam.PORT.PMUX1_0.Set(sam.PORT.PMUX1_0.Get()&^(0xff<<0) | (uint32(val) << 0))
|
||||
case 17:
|
||||
sam.PORT.PMUX1_0.Set(sam.PORT.PMUX1_0.Get()&^(0xff<<8) | (uint32(val) << 8))
|
||||
case 18:
|
||||
sam.PORT.PMUX1_0.Set(sam.PORT.PMUX1_0.Get()&^(0xff<<16) | (uint32(val) << 16))
|
||||
case 19:
|
||||
sam.PORT.PMUX1_0.Set(sam.PORT.PMUX1_0.Get()&^(0xff<<24) | (uint32(val) << 24))
|
||||
case 20:
|
||||
sam.PORT.PMUX1_4.Set(sam.PORT.PMUX1_4.Get()&^(0xff<<0) | (uint32(val) << 0))
|
||||
case 21:
|
||||
sam.PORT.PMUX1_4.Set(sam.PORT.PMUX1_4.Get()&^(0xff<<8) | (uint32(val) << 8))
|
||||
case 22:
|
||||
sam.PORT.PMUX1_4.Set(sam.PORT.PMUX1_4.Get()&^(0xff<<16) | (uint32(val) << 16))
|
||||
case 23:
|
||||
sam.PORT.PMUX1_4.Set(sam.PORT.PMUX1_4.Get()&^(0xff<<24) | (uint32(val) << 24))
|
||||
case 24:
|
||||
sam.PORT.PMUX1_8.Set(sam.PORT.PMUX1_8.Get()&^(0xff<<0) | (uint32(val) << 0))
|
||||
case 25:
|
||||
sam.PORT.PMUX1_8.Set(sam.PORT.PMUX1_8.Get()&^(0xff<<8) | (uint32(val) << 8))
|
||||
case 26:
|
||||
sam.PORT.PMUX1_8.Set(sam.PORT.PMUX1_8.Get()&^(0xff<<16) | (uint32(val) << 16))
|
||||
case 27:
|
||||
sam.PORT.PMUX1_8.Set(sam.PORT.PMUX1_8.Get()&^(0xff<<24) | (uint32(val) << 24))
|
||||
case 28:
|
||||
sam.PORT.PMUX1_12.Set(sam.PORT.PMUX1_12.Get()&^(0xff<<0) | (uint32(val) << 0))
|
||||
case 29:
|
||||
sam.PORT.PMUX1_12.Set(sam.PORT.PMUX1_12.Get()&^(0xff<<8) | (uint32(val) << 8))
|
||||
case 30:
|
||||
sam.PORT.PMUX1_12.Set(sam.PORT.PMUX1_12.Get()&^(0xff<<16) | (uint32(val) << 16))
|
||||
case 31:
|
||||
sam.PORT.PMUX1_12.Set(sam.PORT.PMUX1_12.Get()&^(0xff<<24) | (uint32(val) << 24))
|
||||
}
|
||||
}
|
||||
|
||||
// getPinCfg returns the value for the correct PINCFG register for this pin.
|
||||
func (p Pin) getPinCfg() uint8 {
|
||||
switch p {
|
||||
case 0:
|
||||
return sam.PORT.PINCFG0_0.Get()
|
||||
case 1:
|
||||
return sam.PORT.PINCFG0_1.Get()
|
||||
case 2:
|
||||
return sam.PORT.PINCFG0_2.Get()
|
||||
case 3:
|
||||
return sam.PORT.PINCFG0_3.Get()
|
||||
case 4:
|
||||
return sam.PORT.PINCFG0_4.Get()
|
||||
case 5:
|
||||
return sam.PORT.PINCFG0_5.Get()
|
||||
case 6:
|
||||
return sam.PORT.PINCFG0_6.Get()
|
||||
case 7:
|
||||
return sam.PORT.PINCFG0_7.Get()
|
||||
case 8:
|
||||
return sam.PORT.PINCFG0_8.Get()
|
||||
case 9:
|
||||
return sam.PORT.PINCFG0_9.Get()
|
||||
case 10:
|
||||
return sam.PORT.PINCFG0_10.Get()
|
||||
case 11:
|
||||
return sam.PORT.PINCFG0_11.Get()
|
||||
case 12:
|
||||
return sam.PORT.PINCFG0_12.Get()
|
||||
case 13:
|
||||
return sam.PORT.PINCFG0_13.Get()
|
||||
case 14:
|
||||
return sam.PORT.PINCFG0_14.Get()
|
||||
case 15:
|
||||
return sam.PORT.PINCFG0_15.Get()
|
||||
case 16:
|
||||
return sam.PORT.PINCFG0_16.Get()
|
||||
case 17:
|
||||
return sam.PORT.PINCFG0_17.Get()
|
||||
case 18:
|
||||
return sam.PORT.PINCFG0_18.Get()
|
||||
case 19:
|
||||
return sam.PORT.PINCFG0_19.Get()
|
||||
case 20:
|
||||
return sam.PORT.PINCFG0_20.Get()
|
||||
case 21:
|
||||
return sam.PORT.PINCFG0_21.Get()
|
||||
case 22:
|
||||
return sam.PORT.PINCFG0_22.Get()
|
||||
case 23:
|
||||
return sam.PORT.PINCFG0_23.Get()
|
||||
case 24:
|
||||
return sam.PORT.PINCFG0_24.Get()
|
||||
case 25:
|
||||
return sam.PORT.PINCFG0_25.Get()
|
||||
case 26:
|
||||
return sam.PORT.PINCFG0_26.Get()
|
||||
case 27:
|
||||
return sam.PORT.PINCFG0_27.Get()
|
||||
case 28:
|
||||
return sam.PORT.PINCFG0_28.Get()
|
||||
case 29:
|
||||
return sam.PORT.PINCFG0_29.Get()
|
||||
case 30:
|
||||
return sam.PORT.PINCFG0_30.Get()
|
||||
case 31:
|
||||
return sam.PORT.PINCFG0_31.Get()
|
||||
case 32: // PB00
|
||||
return uint8(sam.PORT.PINCFG1_0.Get()>>0) & 0xff
|
||||
case 33: // PB01
|
||||
return uint8(sam.PORT.PINCFG1_0.Get()>>8) & 0xff
|
||||
case 34: // PB02
|
||||
return uint8(sam.PORT.PINCFG1_0.Get()>>16) & 0xff
|
||||
case 35: // PB03
|
||||
return uint8(sam.PORT.PINCFG1_0.Get()>>24) & 0xff
|
||||
case 37: // PB04
|
||||
return uint8(sam.PORT.PINCFG1_4.Get()>>0) & 0xff
|
||||
case 38: // PB05
|
||||
return uint8(sam.PORT.PINCFG1_4.Get()>>8) & 0xff
|
||||
case 39: // PB06
|
||||
return uint8(sam.PORT.PINCFG1_4.Get()>>16) & 0xff
|
||||
case 40: // PB07
|
||||
return uint8(sam.PORT.PINCFG1_4.Get()>>24) & 0xff
|
||||
case 41: // PB08
|
||||
return uint8(sam.PORT.PINCFG1_8.Get()>>0) & 0xff
|
||||
case 42: // PB09
|
||||
return uint8(sam.PORT.PINCFG1_8.Get()>>8) & 0xff
|
||||
case 43: // PB10
|
||||
return uint8(sam.PORT.PINCFG1_8.Get()>>16) & 0xff
|
||||
case 44: // PB11
|
||||
return uint8(sam.PORT.PINCFG1_8.Get()>>24) & 0xff
|
||||
case 45: // PB12
|
||||
return uint8(sam.PORT.PINCFG1_12.Get()>>0) & 0xff
|
||||
case 46: // PB13
|
||||
return uint8(sam.PORT.PINCFG1_12.Get()>>8) & 0xff
|
||||
case 47: // PB14
|
||||
return uint8(sam.PORT.PINCFG1_12.Get()>>16) & 0xff
|
||||
case 48: // PB15
|
||||
return uint8(sam.PORT.PINCFG1_12.Get()>>24) & 0xff
|
||||
case 49: // PB16
|
||||
return uint8(sam.PORT.PINCFG1_16.Get()>>0) & 0xff
|
||||
case 50: // PB17
|
||||
return uint8(sam.PORT.PINCFG1_16.Get()>>8) & 0xff
|
||||
case 51: // PB18
|
||||
return uint8(sam.PORT.PINCFG1_16.Get()>>16) & 0xff
|
||||
case 52: // PB19
|
||||
return uint8(sam.PORT.PINCFG1_16.Get()>>24) & 0xff
|
||||
case 53: // PB20
|
||||
return uint8(sam.PORT.PINCFG1_20.Get()>>0) & 0xff
|
||||
case 54: // PB21
|
||||
return uint8(sam.PORT.PINCFG1_20.Get()>>8) & 0xff
|
||||
case 55: // PB22
|
||||
return uint8(sam.PORT.PINCFG1_20.Get()>>16) & 0xff
|
||||
case 56: // PB23
|
||||
return uint8(sam.PORT.PINCFG1_20.Get()>>24) & 0xff
|
||||
case 57: // PB24
|
||||
return uint8(sam.PORT.PINCFG1_24.Get()>>0) & 0xff
|
||||
case 58: // PB25
|
||||
return uint8(sam.PORT.PINCFG1_24.Get()>>8) & 0xff
|
||||
case 59: // PB26
|
||||
return uint8(sam.PORT.PINCFG1_24.Get()>>16) & 0xff
|
||||
case 60: // PB27
|
||||
return uint8(sam.PORT.PINCFG1_24.Get()>>24) & 0xff
|
||||
case 61: // PB28
|
||||
return uint8(sam.PORT.PINCFG1_28.Get()>>0) & 0xff
|
||||
case 62: // PB29
|
||||
return uint8(sam.PORT.PINCFG1_28.Get()>>8) & 0xff
|
||||
case 63: // PB30
|
||||
return uint8(sam.PORT.PINCFG1_28.Get()>>16) & 0xff
|
||||
case 64: // PB31
|
||||
return uint8(sam.PORT.PINCFG1_28.Get()>>24) & 0xff
|
||||
default:
|
||||
return 0
|
||||
}
|
||||
}
|
||||
|
||||
// setPinCfg sets the value for the correct PINCFG register for this pin.
|
||||
func (p Pin) setPinCfg(val uint8) {
|
||||
switch p {
|
||||
case 0:
|
||||
sam.PORT.PINCFG0_0.Set(val)
|
||||
case 1:
|
||||
sam.PORT.PINCFG0_1.Set(val)
|
||||
case 2:
|
||||
sam.PORT.PINCFG0_2.Set(val)
|
||||
case 3:
|
||||
sam.PORT.PINCFG0_3.Set(val)
|
||||
case 4:
|
||||
sam.PORT.PINCFG0_4.Set(val)
|
||||
case 5:
|
||||
sam.PORT.PINCFG0_5.Set(val)
|
||||
case 6:
|
||||
sam.PORT.PINCFG0_6.Set(val)
|
||||
case 7:
|
||||
sam.PORT.PINCFG0_7.Set(val)
|
||||
case 8:
|
||||
sam.PORT.PINCFG0_8.Set(val)
|
||||
case 9:
|
||||
sam.PORT.PINCFG0_9.Set(val)
|
||||
case 10:
|
||||
sam.PORT.PINCFG0_10.Set(val)
|
||||
case 11:
|
||||
sam.PORT.PINCFG0_11.Set(val)
|
||||
case 12:
|
||||
sam.PORT.PINCFG0_12.Set(val)
|
||||
case 13:
|
||||
sam.PORT.PINCFG0_13.Set(val)
|
||||
case 14:
|
||||
sam.PORT.PINCFG0_14.Set(val)
|
||||
case 15:
|
||||
sam.PORT.PINCFG0_15.Set(val)
|
||||
case 16:
|
||||
sam.PORT.PINCFG0_16.Set(val)
|
||||
case 17:
|
||||
sam.PORT.PINCFG0_17.Set(val)
|
||||
case 18:
|
||||
sam.PORT.PINCFG0_18.Set(val)
|
||||
case 19:
|
||||
sam.PORT.PINCFG0_19.Set(val)
|
||||
case 20:
|
||||
sam.PORT.PINCFG0_20.Set(val)
|
||||
case 21:
|
||||
sam.PORT.PINCFG0_21.Set(val)
|
||||
case 22:
|
||||
sam.PORT.PINCFG0_22.Set(val)
|
||||
case 23:
|
||||
sam.PORT.PINCFG0_23.Set(val)
|
||||
case 24:
|
||||
sam.PORT.PINCFG0_24.Set(val)
|
||||
case 25:
|
||||
sam.PORT.PINCFG0_25.Set(val)
|
||||
case 26:
|
||||
sam.PORT.PINCFG0_26.Set(val)
|
||||
case 27:
|
||||
sam.PORT.PINCFG0_27.Set(val)
|
||||
case 28:
|
||||
sam.PORT.PINCFG0_28.Set(val)
|
||||
case 29:
|
||||
sam.PORT.PINCFG0_29.Set(val)
|
||||
case 30:
|
||||
sam.PORT.PINCFG0_30.Set(val)
|
||||
case 31:
|
||||
sam.PORT.PINCFG0_31.Set(val)
|
||||
case 32: // PB00
|
||||
sam.PORT.PINCFG1_0.Set(sam.PORT.PINCFG1_0.Get()&^(0xff<<0) | (uint32(val) << 0))
|
||||
case 33: // PB01
|
||||
sam.PORT.PINCFG1_0.Set(sam.PORT.PINCFG1_0.Get()&^(0xff<<8) | (uint32(val) << 8))
|
||||
case 34: // PB02
|
||||
sam.PORT.PINCFG1_0.Set(sam.PORT.PINCFG1_0.Get()&^(0xff<<16) | (uint32(val) << 16))
|
||||
case 35: // PB03
|
||||
sam.PORT.PINCFG1_0.Set(sam.PORT.PINCFG1_0.Get()&^(0xff<<24) | (uint32(val) << 24))
|
||||
case 36: // PB04
|
||||
sam.PORT.PINCFG1_4.Set(sam.PORT.PINCFG1_4.Get()&^(0xff<<0) | (uint32(val) << 0))
|
||||
case 37: // PB05
|
||||
sam.PORT.PINCFG1_4.Set(sam.PORT.PINCFG1_4.Get()&^(0xff<<8) | (uint32(val) << 8))
|
||||
case 38: // PB06
|
||||
sam.PORT.PINCFG1_4.Set(sam.PORT.PINCFG1_4.Get()&^(0xff<<16) | (uint32(val) << 16))
|
||||
case 39: // PB07
|
||||
sam.PORT.PINCFG1_4.Set(sam.PORT.PINCFG1_4.Get()&^(0xff<<24) | (uint32(val) << 24))
|
||||
case 40: // PB08
|
||||
sam.PORT.PINCFG1_8.Set(sam.PORT.PINCFG1_8.Get()&^(0xff<<0) | (uint32(val) << 0))
|
||||
case 41: // PB09
|
||||
sam.PORT.PINCFG1_8.Set(sam.PORT.PINCFG1_8.Get()&^(0xff<<8) | (uint32(val) << 8))
|
||||
case 42: // PB10
|
||||
sam.PORT.PINCFG1_8.Set(sam.PORT.PINCFG1_8.Get()&^(0xff<<16) | (uint32(val) << 16))
|
||||
case 43: // PB11
|
||||
sam.PORT.PINCFG1_8.Set(sam.PORT.PINCFG1_8.Get()&^(0xff<<24) | (uint32(val) << 24))
|
||||
case 44: // PB12
|
||||
sam.PORT.PINCFG1_12.Set(sam.PORT.PINCFG1_12.Get()&^(0xff<<0) | (uint32(val) << 0))
|
||||
case 45: // PB13
|
||||
sam.PORT.PINCFG1_12.Set(sam.PORT.PINCFG1_12.Get()&^(0xff<<8) | (uint32(val) << 8))
|
||||
case 46: // PB14
|
||||
sam.PORT.PINCFG1_12.Set(sam.PORT.PINCFG1_12.Get()&^(0xff<<16) | (uint32(val) << 16))
|
||||
case 47: // PB15
|
||||
sam.PORT.PINCFG1_12.Set(sam.PORT.PINCFG1_12.Get()&^(0xff<<24) | (uint32(val) << 24))
|
||||
case 48: // PB16
|
||||
sam.PORT.PINCFG1_16.Set(sam.PORT.PINCFG1_16.Get()&^(0xff<<0) | (uint32(val) << 0))
|
||||
case 49: // PB17
|
||||
sam.PORT.PINCFG1_16.Set(sam.PORT.PINCFG1_16.Get()&^(0xff<<8) | (uint32(val) << 8))
|
||||
case 50: // PB18
|
||||
sam.PORT.PINCFG1_16.Set(sam.PORT.PINCFG1_16.Get()&^(0xff<<16) | (uint32(val) << 16))
|
||||
case 51: // PB19
|
||||
sam.PORT.PINCFG1_16.Set(sam.PORT.PINCFG1_16.Get()&^(0xff<<24) | (uint32(val) << 24))
|
||||
case 52: // PB20
|
||||
sam.PORT.PINCFG1_20.Set(sam.PORT.PINCFG1_20.Get()&^(0xff<<0) | (uint32(val) << 0))
|
||||
case 53: // PB21
|
||||
sam.PORT.PINCFG1_20.Set(sam.PORT.PINCFG1_20.Get()&^(0xff<<8) | (uint32(val) << 8))
|
||||
case 54: // PB22
|
||||
sam.PORT.PINCFG1_20.Set(sam.PORT.PINCFG1_20.Get()&^(0xff<<16) | (uint32(val) << 16))
|
||||
case 55: // PB23
|
||||
sam.PORT.PINCFG1_20.Set(sam.PORT.PINCFG1_20.Get()&^(0xff<<24) | (uint32(val) << 24))
|
||||
case 56: // PB24
|
||||
sam.PORT.PINCFG1_24.Set(sam.PORT.PINCFG1_24.Get()&^(0xff<<0) | (uint32(val) << 0))
|
||||
case 57: // PB25
|
||||
sam.PORT.PINCFG1_24.Set(sam.PORT.PINCFG1_24.Get()&^(0xff<<8) | (uint32(val) << 8))
|
||||
case 58: // PB26
|
||||
sam.PORT.PINCFG1_24.Set(sam.PORT.PINCFG1_24.Get()&^(0xff<<16) | (uint32(val) << 16))
|
||||
case 59: // PB27
|
||||
sam.PORT.PINCFG1_24.Set(sam.PORT.PINCFG1_24.Get()&^(0xff<<24) | (uint32(val) << 24))
|
||||
case 60: // PB28
|
||||
sam.PORT.PINCFG1_28.Set(sam.PORT.PINCFG1_28.Get()&^(0xff<<0) | (uint32(val) << 0))
|
||||
case 61: // PB29
|
||||
sam.PORT.PINCFG1_28.Set(sam.PORT.PINCFG1_28.Get()&^(0xff<<8) | (uint32(val) << 8))
|
||||
case 62: // PB30
|
||||
sam.PORT.PINCFG1_28.Set(sam.PORT.PINCFG1_28.Get()&^(0xff<<16) | (uint32(val) << 16))
|
||||
case 63: // PB31
|
||||
sam.PORT.PINCFG1_28.Set(sam.PORT.PINCFG1_28.Get()&^(0xff<<24) | (uint32(val) << 24))
|
||||
}
|
||||
}
|
||||
@@ -4,6 +4,7 @@ package machine
|
||||
|
||||
import (
|
||||
"device/avr"
|
||||
"runtime/volatile"
|
||||
)
|
||||
|
||||
// Configure sets the pin to input or output.
|
||||
@@ -15,7 +16,7 @@ func (p Pin) Configure(config PinConfig) {
|
||||
}
|
||||
}
|
||||
|
||||
func (p Pin) getPortMask() (*avr.Register8, uint8) {
|
||||
func (p Pin) getPortMask() (*volatile.Register8, uint8) {
|
||||
return avr.PORTB, 1 << uint8(p)
|
||||
}
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@ package machine
|
||||
|
||||
import (
|
||||
"device/avr"
|
||||
"runtime/volatile"
|
||||
)
|
||||
|
||||
type PinMode uint8
|
||||
@@ -30,7 +31,7 @@ func (p Pin) Set(value bool) {
|
||||
// Warning: there are no separate pin set/clear registers on the AVR. The
|
||||
// returned mask is only valid as long as no other pin in the same port has been
|
||||
// changed.
|
||||
func (p Pin) PortMaskSet() (*avr.Register8, uint8) {
|
||||
func (p Pin) PortMaskSet() (*volatile.Register8, uint8) {
|
||||
port, mask := p.getPortMask()
|
||||
return port, port.Get() | mask
|
||||
}
|
||||
@@ -41,7 +42,7 @@ func (p Pin) PortMaskSet() (*avr.Register8, uint8) {
|
||||
// Warning: there are no separate pin set/clear registers on the AVR. The
|
||||
// returned mask is only valid as long as no other pin in the same port has been
|
||||
// changed.
|
||||
func (p Pin) PortMaskClear() (*avr.Register8, uint8) {
|
||||
func (p Pin) PortMaskClear() (*volatile.Register8, uint8) {
|
||||
port, mask := p.getPortMask()
|
||||
return port, port.Get() &^ mask
|
||||
}
|
||||
|
||||
@@ -1,40 +0,0 @@
|
||||
// +build !avr,!nrf,!sam,!stm32
|
||||
|
||||
package machine
|
||||
|
||||
// Dummy machine package, filled with no-ops.
|
||||
|
||||
type PinMode uint8
|
||||
|
||||
const (
|
||||
PinInput PinMode = iota
|
||||
PinOutput
|
||||
)
|
||||
|
||||
// Fake LED numbers, for testing.
|
||||
const (
|
||||
LED Pin = LED1
|
||||
LED1 Pin = 0
|
||||
LED2 Pin = 0
|
||||
LED3 Pin = 0
|
||||
LED4 Pin = 0
|
||||
)
|
||||
|
||||
// Fake button numbers, for testing.
|
||||
const (
|
||||
BUTTON Pin = BUTTON1
|
||||
BUTTON1 Pin = 0
|
||||
BUTTON2 Pin = 0
|
||||
BUTTON3 Pin = 0
|
||||
BUTTON4 Pin = 0
|
||||
)
|
||||
|
||||
func (p Pin) Configure(config PinConfig) {
|
||||
}
|
||||
|
||||
func (p Pin) Set(value bool) {
|
||||
}
|
||||
|
||||
func (p Pin) Get() bool {
|
||||
return false
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
// +build fe310
|
||||
|
||||
package machine
|
||||
|
||||
import (
|
||||
"device/sifive"
|
||||
)
|
||||
|
||||
type PinMode uint8
|
||||
|
||||
const (
|
||||
PinInput PinMode = iota
|
||||
PinOutput
|
||||
)
|
||||
|
||||
// Configure this pin with the given configuration.
|
||||
func (p Pin) Configure(config PinConfig) {
|
||||
sifive.GPIO0.INPUT_EN.SetBits(1 << uint8(p))
|
||||
if config.Mode == PinOutput {
|
||||
sifive.GPIO0.OUTPUT_EN.SetBits(1 << uint8(p))
|
||||
}
|
||||
}
|
||||
|
||||
// Set the pin to high or low.
|
||||
func (p Pin) Set(high bool) {
|
||||
if high {
|
||||
sifive.GPIO0.PORT.SetBits(1 << uint8(p))
|
||||
} else {
|
||||
sifive.GPIO0.PORT.ClearBits(1 << uint8(p))
|
||||
}
|
||||
}
|
||||
|
||||
type UART struct {
|
||||
Bus *sifive.UART_Type
|
||||
Buffer *RingBuffer
|
||||
}
|
||||
|
||||
var (
|
||||
UART0 = UART{Bus: sifive.UART0, Buffer: NewRingBuffer()}
|
||||
)
|
||||
|
||||
func (uart UART) Configure(config UARTConfig) {
|
||||
// Assuming a 16Mhz Crystal (which is Y1 on the HiFive1), the divisor for a
|
||||
// 115200 baud rate is 138.
|
||||
sifive.UART0.DIV.Set(138)
|
||||
sifive.UART0.TXCTRL.Set(sifive.UART_TXCTRL_ENABLE)
|
||||
}
|
||||
|
||||
func (uart UART) WriteByte(c byte) {
|
||||
for sifive.UART0.TXDATA.Get()&sifive.UART_TXDATA_FULL != 0 {
|
||||
}
|
||||
|
||||
sifive.UART0.TXDATA.Set(uint32(c))
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -0,0 +1,184 @@
|
||||
// +build !baremetal
|
||||
|
||||
package machine
|
||||
|
||||
// Dummy machine package that calls out to external functions.
|
||||
|
||||
var (
|
||||
SPI0 = SPI{0}
|
||||
I2C0 = I2C{0}
|
||||
UART0 = UART{0}
|
||||
)
|
||||
|
||||
type PinMode uint8
|
||||
|
||||
const (
|
||||
PinInput PinMode = iota
|
||||
PinOutput
|
||||
PinInputPullup
|
||||
PinInputPulldown
|
||||
)
|
||||
|
||||
func (p Pin) Configure(config PinConfig) {
|
||||
gpioConfigure(p, config)
|
||||
}
|
||||
|
||||
func (p Pin) Set(value bool) {
|
||||
gpioSet(p, value)
|
||||
}
|
||||
|
||||
func (p Pin) Get() bool {
|
||||
return gpioGet(p)
|
||||
}
|
||||
|
||||
//go:export __tinygo_gpio_configure
|
||||
func gpioConfigure(pin Pin, config PinConfig)
|
||||
|
||||
//go:export __tinygo_gpio_set
|
||||
func gpioSet(pin Pin, value bool)
|
||||
|
||||
//go:export __tinygo_gpio_get
|
||||
func gpioGet(pin Pin) bool
|
||||
|
||||
type SPI struct {
|
||||
Bus uint8
|
||||
}
|
||||
|
||||
type SPIConfig struct {
|
||||
Frequency uint32
|
||||
SCK Pin
|
||||
MOSI Pin
|
||||
MISO Pin
|
||||
Mode uint8
|
||||
}
|
||||
|
||||
func (spi SPI) Configure(config SPIConfig) {
|
||||
spiConfigure(spi.Bus, config.SCK, config.MOSI, config.MISO)
|
||||
}
|
||||
|
||||
// Transfer writes/reads a single byte using the SPI interface.
|
||||
func (spi SPI) Transfer(w byte) (byte, error) {
|
||||
return spiTransfer(spi.Bus, w), nil
|
||||
}
|
||||
|
||||
//go:export __tinygo_spi_configure
|
||||
func spiConfigure(bus uint8, sck Pin, mosi Pin, miso Pin)
|
||||
|
||||
//go:export __tinygo_spi_transfer
|
||||
func spiTransfer(bus uint8, w uint8) uint8
|
||||
|
||||
// InitADC enables support for ADC peripherals.
|
||||
func InitADC() {
|
||||
// Nothing to do here.
|
||||
}
|
||||
|
||||
// Configure configures an ADC pin to be able to be used to read data.
|
||||
func (adc ADC) Configure() {
|
||||
}
|
||||
|
||||
// Get reads the current analog value from this ADC peripheral.
|
||||
func (adc ADC) Get() uint16 {
|
||||
return adcRead(adc.Pin)
|
||||
}
|
||||
|
||||
//go:export __tinygo_adc_read
|
||||
func adcRead(pin Pin) uint16
|
||||
|
||||
// InitPWM enables support for PWM peripherals.
|
||||
func InitPWM() {
|
||||
// Nothing to do here.
|
||||
}
|
||||
|
||||
// Configure configures a PWM pin for output.
|
||||
func (pwm PWM) Configure() {
|
||||
}
|
||||
|
||||
// Set turns on the duty cycle for a PWM pin using the provided value.
|
||||
func (pwm PWM) Set(value uint16) {
|
||||
pwmSet(pwm.Pin, value)
|
||||
}
|
||||
|
||||
//go:export __tinygo_pwm_set
|
||||
func pwmSet(pin Pin, value uint16)
|
||||
|
||||
// I2C is a generic implementation of the Inter-IC communication protocol.
|
||||
type I2C struct {
|
||||
Bus uint8
|
||||
}
|
||||
|
||||
// I2CConfig is used to store config info for I2C.
|
||||
type I2CConfig struct {
|
||||
Frequency uint32
|
||||
SCL Pin
|
||||
SDA Pin
|
||||
}
|
||||
|
||||
// Configure is intended to setup the I2C interface.
|
||||
func (i2c I2C) Configure(config I2CConfig) {
|
||||
i2cConfigure(i2c.Bus, config.SCL, config.SDA)
|
||||
}
|
||||
|
||||
// Tx does a single I2C transaction at the specified address.
|
||||
func (i2c I2C) Tx(addr uint16, w, r []byte) error {
|
||||
i2cTransfer(i2c.Bus, &w[0], len(w), &r[0], len(r))
|
||||
// TODO: do something with the returned error code.
|
||||
return nil
|
||||
}
|
||||
|
||||
//go:export __tinygo_i2c_configure
|
||||
func i2cConfigure(bus uint8, scl Pin, sda Pin)
|
||||
|
||||
//go:export __tinygo_i2c_transfer
|
||||
func i2cTransfer(bus uint8, w *byte, wlen int, r *byte, rlen int) int
|
||||
|
||||
type UART struct {
|
||||
Bus uint8
|
||||
}
|
||||
|
||||
type UARTConfig struct {
|
||||
BaudRate uint32
|
||||
TX Pin
|
||||
RX Pin
|
||||
}
|
||||
|
||||
// Configure the UART.
|
||||
func (uart UART) Configure(config UARTConfig) {
|
||||
uartConfigure(uart.Bus, config.TX, config.RX)
|
||||
}
|
||||
|
||||
// Read from the UART.
|
||||
func (uart UART) Read(data []byte) (n int, err error) {
|
||||
return uartRead(uart.Bus, &data[0], len(data)), nil
|
||||
}
|
||||
|
||||
// Write to the UART.
|
||||
func (uart UART) Write(data []byte) (n int, err error) {
|
||||
return uartWrite(uart.Bus, &data[0], len(data)), nil
|
||||
}
|
||||
|
||||
// Buffered returns the number of bytes currently stored in the RX buffer.
|
||||
func (uart UART) Buffered() int {
|
||||
return 0
|
||||
}
|
||||
|
||||
// ReadByte reads a single byte from the UART.
|
||||
func (uart UART) ReadByte() (byte, error) {
|
||||
var b byte
|
||||
uartRead(uart.Bus, &b, 1)
|
||||
return b, nil
|
||||
}
|
||||
|
||||
// WriteByte writes a single byte to the UART.
|
||||
func (uart UART) WriteByte(b byte) error {
|
||||
uartWrite(uart.Bus, &b, 1)
|
||||
return nil
|
||||
}
|
||||
|
||||
//go:export __tinygo_uart_configure
|
||||
func uartConfigure(bus uint8, tx Pin, rx Pin)
|
||||
|
||||
//go:export __tinygo_uart_read
|
||||
func uartRead(bus uint8, buf *byte, bufLen int) int
|
||||
|
||||
//go:export __tinygo_uart_write
|
||||
func uartWrite(bus uint8, buf *byte, bufLen int) int
|
||||
@@ -5,6 +5,12 @@ package machine
|
||||
import (
|
||||
"device/arm"
|
||||
"device/nrf"
|
||||
"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")
|
||||
)
|
||||
|
||||
type PinMode uint8
|
||||
@@ -322,3 +328,70 @@ 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 {
|
||||
if w == nil && r == nil {
|
||||
return ErrTxSlicesRequired
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
@@ -5,14 +5,3 @@ package machine
|
||||
// Peripheral abstraction layer for the stm32.
|
||||
|
||||
type PinMode uint8
|
||||
|
||||
const (
|
||||
portA Pin = iota * 16
|
||||
portB
|
||||
portC
|
||||
portD
|
||||
portE
|
||||
portF
|
||||
portG
|
||||
portH
|
||||
)
|
||||
|
||||
@@ -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
-1
@@ -1,4 +1,4 @@
|
||||
// +build nrf stm32f103xx atsamd21g18a
|
||||
// +build sam stm32,!stm32f407
|
||||
|
||||
package machine
|
||||
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
// +build avr nrf sam stm32
|
||||
// +build avr nrf sam sifive stm32
|
||||
|
||||
package machine
|
||||
|
||||
|
||||
+12
-9
@@ -4,9 +4,9 @@ package machine
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"device/sam"
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"runtime/volatile"
|
||||
)
|
||||
|
||||
const deviceDescriptorSize = 18
|
||||
@@ -316,8 +316,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,
|
||||
@@ -352,8 +352,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()
|
||||
}
|
||||
|
||||
@@ -473,6 +473,9 @@ const (
|
||||
usb_CDC_CS_INTERFACE = 0x24
|
||||
usb_CDC_CS_ENDPOINT = 0x25
|
||||
usb_CDC_DATA_INTERFACE_CLASS = 0x0A
|
||||
|
||||
usb_CDC_LINESTATE_DTR = 0x01
|
||||
usb_CDC_LINESTATE_RTS = 0x02
|
||||
)
|
||||
|
||||
// usbDeviceDescBank is the USB device endpoint descriptor.
|
||||
@@ -484,11 +487,11 @@ const (
|
||||
// RoReg8 Reserved1[0x5];
|
||||
// } UsbDeviceDescBank;
|
||||
type usbDeviceDescBank struct {
|
||||
ADDR sam.Register32
|
||||
PCKSIZE sam.Register32
|
||||
EXTREG sam.Register16
|
||||
STATUS_BK sam.Register8
|
||||
_reserved [5]sam.Register8
|
||||
ADDR volatile.Register32
|
||||
PCKSIZE volatile.Register32
|
||||
EXTREG volatile.Register16
|
||||
STATUS_BK volatile.Register8
|
||||
_reserved [5]volatile.Register8
|
||||
}
|
||||
|
||||
type usbDeviceDescriptor struct {
|
||||
|
||||
+160
-1
@@ -11,7 +11,8 @@ import (
|
||||
|
||||
// Portable analogs of some common system call errors.
|
||||
var (
|
||||
ErrUnsupported = errors.New("operation not supported")
|
||||
errUnsupported = errors.New("operation not supported")
|
||||
notImplemented = errors.New("os: not implemented")
|
||||
)
|
||||
|
||||
// Stdin, Stdout, and Stderr are open Files pointing to the standard input,
|
||||
@@ -28,6 +29,21 @@ type File struct {
|
||||
name string
|
||||
}
|
||||
|
||||
// Readdir is a stub, not yet implemented
|
||||
func (f *File) Readdir(n int) ([]FileInfo, error) {
|
||||
return nil, notImplemented
|
||||
}
|
||||
|
||||
// Readdirnames is a stub, not yet implemented
|
||||
func (f *File) Readdirnames(n int) (names []string, err error) {
|
||||
return nil, notImplemented
|
||||
}
|
||||
|
||||
// Stat is a stub, not yet implemented
|
||||
func (f *File) Stat() (FileInfo, error) {
|
||||
return nil, notImplemented
|
||||
}
|
||||
|
||||
// NewFile returns a new File with the given file descriptor and name.
|
||||
func NewFile(fd uintptr, name string) *File {
|
||||
return &File{fd, name}
|
||||
@@ -38,3 +54,146 @@ func NewFile(fd uintptr, name string) *File {
|
||||
func (f *File) Fd() uintptr {
|
||||
return f.fd
|
||||
}
|
||||
|
||||
const (
|
||||
PathSeparator = '/' // OS-specific path separator
|
||||
PathListSeparator = ':' // OS-specific path list separator
|
||||
)
|
||||
|
||||
// IsPathSeparator reports whether c is a directory separator character.
|
||||
func IsPathSeparator(c uint8) bool {
|
||||
return PathSeparator == c
|
||||
}
|
||||
|
||||
// PathError records an error and the operation and file path that caused it.
|
||||
type PathError struct {
|
||||
Op string
|
||||
Path string
|
||||
Err error
|
||||
}
|
||||
|
||||
func (e *PathError) Error() string { return e.Op + " " + e.Path + ": " + e.Err.Error() }
|
||||
|
||||
// Open is a super simple stub function (for now), only capable of opening stdin, stdout, and stderr
|
||||
func Open(name string) (*File, error) {
|
||||
fd := uintptr(999)
|
||||
switch name {
|
||||
case "/dev/stdin":
|
||||
fd = 0
|
||||
case "/dev/stdout":
|
||||
fd = 1
|
||||
case "/dev/stderr":
|
||||
fd = 2
|
||||
default:
|
||||
return nil, &PathError{"open", name, notImplemented}
|
||||
}
|
||||
return &File{fd, name}, nil
|
||||
}
|
||||
|
||||
// OpenFile is a stub, passing through to the stub Open() call
|
||||
func OpenFile(name string, flag int, perm FileMode) (*File, error) {
|
||||
return Open(name)
|
||||
}
|
||||
|
||||
// Create is a stub, passing through to the stub Open() call
|
||||
func Create(name string) (*File, error) {
|
||||
return Open(name)
|
||||
}
|
||||
|
||||
type FileMode uint32
|
||||
|
||||
// Mode constants, copied from the mainline Go source
|
||||
// https://github.com/golang/go/blob/4ce6a8e89668b87dce67e2f55802903d6eb9110a/src/os/types.go#L35-L63
|
||||
const (
|
||||
// The single letters are the abbreviations used by the String method's formatting.
|
||||
ModeDir FileMode = 1 << (32 - 1 - iota) // d: is a directory
|
||||
ModeAppend // a: append-only
|
||||
ModeExclusive // l: exclusive use
|
||||
ModeTemporary // T: temporary file; Plan 9 only
|
||||
ModeSymlink // L: symbolic link
|
||||
ModeDevice // D: device file
|
||||
ModeNamedPipe // p: named pipe (FIFO)
|
||||
ModeSocket // S: Unix domain socket
|
||||
ModeSetuid // u: setuid
|
||||
ModeSetgid // g: setgid
|
||||
ModeCharDevice // c: Unix character device, when ModeDevice is set
|
||||
ModeSticky // t: sticky
|
||||
ModeIrregular // ?: non-regular file; nothing else is known about this file
|
||||
|
||||
// Mask for the type bits. For regular files, none will be set.
|
||||
ModeType = ModeDir | ModeSymlink | ModeNamedPipe | ModeSocket | ModeDevice | ModeCharDevice | ModeIrregular
|
||||
|
||||
ModePerm FileMode = 0777 // Unix permission bits
|
||||
)
|
||||
|
||||
// IsDir is a stub, always returning false
|
||||
func (m FileMode) IsDir() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
// Stub constants
|
||||
const (
|
||||
O_RDONLY int = 1
|
||||
O_WRONLY int = 2
|
||||
O_RDWR int = 4
|
||||
O_APPEND int = 8
|
||||
O_CREATE int = 16
|
||||
O_EXCL int = 32
|
||||
O_SYNC int = 64
|
||||
O_TRUNC int = 128
|
||||
)
|
||||
|
||||
// A FileInfo describes a file and is returned by Stat and Lstat.
|
||||
type FileInfo interface {
|
||||
Name() string // base name of the file
|
||||
Size() int64 // length in bytes for regular files; system-dependent for others
|
||||
Mode() FileMode // file mode bits
|
||||
// ModTime() time.Time // modification time
|
||||
IsDir() bool // abbreviation for Mode().IsDir()
|
||||
Sys() interface{} // underlying data source (can return nil)
|
||||
}
|
||||
|
||||
// Stat is a stub, not yet implemented
|
||||
func Stat(name string) (FileInfo, error) {
|
||||
return nil, notImplemented
|
||||
}
|
||||
|
||||
// Lstat is a stub, not yet implemented
|
||||
func Lstat(name string) (FileInfo, error) {
|
||||
return nil, notImplemented
|
||||
}
|
||||
|
||||
// Getwd is a stub (for now), always returning an empty string
|
||||
func Getwd() (string, error) {
|
||||
return "", nil
|
||||
}
|
||||
|
||||
// Readlink is a stub (for now), always returning the string it was given
|
||||
func Readlink(name string) (string, error) {
|
||||
return name, nil
|
||||
}
|
||||
|
||||
// TempDir is a stub (for now), always returning the string "/tmp"
|
||||
func TempDir() string {
|
||||
return "/tmp"
|
||||
}
|
||||
|
||||
// Mkdir is a stub, not yet implemented
|
||||
func Mkdir(name string, perm FileMode) error {
|
||||
return notImplemented
|
||||
}
|
||||
|
||||
// IsExist is a stub (for now), always returning false
|
||||
func IsExist(err error) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
// IsNotExist is a stub (for now), always returning false
|
||||
func IsNotExist(err error) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
// Getpid is a stub (for now), always returning 1
|
||||
func Getpid() int {
|
||||
return 1
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// +build avr cortexm wasm
|
||||
// +build baremetal wasm
|
||||
|
||||
package os
|
||||
|
||||
@@ -8,7 +8,7 @@ import (
|
||||
|
||||
// Read is unsupported on this system.
|
||||
func (f *File) Read(b []byte) (n int, err error) {
|
||||
return 0, ErrUnsupported
|
||||
return 0, errUnsupported
|
||||
}
|
||||
|
||||
// Write writes len(b) bytes to the output. It returns the number of bytes
|
||||
@@ -21,13 +21,13 @@ func (f *File) Write(b []byte) (n int, err error) {
|
||||
}
|
||||
return len(b), nil
|
||||
default:
|
||||
return 0, ErrUnsupported
|
||||
return 0, errUnsupported
|
||||
}
|
||||
}
|
||||
|
||||
// Close is unsupported on this system.
|
||||
func (f *File) Close() error {
|
||||
return ErrUnsupported
|
||||
return errUnsupported
|
||||
}
|
||||
|
||||
//go:linkname putchar runtime.putchar
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user