mirror of
https://github.com/tinygo-org/tinygo.git
synced 2026-08-08 04:53:42 +00:00
Compare commits
94 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| a44d74d4c0 | |||
| 24ff2d1ee2 | |||
| 0db26b0662 | |||
| 4f7a650614 | |||
| e74db01f82 | |||
| 86f48da594 | |||
| 4be80e0372 | |||
| 10e1420237 | |||
| 6a1bb134f9 | |||
| f0bb3c092d | |||
| 3d3e48179e | |||
| ea5df0f214 | |||
| c09724bfc5 | |||
| 00f745e351 | |||
| 5171618284 | |||
| 4605cbbc6e | |||
| 98eee7c22a | |||
| 81199da3f1 | |||
| 3cedebd299 | |||
| 118af9df69 | |||
| 172efc26a7 | |||
| f49e69b02a | |||
| e20af665fa | |||
| 36d1198115 | |||
| 009b27350e | |||
| 93961f9d41 | |||
| acdaaa17d8 | |||
| b7b548a8d0 | |||
| 8266d2ff58 | |||
| d2d78d3d0a | |||
| c6255e4d0a | |||
| 8e6cb89ceb | |||
| 946e2dd405 | |||
| ad73a727a3 | |||
| efafda1d32 | |||
| 405c0263b0 | |||
| 45b5decb4e | |||
| fb39e0917b | |||
| 16fbf53ae2 | |||
| b153bd63f2 | |||
| 6108ee6859 | |||
| 76c9f13e13 | |||
| d31deda1b5 | |||
| 473576e756 | |||
| 913131bf62 | |||
| b41e58bcb4 | |||
| 0db403dc0c | |||
| cadb75a4aa | |||
| 5987233b99 | |||
| 992f1fa248 | |||
| 1cbe09ee89 | |||
| 18cce571a2 | |||
| ac330f4a70 | |||
| e977276044 | |||
| feb2b4715b | |||
| 3b0ed63c29 | |||
| 59cc901340 | |||
| ef600965aa | |||
| e7cf75030c | |||
| dff6e6566d | |||
| cdff0bd3ee | |||
| fa25fa1b0c | |||
| be7529b261 | |||
| 26bdfa9c84 | |||
| b72f677310 | |||
| 2a5ab2500d | |||
| c138a50457 | |||
| 86ab03c999 | |||
| 923a6f5873 | |||
| 071f863e5d | |||
| b884db81ea | |||
| 41df9648a8 | |||
| 3ec94a06ed | |||
| 3777791aa3 | |||
| 96d4987345 | |||
| 66ed03faa2 | |||
| 46d468b79d | |||
| 373fa6d69b | |||
| a4642ddf59 | |||
| ceece08959 | |||
| 7014f90120 | |||
| f7dde33842 | |||
| 2f059ac91e | |||
| e139a9dd71 | |||
| 8dc0deaf46 | |||
| 388d11eecf | |||
| adff391bd2 | |||
| a7794de99d | |||
| 4339cbd56f | |||
| 6b1faeb882 | |||
| 2c15f36702 | |||
| 9d21bfce8c | |||
| c4dff990d9 | |||
| b66b15d02c |
+25
-25
@@ -44,12 +44,12 @@ commands:
|
|||||||
steps:
|
steps:
|
||||||
- restore_cache:
|
- restore_cache:
|
||||||
keys:
|
keys:
|
||||||
- llvm-source-8-v5
|
- llvm-source-9-v0
|
||||||
- run:
|
- run:
|
||||||
name: "Fetch LLVM source"
|
name: "Fetch LLVM source"
|
||||||
command: make llvm-source
|
command: make llvm-source
|
||||||
- save_cache:
|
- save_cache:
|
||||||
key: llvm-source-8-v5
|
key: llvm-source-9-v0
|
||||||
paths:
|
paths:
|
||||||
- llvm-project
|
- llvm-project
|
||||||
test-linux:
|
test-linux:
|
||||||
@@ -57,7 +57,7 @@ commands:
|
|||||||
- checkout
|
- checkout
|
||||||
- submodules
|
- submodules
|
||||||
- apt-dependencies:
|
- apt-dependencies:
|
||||||
llvm: "-8"
|
llvm: "-9"
|
||||||
- install-node
|
- install-node
|
||||||
- restore_cache:
|
- restore_cache:
|
||||||
keys:
|
keys:
|
||||||
@@ -65,7 +65,7 @@ commands:
|
|||||||
- go-cache-v2-{{ checksum "go.mod" }}
|
- go-cache-v2-{{ checksum "go.mod" }}
|
||||||
- llvm-source-linux
|
- llvm-source-linux
|
||||||
- run: go install .
|
- run: go install .
|
||||||
- run: go test -v ./interp ./transform .
|
- run: go test -v ./cgo ./compileopts ./interp ./transform .
|
||||||
- run: make gen-device -j4
|
- run: make gen-device -j4
|
||||||
- run: make smoketest RISCV=0
|
- run: make smoketest RISCV=0
|
||||||
- save_cache:
|
- save_cache:
|
||||||
@@ -101,7 +101,7 @@ commands:
|
|||||||
- llvm-source-linux
|
- llvm-source-linux
|
||||||
- restore_cache:
|
- restore_cache:
|
||||||
keys:
|
keys:
|
||||||
- llvm-build-8-linux-v7-assert
|
- llvm-build-9-linux-v0-assert
|
||||||
- run:
|
- run:
|
||||||
name: "Build LLVM"
|
name: "Build LLVM"
|
||||||
command: |
|
command: |
|
||||||
@@ -119,15 +119,15 @@ commands:
|
|||||||
make ASSERT=1 llvm-build
|
make ASSERT=1 llvm-build
|
||||||
fi
|
fi
|
||||||
- save_cache:
|
- save_cache:
|
||||||
key: llvm-build-8-linux-v7-assert
|
key: llvm-build-9-linux-v0-assert
|
||||||
paths:
|
paths:
|
||||||
llvm-build
|
llvm-build
|
||||||
- run:
|
- run:
|
||||||
name: "Create LLVM symlinks"
|
name: "Create LLVM symlinks"
|
||||||
command: |
|
command: |
|
||||||
ln -s $PWD/llvm-build/bin/clang-8 /go/bin/clang-8
|
ln -s $PWD/llvm-build/bin/clang-9 /go/bin/clang-9
|
||||||
ln -s $PWD/llvm-build/bin/ld.lld /go/bin/ld.lld-8
|
ln -s $PWD/llvm-build/bin/ld.lld /go/bin/ld.lld-9
|
||||||
ln -s $PWD/llvm-build/bin/wasm-ld /go/bin/wasm-ld-8
|
ln -s $PWD/llvm-build/bin/wasm-ld /go/bin/wasm-ld-9
|
||||||
- run: make ASSERT=1
|
- run: make ASSERT=1
|
||||||
- run:
|
- run:
|
||||||
name: "Test TinyGo"
|
name: "Test TinyGo"
|
||||||
@@ -166,7 +166,7 @@ commands:
|
|||||||
- llvm-source-linux
|
- llvm-source-linux
|
||||||
- restore_cache:
|
- restore_cache:
|
||||||
keys:
|
keys:
|
||||||
- llvm-build-8-linux-v7
|
- llvm-build-9-linux-v0
|
||||||
- run:
|
- run:
|
||||||
name: "Build LLVM"
|
name: "Build LLVM"
|
||||||
command: |
|
command: |
|
||||||
@@ -184,15 +184,15 @@ commands:
|
|||||||
make llvm-build
|
make llvm-build
|
||||||
fi
|
fi
|
||||||
- save_cache:
|
- save_cache:
|
||||||
key: llvm-build-8-linux-v7
|
key: llvm-build-9-linux-v0
|
||||||
paths:
|
paths:
|
||||||
llvm-build
|
llvm-build
|
||||||
- run:
|
- run:
|
||||||
name: "Create LLVM symlinks"
|
name: "Create LLVM symlinks"
|
||||||
command: |
|
command: |
|
||||||
ln -s $PWD/llvm-build/bin/clang-8 /go/bin/clang-8
|
ln -s $PWD/llvm-build/bin/clang-9 /go/bin/clang-9
|
||||||
ln -s $PWD/llvm-build/bin/ld.lld /go/bin/ld.lld-8
|
ln -s $PWD/llvm-build/bin/ld.lld /go/bin/ld.lld-9
|
||||||
ln -s $PWD/llvm-build/bin/wasm-ld /go/bin/wasm-ld-8
|
ln -s $PWD/llvm-build/bin/wasm-ld /go/bin/wasm-ld-9
|
||||||
- run:
|
- run:
|
||||||
name: "Test TinyGo"
|
name: "Test TinyGo"
|
||||||
command: make test
|
command: make test
|
||||||
@@ -239,17 +239,17 @@ commands:
|
|||||||
- go-cache-macos-v2-{{ checksum "go.mod" }}
|
- go-cache-macos-v2-{{ checksum "go.mod" }}
|
||||||
- restore_cache:
|
- restore_cache:
|
||||||
keys:
|
keys:
|
||||||
- llvm-source-8-macos-v5
|
- llvm-source-9-macos-v0
|
||||||
- run:
|
- run:
|
||||||
name: "Fetch LLVM source"
|
name: "Fetch LLVM source"
|
||||||
command: make llvm-source
|
command: make llvm-source
|
||||||
- save_cache:
|
- save_cache:
|
||||||
key: llvm-source-8-macos-v5
|
key: llvm-source-9-macos-v0
|
||||||
paths:
|
paths:
|
||||||
- llvm-project
|
- llvm-project
|
||||||
- restore_cache:
|
- restore_cache:
|
||||||
keys:
|
keys:
|
||||||
- llvm-build-8-macos-v6
|
- llvm-build-9-macos-v0
|
||||||
- run:
|
- run:
|
||||||
name: "Build LLVM"
|
name: "Build LLVM"
|
||||||
command: |
|
command: |
|
||||||
@@ -261,13 +261,13 @@ commands:
|
|||||||
make llvm-build
|
make llvm-build
|
||||||
fi
|
fi
|
||||||
- save_cache:
|
- save_cache:
|
||||||
key: llvm-build-8-macos-v6
|
key: llvm-build-9-macos-v0
|
||||||
paths:
|
paths:
|
||||||
llvm-build
|
llvm-build
|
||||||
- run:
|
- run:
|
||||||
name: "Create LLVM symlinks"
|
name: "Create LLVM symlinks"
|
||||||
command: |
|
command: |
|
||||||
ln -s $PWD/llvm-build/bin/clang-8 /usr/local/bin/clang-8
|
ln -s $PWD/llvm-build/bin/clang-9 /usr/local/bin/clang-9
|
||||||
- run:
|
- run:
|
||||||
name: "Test TinyGo"
|
name: "Test TinyGo"
|
||||||
command: make test
|
command: make test
|
||||||
@@ -300,17 +300,17 @@ commands:
|
|||||||
|
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
test-llvm8-go111:
|
test-llvm9-go111:
|
||||||
docker:
|
docker:
|
||||||
- image: circleci/golang:1.11-stretch
|
- image: circleci/golang:1.11-stretch
|
||||||
steps:
|
steps:
|
||||||
- test-linux
|
- test-linux
|
||||||
test-llvm8-go112:
|
test-llvm9-go112:
|
||||||
docker:
|
docker:
|
||||||
- image: circleci/golang:1.12-stretch
|
- image: circleci/golang:1.12-stretch
|
||||||
steps:
|
steps:
|
||||||
- test-linux
|
- test-linux
|
||||||
test-llvm8-go113:
|
test-llvm9-go113:
|
||||||
docker:
|
docker:
|
||||||
- image: circleci/golang:1.13-stretch
|
- image: circleci/golang:1.13-stretch
|
||||||
steps:
|
steps:
|
||||||
@@ -337,9 +337,9 @@ jobs:
|
|||||||
workflows:
|
workflows:
|
||||||
test-all:
|
test-all:
|
||||||
jobs:
|
jobs:
|
||||||
- test-llvm8-go111
|
- test-llvm9-go111
|
||||||
- test-llvm8-go112
|
- test-llvm9-go112
|
||||||
- test-llvm8-go113
|
- test-llvm9-go113
|
||||||
- build-linux
|
- build-linux
|
||||||
- build-macos
|
- build-macos
|
||||||
- assert-test-linux
|
- assert-test-linux
|
||||||
|
|||||||
@@ -1,3 +1,66 @@
|
|||||||
|
0.10.0
|
||||||
|
---
|
||||||
|
* **command line**
|
||||||
|
- halt GDB after flashing with `gdb` subcommand
|
||||||
|
- fix a crash when using `-ocd-output`
|
||||||
|
- add `info` subcommand
|
||||||
|
- add `-programmer` flag
|
||||||
|
* **builder**
|
||||||
|
- macos: use llvm@8 instead of just llvm in paths
|
||||||
|
- add `linkerscript` key to target JSON files
|
||||||
|
- write a symbol table when writing out the compiler-rt lib
|
||||||
|
- make Clang header detection more robust
|
||||||
|
- switch to LLVM 9
|
||||||
|
* **compiler**
|
||||||
|
- fix interface miscompilation with reflect
|
||||||
|
- fix miscompile of static goroutine calls to closures
|
||||||
|
- fix `todo: store` panic
|
||||||
|
- fix incorrect starting value for optimized allocations in a loop
|
||||||
|
- optimize coroutines on non-Cortex-M targets
|
||||||
|
- fix crash for programs which have heap allocations but never hit the GC
|
||||||
|
- add support for async interface calls
|
||||||
|
- fix inserting non-const values in a const global
|
||||||
|
- interp: improve error reporting
|
||||||
|
- interp: implement comparing ptrtoint to 0
|
||||||
|
* **cgo**
|
||||||
|
- improve diagnostics
|
||||||
|
- implement the constant parser (for `#define`) as a real parser
|
||||||
|
- rename reserved field names such as `type`
|
||||||
|
- avoid `"unsafe" imported but not used` error
|
||||||
|
- include all enums in the CGo Go AST
|
||||||
|
- add support for nested structs and unions
|
||||||
|
- implement `#cgo CFLAGS`
|
||||||
|
* **standard library**
|
||||||
|
- `reflect`: add implementation of array alignment
|
||||||
|
- `runtime`: improve scheduler performance when no goroutines are queued
|
||||||
|
- `runtime`: add blocking select
|
||||||
|
- `runtime`: implement interface equality in non-trivial cases
|
||||||
|
- `runtime`: add AdjustTimeOffset to update current time
|
||||||
|
- `runtime`: only implement CountString for required platforms
|
||||||
|
- `runtime`: use MSP/PSP registers for scheduling on Cortex-M
|
||||||
|
* **targets**
|
||||||
|
- `arm`: add system timer registers
|
||||||
|
- `atmega`: add port C GPIO support
|
||||||
|
- `atsamd21`: correct handling of pins >= 32
|
||||||
|
- `atsamd21`: i2s initialization fixes
|
||||||
|
- `atsamd51`: fix clock init code
|
||||||
|
- `atsamd51`: correct initialization for RTC
|
||||||
|
- `atsamd51`: fix pin function selection
|
||||||
|
- `atsamd51`: pin method cleanup
|
||||||
|
- `atsamd51`: allow setting pin mode for each of the SPI pins
|
||||||
|
- `atsamd51`: correct channel init and pin map for ADC based on ItsyBitsy-M4
|
||||||
|
- `feather-m4`: add Adafruit Feather M4 board
|
||||||
|
- `hifive1b`: add support for SPI1
|
||||||
|
- `hifive1b`: fix compiling in simulation
|
||||||
|
- `linux`: fix time on arm32
|
||||||
|
- `metro-m4`: add support for Adafruit Metro M4 Express Airlift board
|
||||||
|
- `metro-m4`: fixes for UART2
|
||||||
|
- `pinetime-devkit0`: add support for the PineTime dev kit
|
||||||
|
- `x9pro`: add support for this smartwatch
|
||||||
|
- `pca10040-s132v6`: add support for SoftDevice
|
||||||
|
- `pca10056-s140v7`: add support for SoftDevice
|
||||||
|
- `arduino-nano33`: added SPI1 connected to NINA-W102 chip on Arduino Nano 33 IOT
|
||||||
|
|
||||||
0.9.0
|
0.9.0
|
||||||
---
|
---
|
||||||
* **command line**
|
* **command line**
|
||||||
|
|||||||
+7
-7
@@ -1,10 +1,10 @@
|
|||||||
# TinyGo base stage installs Go 1.13, LLVM 8 and the TinyGo compiler itself.
|
# TinyGo base stage installs Go 1.13, LLVM 9 and the TinyGo compiler itself.
|
||||||
FROM golang:1.13 AS tinygo-base
|
FROM golang:1.13 AS tinygo-base
|
||||||
|
|
||||||
RUN wget -O- https://apt.llvm.org/llvm-snapshot.gpg.key| apt-key add - && \
|
RUN wget -O- https://apt.llvm.org/llvm-snapshot.gpg.key| apt-key add - && \
|
||||||
echo "deb http://apt.llvm.org/buster/ llvm-toolchain-buster-8 main" >> /etc/apt/sources.list && \
|
echo "deb http://apt.llvm.org/buster/ llvm-toolchain-buster-9 main" >> /etc/apt/sources.list && \
|
||||||
apt-get update && \
|
apt-get update && \
|
||||||
apt-get install -y llvm-8-dev libclang-8-dev git
|
apt-get install -y llvm-9-dev libclang-9-dev git
|
||||||
|
|
||||||
COPY . /tinygo
|
COPY . /tinygo
|
||||||
|
|
||||||
@@ -25,9 +25,9 @@ COPY --from=tinygo-base /tinygo/src /tinygo/src
|
|||||||
COPY --from=tinygo-base /tinygo/targets /tinygo/targets
|
COPY --from=tinygo-base /tinygo/targets /tinygo/targets
|
||||||
|
|
||||||
RUN wget -O- https://apt.llvm.org/llvm-snapshot.gpg.key| apt-key add - && \
|
RUN wget -O- https://apt.llvm.org/llvm-snapshot.gpg.key| apt-key add - && \
|
||||||
echo "deb http://apt.llvm.org/buster/ llvm-toolchain-buster-8 main" >> /etc/apt/sources.list && \
|
echo "deb http://apt.llvm.org/buster/ llvm-toolchain-buster-9 main" >> /etc/apt/sources.list && \
|
||||||
apt-get update && \
|
apt-get update && \
|
||||||
apt-get install -y libllvm8 lld-8
|
apt-get install -y libllvm9 lld-9
|
||||||
|
|
||||||
# tinygo-avr stage installs the needed dependencies to compile TinyGo programs for AVR microcontrollers.
|
# tinygo-avr stage installs the needed dependencies to compile TinyGo programs for AVR microcontrollers.
|
||||||
FROM tinygo-base AS tinygo-avr
|
FROM tinygo-base AS tinygo-avr
|
||||||
@@ -59,7 +59,7 @@ COPY --from=tinygo-base /tinygo/lib /tinygo/lib
|
|||||||
|
|
||||||
RUN cd /tinygo/ && \
|
RUN cd /tinygo/ && \
|
||||||
apt-get update && \
|
apt-get update && \
|
||||||
apt-get install -y apt-utils python3 make clang-8 && \
|
apt-get install -y apt-utils python3 make clang-9 && \
|
||||||
make gen-device-nrf && make gen-device-stm32 && \
|
make gen-device-nrf && make gen-device-stm32 && \
|
||||||
apt-get remove -y python3 && \
|
apt-get remove -y python3 && \
|
||||||
apt-get autoremove -y && \
|
apt-get autoremove -y && \
|
||||||
@@ -74,7 +74,7 @@ COPY --from=tinygo-base /tinygo/lib /tinygo/lib
|
|||||||
|
|
||||||
RUN cd /tinygo/ && \
|
RUN cd /tinygo/ && \
|
||||||
apt-get update && \
|
apt-get update && \
|
||||||
apt-get install -y apt-utils python3 make clang-8 binutils-avr gcc-avr avr-libc && \
|
apt-get install -y apt-utils python3 make clang-9 binutils-avr gcc-avr avr-libc && \
|
||||||
make gen-device && \
|
make gen-device && \
|
||||||
apt-get remove -y python3 && \
|
apt-get remove -y python3 && \
|
||||||
apt-get autoremove -y && \
|
apt-get autoremove -y && \
|
||||||
|
|||||||
@@ -87,7 +87,7 @@ endif
|
|||||||
clean:
|
clean:
|
||||||
@rm -rf build
|
@rm -rf build
|
||||||
|
|
||||||
FMT_PATHS = ./*.go cgo compiler interp ir loader src/device/arm src/examples src/machine src/os src/reflect src/runtime src/sync src/syscall src/internal/reflectlite transform
|
FMT_PATHS = ./*.go builder cgo compiler interp ir loader src/device/arm src/examples src/machine src/os src/reflect src/runtime src/sync src/syscall src/internal/reflectlite transform
|
||||||
fmt:
|
fmt:
|
||||||
@gofmt -l -w $(FMT_PATHS)
|
@gofmt -l -w $(FMT_PATHS)
|
||||||
fmt-check:
|
fmt-check:
|
||||||
@@ -110,7 +110,7 @@ gen-device-sam:
|
|||||||
GO111MODULE=off $(GO) fmt ./src/device/sam
|
GO111MODULE=off $(GO) fmt ./src/device/sam
|
||||||
|
|
||||||
gen-device-sifive:
|
gen-device-sifive:
|
||||||
$(PYTHON) ./tools/gen-device-svd.py lib/cmsis-svd/data/SiFive-Community/ src/device/sifive/ --source=https://github.com/AdaCore/svd2ada/tree/master/CMSIS-SVD/SiFive-Community
|
$(PYTHON) ./tools/gen-device-svd.py lib/cmsis-svd/data/SiFive-Community/ src/device/sifive/ --source=https://github.com/posborne/cmsis-svd/tree/master/data/SiFive-Community
|
||||||
GO111MODULE=off $(GO) fmt ./src/device/sifive
|
GO111MODULE=off $(GO) fmt ./src/device/sifive
|
||||||
|
|
||||||
gen-device-stm32:
|
gen-device-stm32:
|
||||||
@@ -120,13 +120,13 @@ gen-device-stm32:
|
|||||||
|
|
||||||
# Get LLVM sources.
|
# Get LLVM sources.
|
||||||
llvm-project/README.md:
|
llvm-project/README.md:
|
||||||
git clone -b release/8.x https://github.com/llvm/llvm-project
|
git clone -b release/9.x https://github.com/llvm/llvm-project
|
||||||
llvm-source: llvm-project/README.md
|
llvm-source: llvm-project/README.md
|
||||||
|
|
||||||
# Configure LLVM.
|
# Configure LLVM.
|
||||||
TINYGO_SOURCE_DIR=$(shell pwd)
|
TINYGO_SOURCE_DIR=$(shell pwd)
|
||||||
$(LLVM_BUILDDIR)/build.ninja: llvm-source
|
$(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 -DLIBCLANG_BUILD_STATIC=ON -DLLVM_ENABLE_TERMINFO=OFF -DLLVM_ENABLE_ZLIB=OFF -DLLVM_ENABLE_PROJECTS="clang;lld" -DLLVM_TOOL_CLANG_TOOLS_EXTRA_BUILD=OFF $(LLVM_OPTION)
|
mkdir -p $(LLVM_BUILDDIR); cd $(LLVM_BUILDDIR); cmake -G Ninja $(TINYGO_SOURCE_DIR)/llvm-project/llvm "-DLLVM_TARGETS_TO_BUILD=X86;ARM;AArch64;RISCV;WebAssembly" "-DLLVM_EXPERIMENTAL_TARGETS_TO_BUILD=AVR" -DCMAKE_BUILD_TYPE=Release -DLIBCLANG_BUILD_STATIC=ON -DLLVM_ENABLE_TERMINFO=OFF -DLLVM_ENABLE_ZLIB=OFF -DLLVM_ENABLE_PROJECTS="clang;lld" -DLLVM_TOOL_CLANG_TOOLS_EXTRA_BUILD=OFF $(LLVM_OPTION)
|
||||||
|
|
||||||
# Build LLVM.
|
# Build LLVM.
|
||||||
$(LLVM_BUILDDIR): $(LLVM_BUILDDIR)/build.ninja
|
$(LLVM_BUILDDIR): $(LLVM_BUILDDIR)/build.ninja
|
||||||
@@ -139,13 +139,14 @@ tinygo:
|
|||||||
CGO_CPPFLAGS="$(CGO_CPPFLAGS)" CGO_CXXFLAGS="$(CGO_CXXFLAGS)" CGO_LDFLAGS="$(CGO_LDFLAGS)" $(GO) build -o build/tinygo$(EXE) -tags byollvm .
|
CGO_CPPFLAGS="$(CGO_CPPFLAGS)" CGO_CXXFLAGS="$(CGO_CXXFLAGS)" CGO_LDFLAGS="$(CGO_LDFLAGS)" $(GO) build -o build/tinygo$(EXE) -tags byollvm .
|
||||||
|
|
||||||
test:
|
test:
|
||||||
CGO_CPPFLAGS="$(CGO_CPPFLAGS)" CGO_CXXFLAGS="$(CGO_CXXFLAGS)" CGO_LDFLAGS="$(CGO_LDFLAGS)" $(GO) test -v -tags byollvm ./interp ./transform .
|
CGO_CPPFLAGS="$(CGO_CPPFLAGS)" CGO_CXXFLAGS="$(CGO_CXXFLAGS)" CGO_LDFLAGS="$(CGO_LDFLAGS)" $(GO) test -v -tags byollvm ./cgo ./compileopts ./interp ./transform .
|
||||||
|
|
||||||
tinygo-test:
|
tinygo-test:
|
||||||
cd tests/tinygotest && tinygo test
|
cd tests/tinygotest && tinygo test
|
||||||
|
|
||||||
.PHONY: smoketest
|
.PHONY: smoketest
|
||||||
smoketest:
|
smoketest:
|
||||||
|
$(TINYGO) version
|
||||||
# test all examples
|
# test all examples
|
||||||
$(TINYGO) build -size short -o test.hex -target=pca10040 examples/blinky1
|
$(TINYGO) build -size short -o test.hex -target=pca10040 examples/blinky1
|
||||||
@$(MD5SUM) test.hex
|
@$(MD5SUM) test.hex
|
||||||
@@ -171,10 +172,24 @@ smoketest:
|
|||||||
@$(MD5SUM) test.hex
|
@$(MD5SUM) test.hex
|
||||||
$(TINYGO) build -size short -o test.hex -target=pca10040 examples/serial
|
$(TINYGO) build -size short -o test.hex -target=pca10040 examples/serial
|
||||||
@$(MD5SUM) test.hex
|
@$(MD5SUM) test.hex
|
||||||
|
$(TINYGO) build -size short -o test.hex -target=pca10040 examples/systick
|
||||||
|
@$(MD5SUM) test.hex
|
||||||
$(TINYGO) build -size short -o test.hex -target=pca10040 examples/test
|
$(TINYGO) build -size short -o test.hex -target=pca10040 examples/test
|
||||||
@$(MD5SUM) test.hex
|
@$(MD5SUM) test.hex
|
||||||
# test all targets/boards
|
# test simulated boards on play.tinygo.org
|
||||||
|
$(TINYGO) build -o test.wasm -tags=arduino examples/blinky1
|
||||||
|
@$(MD5SUM) test.wasm
|
||||||
|
$(TINYGO) build -o test.wasm -tags=hifive1b examples/blinky1
|
||||||
|
@$(MD5SUM) test.wasm
|
||||||
|
$(TINYGO) build -o test.wasm -tags=reelboard examples/blinky1
|
||||||
|
@$(MD5SUM) test.wasm
|
||||||
$(TINYGO) build -o test.wasm -tags=pca10040 examples/blinky2
|
$(TINYGO) build -o test.wasm -tags=pca10040 examples/blinky2
|
||||||
|
@$(MD5SUM) test.wasm
|
||||||
|
$(TINYGO) build -o test.wasm -tags=pca10056 examples/blinky2
|
||||||
|
@$(MD5SUM) test.wasm
|
||||||
|
# test all targets/boards
|
||||||
|
$(TINYGO) build -size short -o test.hex -target=pca10040-s132v6 examples/blinky1
|
||||||
|
@$(MD5SUM) test.hex
|
||||||
$(TINYGO) build -size short -o test.hex -target=microbit examples/echo
|
$(TINYGO) build -size short -o test.hex -target=microbit examples/echo
|
||||||
@$(MD5SUM) test.hex
|
@$(MD5SUM) test.hex
|
||||||
$(TINYGO) build -size short -o test.hex -target=nrf52840-mdk examples/blinky1
|
$(TINYGO) build -size short -o test.hex -target=nrf52840-mdk examples/blinky1
|
||||||
@@ -209,8 +224,18 @@ smoketest:
|
|||||||
@$(MD5SUM) test.gba
|
@$(MD5SUM) test.gba
|
||||||
$(TINYGO) build -size short -o test.hex -target=itsybitsy-m4 examples/blinky1
|
$(TINYGO) build -size short -o test.hex -target=itsybitsy-m4 examples/blinky1
|
||||||
@$(MD5SUM) test.hex
|
@$(MD5SUM) test.hex
|
||||||
|
$(TINYGO) build -size short -o test.hex -target=feather-m4 examples/blinky1
|
||||||
|
@$(MD5SUM) test.hex
|
||||||
|
$(TINYGO) build -size short -o test.hex -target=metro-m4-airlift examples/blinky1
|
||||||
|
@$(MD5SUM) test.hex
|
||||||
$(TINYGO) build -size short -o test.hex -target=nucleo-f103rb examples/blinky1
|
$(TINYGO) build -size short -o test.hex -target=nucleo-f103rb examples/blinky1
|
||||||
@$(MD5SUM) test.hex
|
@$(MD5SUM) test.hex
|
||||||
|
$(TINYGO) build -size short -o test.hex -target=pinetime-devkit0 examples/blinky1
|
||||||
|
@$(MD5SUM) test.hex
|
||||||
|
$(TINYGO) build -size short -o test.hex -target=x9pro examples/blinky1
|
||||||
|
@$(MD5SUM) test.hex
|
||||||
|
$(TINYGO) build -size short -o test.hex -target=pca10056-s140v7 examples/blinky1
|
||||||
|
@$(MD5SUM) test.hex
|
||||||
ifneq ($(AVR), 0)
|
ifneq ($(AVR), 0)
|
||||||
$(TINYGO) build -size short -o test.hex -target=arduino examples/blinky1
|
$(TINYGO) build -size short -o test.hex -target=arduino examples/blinky1
|
||||||
@$(MD5SUM) test.hex
|
@$(MD5SUM) test.hex
|
||||||
|
|||||||
@@ -43,12 +43,14 @@ See the [getting started instructions](https://tinygo.org/getting-started/) for
|
|||||||
|
|
||||||
You can compile TinyGo programs for microcontrollers, WebAssembly and Linux.
|
You can compile TinyGo programs for microcontrollers, WebAssembly and Linux.
|
||||||
|
|
||||||
The following 19 microcontroller boards are currently supported:
|
The following 22 microcontroller boards are currently supported:
|
||||||
|
|
||||||
* [Adafruit Circuit Playground Express](https://www.adafruit.com/product/3333)
|
* [Adafruit Circuit Playground Express](https://www.adafruit.com/product/3333)
|
||||||
* [Adafruit Feather M0](https://www.adafruit.com/product/2772)
|
* [Adafruit Feather M0](https://www.adafruit.com/product/2772)
|
||||||
|
* [Adafruit Feather M4](https://www.adafruit.com/product/3857)
|
||||||
* [Adafruit ItsyBitsy M0](https://www.adafruit.com/product/3727)
|
* [Adafruit ItsyBitsy M0](https://www.adafruit.com/product/3727)
|
||||||
* [Adafruit ItsyBitsy M4](https://www.adafruit.com/product/3800)
|
* [Adafruit ItsyBitsy M4](https://www.adafruit.com/product/3800)
|
||||||
|
* [Adafruit Metro M4 Express Airlift](https://www.adafruit.com/product/4000)
|
||||||
* [Adafruit Trinket M0](https://www.adafruit.com/product/3500)
|
* [Adafruit Trinket M0](https://www.adafruit.com/product/3500)
|
||||||
* [Arduino Nano33 IoT](https://store.arduino.cc/nano-33-iot)
|
* [Arduino Nano33 IoT](https://store.arduino.cc/nano-33-iot)
|
||||||
* [Arduino Uno](https://store.arduino.cc/arduino-uno-rev3)
|
* [Arduino Uno](https://store.arduino.cc/arduino-uno-rev3)
|
||||||
@@ -64,6 +66,7 @@ The following 19 microcontroller boards are currently supported:
|
|||||||
* [ST Micro "Nucleo F103RB"](https://www.st.com/en/evaluation-tools/nucleo-f103rb.html)
|
* [ST Micro "Nucleo F103RB"](https://www.st.com/en/evaluation-tools/nucleo-f103rb.html)
|
||||||
* [ST Micro STM32F103XX "Bluepill"](http://wiki.stm32duino.com/index.php?title=Blue_Pill)
|
* [ST Micro STM32F103XX "Bluepill"](http://wiki.stm32duino.com/index.php?title=Blue_Pill)
|
||||||
* [ST Micro STM32F407 "Discovery"](https://www.st.com/en/evaluation-tools/stm32f4discovery.html)
|
* [ST Micro STM32F407 "Discovery"](https://www.st.com/en/evaluation-tools/stm32f4discovery.html)
|
||||||
|
* [X9 Pro smartwatch](https://github.com/curtpw/nRF5x-device-reverse-engineering/tree/master/X9-nrf52832-activity-tracker/)
|
||||||
|
|
||||||
For more information, see [this list of boards](https://tinygo.org/microcontrollers/). Pull requests for additional support are welcome!
|
For more information, see [this list of boards](https://tinygo.org/microcontrollers/). Pull requests for additional support are welcome!
|
||||||
|
|
||||||
|
|||||||
+18
-5
@@ -1,3 +1,9 @@
|
|||||||
|
# Avoid lengthy LLVM rebuilds on each newly pushed branch. Pull requests will
|
||||||
|
# be built anyway.
|
||||||
|
trigger:
|
||||||
|
- master
|
||||||
|
- dev
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
- job: Build
|
- job: Build
|
||||||
timeoutInMinutes: 180
|
timeoutInMinutes: 180
|
||||||
@@ -8,7 +14,7 @@ jobs:
|
|||||||
- task: CacheBeta@0
|
- task: CacheBeta@0
|
||||||
displayName: Cache LLVM source
|
displayName: Cache LLVM source
|
||||||
inputs:
|
inputs:
|
||||||
key: llvm-source-8-windows-v0
|
key: llvm-source-9-windows-v0
|
||||||
path: llvm-project
|
path: llvm-project
|
||||||
- task: Bash@3
|
- task: Bash@3
|
||||||
displayName: Download LLVM source
|
displayName: Download LLVM source
|
||||||
@@ -18,7 +24,7 @@ jobs:
|
|||||||
- task: CacheBeta@0
|
- task: CacheBeta@0
|
||||||
displayName: Cache LLVM build
|
displayName: Cache LLVM build
|
||||||
inputs:
|
inputs:
|
||||||
key: llvm-build-8-windows-v1
|
key: llvm-build-9-windows-v0
|
||||||
path: llvm-build
|
path: llvm-build
|
||||||
- task: Bash@3
|
- task: Bash@3
|
||||||
displayName: Build LLVM
|
displayName: Build LLVM
|
||||||
@@ -28,6 +34,10 @@ jobs:
|
|||||||
if [ ! -f llvm-build/lib/liblldELF.a ]
|
if [ ! -f llvm-build/lib/liblldELF.a ]
|
||||||
then
|
then
|
||||||
choco install ninja
|
choco install ninja
|
||||||
|
# LLVM 9 cannot be built with MinGW 8.
|
||||||
|
# For details: https://reviews.llvm.org/D70266
|
||||||
|
choco uninstall mingw
|
||||||
|
choco install mingw --version=7.3.0
|
||||||
make llvm-build
|
make llvm-build
|
||||||
fi
|
fi
|
||||||
- task: Bash@3
|
- task: Bash@3
|
||||||
@@ -40,14 +50,16 @@ jobs:
|
|||||||
inputs:
|
inputs:
|
||||||
targetType: inline
|
targetType: inline
|
||||||
script: |
|
script: |
|
||||||
export PATH="$PATH:./llvm-build/bin:/c/Program Files/qemu"
|
export PATH="/c/Go1.13/bin:$PATH:./llvm-build/bin:/c/Program Files/qemu"
|
||||||
|
unset GOROOT
|
||||||
make test
|
make test
|
||||||
- task: Bash@3
|
- task: Bash@3
|
||||||
displayName: Build TinyGo release tarball
|
displayName: Build TinyGo release tarball
|
||||||
inputs:
|
inputs:
|
||||||
targetType: inline
|
targetType: inline
|
||||||
script: |
|
script: |
|
||||||
export PATH="$PATH:./llvm-build/bin:/c/Program Files/qemu"
|
export PATH="/c/Go1.13/bin:$PATH:./llvm-build/bin:/c/Program Files/qemu"
|
||||||
|
unset GOROOT
|
||||||
make release -j4
|
make release -j4
|
||||||
- publish: $(System.DefaultWorkingDirectory)/build/release.tar.gz
|
- publish: $(System.DefaultWorkingDirectory)/build/release.tar.gz
|
||||||
displayName: Publish tarball as artifact
|
displayName: Publish tarball as artifact
|
||||||
@@ -57,5 +69,6 @@ jobs:
|
|||||||
inputs:
|
inputs:
|
||||||
targetType: inline
|
targetType: inline
|
||||||
script: |
|
script: |
|
||||||
export PATH="$PATH:./llvm-build/bin:/c/Program Files/qemu"
|
export PATH="/c/Go1.13/bin:$PATH:./llvm-build/bin:/c/Program Files/qemu"
|
||||||
|
unset GOROOT
|
||||||
make smoketest TINYGO=build/tinygo AVR=0 RISCV=0
|
make smoketest TINYGO=build/tinygo AVR=0 RISCV=0
|
||||||
|
|||||||
+182
@@ -0,0 +1,182 @@
|
|||||||
|
package builder
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"debug/elf"
|
||||||
|
"encoding/binary"
|
||||||
|
"errors"
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/blakesmith/ar"
|
||||||
|
)
|
||||||
|
|
||||||
|
// makeArchive creates an arcive for static linking from a list of object files
|
||||||
|
// given as a parameter. It is equivalent to the following command:
|
||||||
|
//
|
||||||
|
// ar -rcs <archivePath> <objs...>
|
||||||
|
func makeArchive(archivePath string, objs []string) error {
|
||||||
|
// Open the archive file.
|
||||||
|
arfile, err := os.Create(archivePath)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer arfile.Close()
|
||||||
|
arwriter := ar.NewWriter(arfile)
|
||||||
|
err = arwriter.WriteGlobalHeader()
|
||||||
|
if err != nil {
|
||||||
|
return &os.PathError{"write ar header", archivePath, err}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Open all object files and read the symbols for the symbol table.
|
||||||
|
symbolTable := []struct {
|
||||||
|
name string // symbol name
|
||||||
|
fileIndex int // index into objfiles
|
||||||
|
}{}
|
||||||
|
objfiles := make([]struct {
|
||||||
|
file *os.File
|
||||||
|
archiveOffset int32
|
||||||
|
}, len(objs))
|
||||||
|
for i, objpath := range objs {
|
||||||
|
objfile, err := os.Open(objpath)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
objfiles[i].file = objfile
|
||||||
|
|
||||||
|
// Read the symbols and add them to the symbol table.
|
||||||
|
dbg, err := elf.NewFile(objfile)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
symbols, err := dbg.Symbols()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
for _, symbol := range symbols {
|
||||||
|
bind := elf.ST_BIND(symbol.Info)
|
||||||
|
if bind != elf.STB_GLOBAL && bind != elf.STB_WEAK {
|
||||||
|
// Don't include local symbols (STB_LOCAL).
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if elf.ST_TYPE(symbol.Info) != elf.STT_FUNC {
|
||||||
|
// Not a function.
|
||||||
|
// TODO: perhaps globals variables should also be included?
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
// Include in archive.
|
||||||
|
symbolTable = append(symbolTable, struct {
|
||||||
|
name string
|
||||||
|
fileIndex int
|
||||||
|
}{symbol.Name, i})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create the symbol table buffer.
|
||||||
|
// For some (sparse) details on the file format:
|
||||||
|
// https://en.wikipedia.org/wiki/Ar_(Unix)#System_V_(or_GNU)_variant
|
||||||
|
buf := &bytes.Buffer{}
|
||||||
|
binary.Write(buf, binary.BigEndian, int32(len(symbolTable)))
|
||||||
|
for range symbolTable {
|
||||||
|
// This is a placeholder index, it will be updated after all files have
|
||||||
|
// been written to the archive (see the end of this function).
|
||||||
|
err = binary.Write(buf, binary.BigEndian, int32(0))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for _, sym := range symbolTable {
|
||||||
|
_, err := buf.Write([]byte(sym.name + "\x00"))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for buf.Len()%2 != 0 {
|
||||||
|
// The symbol table must be aligned.
|
||||||
|
// This appears to be required by lld.
|
||||||
|
buf.WriteByte(0)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write the symbol table.
|
||||||
|
err = arwriter.WriteHeader(&ar.Header{
|
||||||
|
Name: "/",
|
||||||
|
ModTime: time.Unix(0, 0),
|
||||||
|
Uid: 0,
|
||||||
|
Gid: 0,
|
||||||
|
Mode: 0,
|
||||||
|
Size: int64(buf.Len()),
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Keep track of the start of the symbol table.
|
||||||
|
symbolTableStart, err := arfile.Seek(0, os.SEEK_CUR)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write symbol table contents.
|
||||||
|
_, err = arfile.Write(buf.Bytes())
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add all object files to the archive.
|
||||||
|
for i, objfile := range objfiles {
|
||||||
|
// Store the start index, for when we'll update the symbol table with
|
||||||
|
// the correct file start indices.
|
||||||
|
offset, err := arfile.Seek(0, os.SEEK_CUR)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if int64(int32(offset)) != offset {
|
||||||
|
return errors.New("large archives (4GB+) not supported: " + archivePath)
|
||||||
|
}
|
||||||
|
objfiles[i].archiveOffset = int32(offset)
|
||||||
|
|
||||||
|
// Write the file header.
|
||||||
|
st, err := objfile.file.Stat()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
err = arwriter.WriteHeader(&ar.Header{
|
||||||
|
Name: filepath.Base(objfile.file.Name()),
|
||||||
|
ModTime: time.Unix(0, 0),
|
||||||
|
Uid: 0,
|
||||||
|
Gid: 0,
|
||||||
|
Mode: 0644,
|
||||||
|
Size: st.Size(),
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Copy the file contents into the archive.
|
||||||
|
n, err := io.Copy(arwriter, objfile.file)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if n != st.Size() {
|
||||||
|
return errors.New("file modified during ar creation: " + archivePath)
|
||||||
|
}
|
||||||
|
|
||||||
|
// File is not needed anymore.
|
||||||
|
objfile.file.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create symbol indices.
|
||||||
|
indicesBuf := &bytes.Buffer{}
|
||||||
|
for _, sym := range symbolTable {
|
||||||
|
err = binary.Write(indicesBuf, binary.BigEndian, objfiles[sym.fileIndex].archiveOffset)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Overwrite placeholder indices.
|
||||||
|
_, err = arfile.WriteAt(indicesBuf.Bytes(), symbolTableStart+4)
|
||||||
|
return err
|
||||||
|
}
|
||||||
@@ -0,0 +1,227 @@
|
|||||||
|
// Package builder is the compiler driver of TinyGo. It takes in a package name
|
||||||
|
// and an output path, and outputs an executable. It manages the entire
|
||||||
|
// compilation pipeline in between.
|
||||||
|
package builder
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/tinygo-org/tinygo/compileopts"
|
||||||
|
"github.com/tinygo-org/tinygo/compiler"
|
||||||
|
"github.com/tinygo-org/tinygo/goenv"
|
||||||
|
"github.com/tinygo-org/tinygo/interp"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Build performs a single package to executable Go build. It takes in a package
|
||||||
|
// name, an output path, and set of compile options and from that it manages the
|
||||||
|
// whole compilation process.
|
||||||
|
//
|
||||||
|
// The error value may be of type *MultiError. Callers will likely want to check
|
||||||
|
// for this case and print such errors individually.
|
||||||
|
func Build(pkgName, outpath string, config *compileopts.Config, action func(string) error) error {
|
||||||
|
c, err := compiler.NewCompiler(pkgName, config)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Compile Go code to IR.
|
||||||
|
errs := c.Compile(pkgName)
|
||||||
|
if len(errs) != 0 {
|
||||||
|
if len(errs) == 1 {
|
||||||
|
return errs[0]
|
||||||
|
}
|
||||||
|
return &MultiError{errs}
|
||||||
|
}
|
||||||
|
if config.Options.PrintIR {
|
||||||
|
fmt.Println("; Generated LLVM IR:")
|
||||||
|
fmt.Println(c.IR())
|
||||||
|
}
|
||||||
|
if err := c.Verify(); err != nil {
|
||||||
|
return errors.New("verification error after IR construction")
|
||||||
|
}
|
||||||
|
|
||||||
|
err = interp.Run(c.Module(), config.DumpSSA())
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := c.Verify(); err != nil {
|
||||||
|
return errors.New("verification error after interpreting runtime.initAll")
|
||||||
|
}
|
||||||
|
|
||||||
|
if config.GOOS() != "darwin" {
|
||||||
|
c.ApplyFunctionSections() // -ffunction-sections
|
||||||
|
}
|
||||||
|
|
||||||
|
// Browsers cannot handle external functions that have type i64 because it
|
||||||
|
// cannot be represented exactly in JavaScript (JS only has doubles). To
|
||||||
|
// keep functions interoperable, pass int64 types as pointers to
|
||||||
|
// stack-allocated values.
|
||||||
|
// Use -wasm-abi=generic to disable this behaviour.
|
||||||
|
if config.Options.WasmAbi == "js" && strings.HasPrefix(config.Triple(), "wasm") {
|
||||||
|
err := c.ExternalInt64AsPtr()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Optimization levels here are roughly the same as Clang, but probably not
|
||||||
|
// exactly.
|
||||||
|
switch config.Options.Opt {
|
||||||
|
case "none:", "0":
|
||||||
|
err = c.Optimize(0, 0, 0) // -O0
|
||||||
|
case "1":
|
||||||
|
err = c.Optimize(1, 0, 0) // -O1
|
||||||
|
case "2":
|
||||||
|
err = c.Optimize(2, 0, 225) // -O2
|
||||||
|
case "s":
|
||||||
|
err = c.Optimize(2, 1, 225) // -Os
|
||||||
|
case "z":
|
||||||
|
err = c.Optimize(2, 2, 5) // -Oz, default
|
||||||
|
default:
|
||||||
|
err = errors.New("unknown optimization level: -opt=" + config.Options.Opt)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := c.Verify(); err != nil {
|
||||||
|
return errors.New("verification failure after LLVM optimization passes")
|
||||||
|
}
|
||||||
|
|
||||||
|
// On the AVR, pointers can point either to flash or to RAM, but we don't
|
||||||
|
// know. As a temporary fix, load all global variables in RAM.
|
||||||
|
// In the future, there should be a compiler pass that determines which
|
||||||
|
// pointers are flash and which are in RAM so that pointers can have a
|
||||||
|
// correct address space parameter (address space 1 is for flash).
|
||||||
|
if strings.HasPrefix(config.Triple(), "avr") {
|
||||||
|
c.NonConstGlobals()
|
||||||
|
if err := c.Verify(); err != nil {
|
||||||
|
return errors.New("verification error after making all globals non-constant on AVR")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Generate output.
|
||||||
|
outext := filepath.Ext(outpath)
|
||||||
|
switch outext {
|
||||||
|
case ".o":
|
||||||
|
return c.EmitObject(outpath)
|
||||||
|
case ".bc":
|
||||||
|
return c.EmitBitcode(outpath)
|
||||||
|
case ".ll":
|
||||||
|
return c.EmitText(outpath)
|
||||||
|
default:
|
||||||
|
// Act as a compiler driver.
|
||||||
|
|
||||||
|
// Create a temporary directory for intermediary files.
|
||||||
|
dir, err := ioutil.TempDir("", "tinygo")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer os.RemoveAll(dir)
|
||||||
|
|
||||||
|
// Write the object file.
|
||||||
|
objfile := filepath.Join(dir, "main.o")
|
||||||
|
err = c.EmitObject(objfile)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load builtins library from the cache, possibly compiling it on the
|
||||||
|
// fly.
|
||||||
|
var librt string
|
||||||
|
if config.Target.RTLib == "compiler-rt" {
|
||||||
|
librt, err = loadBuiltins(config.Triple())
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Prepare link command.
|
||||||
|
executable := filepath.Join(dir, "main")
|
||||||
|
tmppath := executable // final file
|
||||||
|
ldflags := append(config.LDFlags(), "-o", executable, objfile)
|
||||||
|
if config.Target.RTLib == "compiler-rt" {
|
||||||
|
ldflags = append(ldflags, librt)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Compile extra files.
|
||||||
|
root := goenv.Get("TINYGOROOT")
|
||||||
|
for i, path := range config.ExtraFiles() {
|
||||||
|
abspath := filepath.Join(root, path)
|
||||||
|
outpath := filepath.Join(dir, "extra-"+strconv.Itoa(i)+"-"+filepath.Base(path)+".o")
|
||||||
|
cmdNames := []string{config.Target.Compiler}
|
||||||
|
if names, ok := commands[config.Target.Compiler]; ok {
|
||||||
|
cmdNames = names
|
||||||
|
}
|
||||||
|
err := execCommand(cmdNames, append(config.CFlags(), "-c", "-o", outpath, abspath)...)
|
||||||
|
if err != nil {
|
||||||
|
return &commandError{"failed to build", path, err}
|
||||||
|
}
|
||||||
|
ldflags = append(ldflags, outpath)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Compile C files in packages.
|
||||||
|
for i, pkg := range c.Packages() {
|
||||||
|
for _, file := range pkg.CFiles {
|
||||||
|
path := filepath.Join(pkg.Package.Dir, file)
|
||||||
|
outpath := filepath.Join(dir, "pkg"+strconv.Itoa(i)+"-"+file+".o")
|
||||||
|
cmdNames := []string{config.Target.Compiler}
|
||||||
|
if names, ok := commands[config.Target.Compiler]; ok {
|
||||||
|
cmdNames = names
|
||||||
|
}
|
||||||
|
err := execCommand(cmdNames, append(config.CFlags(), "-c", "-o", outpath, path)...)
|
||||||
|
if err != nil {
|
||||||
|
return &commandError{"failed to build", path, err}
|
||||||
|
}
|
||||||
|
ldflags = append(ldflags, outpath)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Link the object files together.
|
||||||
|
err = link(config.Target.Linker, ldflags...)
|
||||||
|
if err != nil {
|
||||||
|
return &commandError{"failed to link", executable, err}
|
||||||
|
}
|
||||||
|
|
||||||
|
if config.Options.PrintSizes == "short" || config.Options.PrintSizes == "full" {
|
||||||
|
sizes, err := loadProgramSize(executable)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if config.Options.PrintSizes == "short" {
|
||||||
|
fmt.Printf(" code data bss | flash ram\n")
|
||||||
|
fmt.Printf("%7d %7d %7d | %7d %7d\n", sizes.Code, sizes.Data, sizes.BSS, sizes.Code+sizes.Data, sizes.Data+sizes.BSS)
|
||||||
|
} else {
|
||||||
|
fmt.Printf(" code rodata data bss | flash ram | package\n")
|
||||||
|
for _, name := range sizes.sortedPackageNames() {
|
||||||
|
pkgSize := sizes.Packages[name]
|
||||||
|
fmt.Printf("%7d %7d %7d %7d | %7d %7d | %s\n", pkgSize.Code, pkgSize.ROData, pkgSize.Data, pkgSize.BSS, pkgSize.Flash(), pkgSize.RAM(), name)
|
||||||
|
}
|
||||||
|
fmt.Printf("%7d %7d %7d %7d | %7d %7d | (sum)\n", sizes.Sum.Code, sizes.Sum.ROData, sizes.Sum.Data, sizes.Sum.BSS, sizes.Sum.Flash(), sizes.Sum.RAM())
|
||||||
|
fmt.Printf("%7d - %7d %7d | %7d %7d | (all)\n", sizes.Code, sizes.Data, sizes.BSS, sizes.Code+sizes.Data, sizes.Data+sizes.BSS)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get an Intel .hex file or .bin file from the .elf file.
|
||||||
|
if outext == ".hex" || outext == ".bin" || outext == ".gba" {
|
||||||
|
tmppath = filepath.Join(dir, "main"+outext)
|
||||||
|
err := objcopy(executable, tmppath)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
} else if outext == ".uf2" {
|
||||||
|
// Get UF2 from the .elf file.
|
||||||
|
tmppath = filepath.Join(dir, "main"+outext)
|
||||||
|
err := convertELFFileToUF2File(executable, tmppath)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return action(tmppath)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
package main
|
package builder
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"io"
|
"io"
|
||||||
@@ -1,15 +1,11 @@
|
|||||||
package main
|
package builder
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
|
||||||
"io"
|
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/blakesmith/ar"
|
|
||||||
"github.com/tinygo-org/tinygo/goenv"
|
"github.com/tinygo-org/tinygo/goenv"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -197,7 +193,7 @@ func loadBuiltins(target string) (path string, err error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var cachepath string
|
var cachepath string
|
||||||
err = compileBuiltins(target, func(path string) error {
|
err = CompileBuiltins(target, func(path string) error {
|
||||||
path, err := cacheStore(path, outfile, commands["clang"][0], srcs)
|
path, err := cacheStore(path, outfile, commands["clang"][0], srcs)
|
||||||
cachepath = path
|
cachepath = path
|
||||||
return err
|
return err
|
||||||
@@ -205,11 +201,11 @@ func loadBuiltins(target string) (path string, err error) {
|
|||||||
return cachepath, err
|
return cachepath, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// compileBuiltins compiles builtins from compiler-rt into a static library.
|
// CompileBuiltins compiles builtins from compiler-rt into a static library.
|
||||||
// When it succeeds, it will call the callback with the resulting path. The path
|
// When it succeeds, it will call the callback with the resulting path. The path
|
||||||
// will be removed after callback returns. If callback returns an error, this is
|
// will be removed after callback returns. If callback returns an error, this is
|
||||||
// passed through to the return value of this function.
|
// passed through to the return value of this function.
|
||||||
func compileBuiltins(target string, callback func(path string) error) error {
|
func CompileBuiltins(target string, callback func(path string) error) error {
|
||||||
builtinsDir := builtinsDir()
|
builtinsDir := builtinsDir()
|
||||||
|
|
||||||
builtins := builtinFiles(target)
|
builtins := builtinFiles(target)
|
||||||
@@ -246,50 +242,15 @@ func compileBuiltins(target string, callback func(path string) error) error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Put all builtins in an archive to link as a static library.
|
// Put all the object files in a single archive. This archive file will be
|
||||||
// Note: this does not create a symbol index, but ld.lld doesn't seem to
|
// used to statically link compiler-rt.
|
||||||
// care.
|
|
||||||
arpath := filepath.Join(dir, "librt.a")
|
arpath := filepath.Join(dir, "librt.a")
|
||||||
arfile, err := os.Create(arpath)
|
err = makeArchive(arpath, objs)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
defer arfile.Close()
|
|
||||||
arwriter := ar.NewWriter(arfile)
|
|
||||||
err = arwriter.WriteGlobalHeader()
|
|
||||||
if err != nil {
|
|
||||||
return &os.PathError{"write ar header", arpath, err}
|
|
||||||
}
|
|
||||||
for _, objpath := range objs {
|
|
||||||
name := filepath.Base(objpath)
|
|
||||||
objfile, err := os.Open(objpath)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer objfile.Close()
|
|
||||||
st, err := objfile.Stat()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
arwriter.WriteHeader(&ar.Header{
|
|
||||||
Name: name,
|
|
||||||
ModTime: time.Unix(0, 0),
|
|
||||||
Uid: 0,
|
|
||||||
Gid: 0,
|
|
||||||
Mode: 0644,
|
|
||||||
Size: st.Size(),
|
|
||||||
})
|
|
||||||
n, err := io.Copy(arwriter, objfile)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if n != st.Size() {
|
|
||||||
return errors.New("file modified during ar creation: " + arpath)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Give the caller the resulting file. The callback must copy the file,
|
// Give the caller the resulting file. The callback must copy the file,
|
||||||
// because after it returns the temporary directory will be removed.
|
// because after it returns the temporary directory will be removed.
|
||||||
arfile.Close()
|
|
||||||
return callback(arpath)
|
return callback(arpath)
|
||||||
}
|
}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
package main
|
package builder
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
@@ -8,21 +8,23 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Commands used by the compilation process might have different file names
|
// Commands lists command alternatives for various operating systems. These
|
||||||
// across operating systems and distributions.
|
// commands may have a slightly different name across operating systems and
|
||||||
|
// distributions or may not even exist in $PATH, in which case absolute paths
|
||||||
|
// may be used.
|
||||||
var commands = map[string][]string{
|
var commands = map[string][]string{
|
||||||
"clang": {"clang-8"},
|
"clang": {"clang-9"},
|
||||||
"ld.lld": {"ld.lld-8", "ld.lld"},
|
"ld.lld": {"ld.lld-9", "ld.lld"},
|
||||||
"wasm-ld": {"wasm-ld-8", "wasm-ld"},
|
"wasm-ld": {"wasm-ld-9", "wasm-ld"},
|
||||||
}
|
}
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
// Add the path to a Homebrew-installed LLVM 8 for ease of use (no need to
|
// Add the path to a Homebrew-installed LLVM 9 for ease of use (no need to
|
||||||
// manually set $PATH).
|
// manually set $PATH).
|
||||||
if runtime.GOOS == "darwin" {
|
if runtime.GOOS == "darwin" {
|
||||||
commands["clang"] = append(commands["clang"], "/usr/local/opt/llvm/bin/clang-8")
|
commands["clang"] = append(commands["clang"], "/usr/local/opt/llvm@9/bin/clang-9")
|
||||||
commands["ld.lld"] = append(commands["ld.lld"], "/usr/local/opt/llvm/bin/ld.lld")
|
commands["ld.lld"] = append(commands["ld.lld"], "/usr/local/opt/llvm@9/bin/ld.lld")
|
||||||
commands["wasm-ld"] = append(commands["wasm-ld"], "/usr/local/opt/llvm/bin/wasm-ld")
|
commands["wasm-ld"] = append(commands["wasm-ld"], "/usr/local/opt/llvm@9/bin/wasm-ld")
|
||||||
}
|
}
|
||||||
// Add the path for when LLVM was installed with the installer from
|
// Add the path for when LLVM was installed with the installer from
|
||||||
// llvm.org, which by default doesn't add LLVM to the $PATH environment
|
// llvm.org, which by default doesn't add LLVM to the $PATH environment
|
||||||
@@ -0,0 +1,39 @@
|
|||||||
|
package builder
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/tinygo-org/tinygo/compileopts"
|
||||||
|
"github.com/tinygo-org/tinygo/goenv"
|
||||||
|
)
|
||||||
|
|
||||||
|
// NewConfig builds a new Config object from a set of compiler options. It also
|
||||||
|
// loads some information from the environment while doing that. For example, it
|
||||||
|
// uses the currently active GOPATH (from the goenv package) to determine the Go
|
||||||
|
// version to use.
|
||||||
|
func NewConfig(options *compileopts.Options) (*compileopts.Config, error) {
|
||||||
|
spec, err := compileopts.LoadTarget(options.Target)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
goroot := goenv.Get("GOROOT")
|
||||||
|
if goroot == "" {
|
||||||
|
return nil, errors.New("cannot locate $GOROOT, please set it manually")
|
||||||
|
}
|
||||||
|
major, minor, err := getGorootVersion(goroot)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("could not read version from GOROOT (%v): %v", goroot, err)
|
||||||
|
}
|
||||||
|
if major != 1 || (minor != 11 && minor != 12 && minor != 13) {
|
||||||
|
return nil, fmt.Errorf("requires go version 1.11, 1.12, or 1.13, got go%d.%d", major, minor)
|
||||||
|
}
|
||||||
|
clangHeaderPath := getClangHeaderPath(goenv.Get("TINYGOROOT"))
|
||||||
|
return &compileopts.Config{
|
||||||
|
Options: options,
|
||||||
|
Target: spec,
|
||||||
|
GoMinorVersion: minor,
|
||||||
|
ClangHeaders: clangHeaderPath,
|
||||||
|
TestConfig: options.TestConfig,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
+127
@@ -0,0 +1,127 @@
|
|||||||
|
package builder
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"io/ioutil"
|
||||||
|
"os"
|
||||||
|
"os/exec"
|
||||||
|
"path/filepath"
|
||||||
|
"regexp"
|
||||||
|
"sort"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// getGorootVersion returns the major and minor version for a given GOROOT path.
|
||||||
|
// If the goroot cannot be determined, (0, 0) is returned.
|
||||||
|
func getGorootVersion(goroot string) (major, minor int, err error) {
|
||||||
|
s, err := GorootVersionString(goroot)
|
||||||
|
if err != nil {
|
||||||
|
return 0, 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if s == "" || s[:2] != "go" {
|
||||||
|
return 0, 0, errors.New("could not parse Go version: version does not start with 'go' prefix")
|
||||||
|
}
|
||||||
|
|
||||||
|
parts := strings.Split(s[2:], ".")
|
||||||
|
if len(parts) < 2 {
|
||||||
|
return 0, 0, errors.New("could not parse Go version: version has less than two parts")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ignore the errors, we don't really handle errors here anyway.
|
||||||
|
var trailing string
|
||||||
|
n, err := fmt.Sscanf(s, "go%d.%d%s", &major, &minor, &trailing)
|
||||||
|
if n == 2 && err == io.EOF {
|
||||||
|
// Means there were no trailing characters (i.e., not an alpha/beta)
|
||||||
|
err = nil
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return 0, 0, fmt.Errorf("failed to parse version: %s", err)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// GorootVersionString returns the version string as reported by the Go
|
||||||
|
// toolchain for the given GOROOT path. It is usually of the form `go1.x.y` but
|
||||||
|
// can have some variations (for beta releases, for example).
|
||||||
|
func GorootVersionString(goroot string) (string, error) {
|
||||||
|
if data, err := ioutil.ReadFile(filepath.Join(
|
||||||
|
goroot, "src", "runtime", "internal", "sys", "zversion.go")); err == nil {
|
||||||
|
|
||||||
|
r := regexp.MustCompile("const TheVersion = `(.*)`")
|
||||||
|
matches := r.FindSubmatch(data)
|
||||||
|
if len(matches) != 2 {
|
||||||
|
return "", errors.New("Invalid go version output:\n" + string(data))
|
||||||
|
}
|
||||||
|
|
||||||
|
return string(matches[1]), nil
|
||||||
|
|
||||||
|
} else if data, err := ioutil.ReadFile(filepath.Join(goroot, "VERSION")); err == nil {
|
||||||
|
return string(data), nil
|
||||||
|
|
||||||
|
} else {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// getClangHeaderPath returns the path to the built-in Clang headers. It tries
|
||||||
|
// multiple locations, which should make it find the directory when installed in
|
||||||
|
// various ways.
|
||||||
|
func getClangHeaderPath(TINYGOROOT string) string {
|
||||||
|
// Check whether we're running from the source directory.
|
||||||
|
path := filepath.Join(TINYGOROOT, "llvm", "tools", "clang", "lib", "Headers")
|
||||||
|
if _, err := os.Stat(path); !os.IsNotExist(err) {
|
||||||
|
return path
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check whether we're running from the installation directory.
|
||||||
|
path = filepath.Join(TINYGOROOT, "lib", "clang", "include")
|
||||||
|
if _, err := os.Stat(path); !os.IsNotExist(err) {
|
||||||
|
return path
|
||||||
|
}
|
||||||
|
|
||||||
|
// It looks like we are built with a system-installed LLVM. Do a last
|
||||||
|
// attempt: try to use Clang headers relative to the clang binary.
|
||||||
|
for _, cmdName := range commands["clang"] {
|
||||||
|
binpath, err := exec.LookPath(cmdName)
|
||||||
|
if err == nil {
|
||||||
|
// This should be the command that will also be used by
|
||||||
|
// execCommand. To avoid inconsistencies, make sure we use the
|
||||||
|
// headers relative to this command.
|
||||||
|
binpath, err = filepath.EvalSymlinks(binpath)
|
||||||
|
if err != nil {
|
||||||
|
// Unexpected.
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
// Example executable:
|
||||||
|
// /usr/lib/llvm-9/bin/clang
|
||||||
|
// Example include path:
|
||||||
|
// /usr/lib/llvm-9/lib/clang/9.0.1/include/
|
||||||
|
llvmRoot := filepath.Dir(filepath.Dir(binpath))
|
||||||
|
clangVersionRoot := filepath.Join(llvmRoot, "lib", "clang")
|
||||||
|
dirs, err := ioutil.ReadDir(clangVersionRoot)
|
||||||
|
if err != nil {
|
||||||
|
// Unexpected.
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
dirnames := make([]string, len(dirs))
|
||||||
|
for i, d := range dirs {
|
||||||
|
dirnames[i] = d.Name()
|
||||||
|
}
|
||||||
|
sort.Strings(dirnames)
|
||||||
|
// Check for the highest version first.
|
||||||
|
for i := len(dirnames) - 1; i >= 0; i-- {
|
||||||
|
path := filepath.Join(clangVersionRoot, dirnames[i], "include")
|
||||||
|
_, err := os.Stat(filepath.Join(path, "stdint.h"))
|
||||||
|
if err == nil {
|
||||||
|
return path
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Could not find it.
|
||||||
|
return ""
|
||||||
|
}
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
package builder
|
||||||
|
|
||||||
|
// MultiError is a list of multiple errors (actually: diagnostics) returned
|
||||||
|
// during LLVM IR generation.
|
||||||
|
type MultiError struct {
|
||||||
|
Errs []error
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *MultiError) Error() string {
|
||||||
|
// Return the first error, to conform to the error interface. Clients should
|
||||||
|
// really do a type-assertion on *MultiError.
|
||||||
|
return e.Errs[0].Error()
|
||||||
|
}
|
||||||
|
|
||||||
|
// commandError is an error type to wrap os/exec.Command errors. This provides
|
||||||
|
// some more information regarding what went wrong while running a command.
|
||||||
|
type commandError struct {
|
||||||
|
Msg string
|
||||||
|
File string
|
||||||
|
Err error
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *commandError) Error() string {
|
||||||
|
return e.Msg + " " + e.File + ": " + e.Err.Error()
|
||||||
|
}
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
// +build byollvm
|
// +build byollvm
|
||||||
|
|
||||||
package main
|
package builder
|
||||||
|
|
||||||
// This file provides a Link() function that uses the bundled lld if possible.
|
// This file provides a Link() function that uses the bundled lld if possible.
|
||||||
|
|
||||||
@@ -21,10 +21,10 @@ bool tinygo_link_wasm(int argc, char **argv);
|
|||||||
*/
|
*/
|
||||||
import "C"
|
import "C"
|
||||||
|
|
||||||
// Link invokes a linker with the given name and flags.
|
// link invokes a linker with the given name and flags.
|
||||||
//
|
//
|
||||||
// This version uses the built-in linker when trying to use lld.
|
// This version uses the built-in linker when trying to use lld.
|
||||||
func Link(linker string, flags ...string) error {
|
func link(linker string, flags ...string) error {
|
||||||
switch linker {
|
switch linker {
|
||||||
case "ld.lld":
|
case "ld.lld":
|
||||||
flags = append([]string{"tinygo:" + linker}, flags...)
|
flags = append([]string{"tinygo:" + linker}, flags...)
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
// +build !byollvm
|
// +build !byollvm
|
||||||
|
|
||||||
package main
|
package builder
|
||||||
|
|
||||||
// This file provides a Link() function that always runs an external command. It
|
// This file provides a Link() function that always runs an external command. It
|
||||||
// is provided for when tinygo is built without linking to liblld.
|
// is provided for when tinygo is built without linking to liblld.
|
||||||
@@ -12,10 +12,10 @@ import (
|
|||||||
"github.com/tinygo-org/tinygo/goenv"
|
"github.com/tinygo-org/tinygo/goenv"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Link invokes a linker with the given name and arguments.
|
// link invokes a linker with the given name and arguments.
|
||||||
//
|
//
|
||||||
// This version always runs the linker as an external command.
|
// This version always runs the linker as an external command.
|
||||||
func Link(linker string, flags ...string) error {
|
func link(linker string, flags ...string) error {
|
||||||
if cmdNames, ok := commands[linker]; ok {
|
if cmdNames, ok := commands[linker]; ok {
|
||||||
return execCommand(cmdNames, flags...)
|
return execCommand(cmdNames, flags...)
|
||||||
}
|
}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
package main
|
package builder
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"debug/elf"
|
"debug/elf"
|
||||||
@@ -10,31 +10,31 @@ import (
|
|||||||
"github.com/marcinbor85/gohex"
|
"github.com/marcinbor85/gohex"
|
||||||
)
|
)
|
||||||
|
|
||||||
// ObjcopyError is an error returned by functions that act like objcopy.
|
// objcopyError is an error returned by functions that act like objcopy.
|
||||||
type ObjcopyError struct {
|
type objcopyError struct {
|
||||||
Op string
|
Op string
|
||||||
Err error
|
Err error
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e ObjcopyError) Error() string {
|
func (e objcopyError) Error() string {
|
||||||
if e.Err == nil {
|
if e.Err == nil {
|
||||||
return e.Op
|
return e.Op
|
||||||
}
|
}
|
||||||
return e.Op + ": " + e.Err.Error()
|
return e.Op + ": " + e.Err.Error()
|
||||||
}
|
}
|
||||||
|
|
||||||
type ProgSlice []*elf.Prog
|
type progSlice []*elf.Prog
|
||||||
|
|
||||||
func (s ProgSlice) Len() int { return len(s) }
|
func (s progSlice) Len() int { return len(s) }
|
||||||
func (s ProgSlice) Less(i, j int) bool { return s[i].Paddr < s[j].Paddr }
|
func (s progSlice) Less(i, j int) bool { return s[i].Paddr < s[j].Paddr }
|
||||||
func (s ProgSlice) Swap(i, j int) { s[i], s[j] = s[j], s[i] }
|
func (s progSlice) Swap(i, j int) { s[i], s[j] = s[j], s[i] }
|
||||||
|
|
||||||
// ExtractROM extracts a firmware image and the first load address from the
|
// extractROM extracts a firmware image and the first load address from the
|
||||||
// given ELF file. It tries to emulate the behavior of objcopy.
|
// given ELF file. It tries to emulate the behavior of objcopy.
|
||||||
func ExtractROM(path string) (uint64, []byte, error) {
|
func extractROM(path string) (uint64, []byte, error) {
|
||||||
f, err := elf.Open(path)
|
f, err := elf.Open(path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, nil, ObjcopyError{"failed to open ELF file to extract text segment", err}
|
return 0, nil, objcopyError{"failed to open ELF file to extract text segment", err}
|
||||||
}
|
}
|
||||||
defer f.Close()
|
defer f.Close()
|
||||||
|
|
||||||
@@ -56,7 +56,7 @@ func ExtractROM(path string) (uint64, []byte, error) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
progs := make(ProgSlice, 0, 2)
|
progs := make(progSlice, 0, 2)
|
||||||
for _, prog := range f.Progs {
|
for _, prog := range f.Progs {
|
||||||
if prog.Type != elf.PT_LOAD || prog.Filesz == 0 {
|
if prog.Type != elf.PT_LOAD || prog.Filesz == 0 {
|
||||||
continue
|
continue
|
||||||
@@ -64,18 +64,18 @@ func ExtractROM(path string) (uint64, []byte, error) {
|
|||||||
progs = append(progs, prog)
|
progs = append(progs, prog)
|
||||||
}
|
}
|
||||||
if len(progs) == 0 {
|
if len(progs) == 0 {
|
||||||
return 0, nil, ObjcopyError{"file does not contain ROM segments: " + path, nil}
|
return 0, nil, objcopyError{"file does not contain ROM segments: " + path, nil}
|
||||||
}
|
}
|
||||||
sort.Sort(progs)
|
sort.Sort(progs)
|
||||||
|
|
||||||
var rom []byte
|
var rom []byte
|
||||||
for _, prog := range progs {
|
for _, prog := range progs {
|
||||||
if prog.Paddr != progs[0].Paddr+uint64(len(rom)) {
|
if prog.Paddr != progs[0].Paddr+uint64(len(rom)) {
|
||||||
return 0, nil, ObjcopyError{"ROM segments are non-contiguous: " + path, nil}
|
return 0, nil, objcopyError{"ROM segments are non-contiguous: " + path, nil}
|
||||||
}
|
}
|
||||||
data, err := ioutil.ReadAll(prog.Open())
|
data, err := ioutil.ReadAll(prog.Open())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, nil, ObjcopyError{"failed to extract segment from ELF file: " + path, err}
|
return 0, nil, objcopyError{"failed to extract segment from ELF file: " + path, err}
|
||||||
}
|
}
|
||||||
rom = append(rom, data...)
|
rom = append(rom, data...)
|
||||||
}
|
}
|
||||||
@@ -91,9 +91,9 @@ func ExtractROM(path string) (uint64, []byte, error) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Objcopy converts an ELF file to a different (simpler) output file format:
|
// objcopy converts an ELF file to a different (simpler) output file format:
|
||||||
// .bin or .hex. It extracts only the .text section.
|
// .bin or .hex. It extracts only the .text section.
|
||||||
func Objcopy(infile, outfile string) error {
|
func objcopy(infile, outfile string) error {
|
||||||
f, err := os.OpenFile(outfile, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0666)
|
f, err := os.OpenFile(outfile, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0666)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -101,7 +101,7 @@ func Objcopy(infile, outfile string) error {
|
|||||||
defer f.Close()
|
defer f.Close()
|
||||||
|
|
||||||
// Read the .text segment.
|
// Read the .text segment.
|
||||||
addr, data, err := ExtractROM(infile)
|
addr, data, err := extractROM(infile)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -121,7 +121,7 @@ func Objcopy(infile, outfile string) error {
|
|||||||
mem := gohex.NewMemory()
|
mem := gohex.NewMemory()
|
||||||
err := mem.AddBinary(uint32(addr), data)
|
err := mem.AddBinary(uint32(addr), data)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return ObjcopyError{"failed to create .hex file", err}
|
return objcopyError{"failed to create .hex file", err}
|
||||||
}
|
}
|
||||||
mem.DumpIntelHex(f, 16) // TODO: handle error
|
mem.DumpIntelHex(f, 16) // TODO: handle error
|
||||||
return nil
|
return nil
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
package main
|
package builder
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"debug/elf"
|
"debug/elf"
|
||||||
@@ -6,18 +6,18 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Statistics about code size in a program.
|
// programSize contains size statistics per package of a compiled program.
|
||||||
type ProgramSize struct {
|
type programSize struct {
|
||||||
Packages map[string]*PackageSize
|
Packages map[string]*packageSize
|
||||||
Sum *PackageSize
|
Sum *packageSize
|
||||||
Code uint64
|
Code uint64
|
||||||
Data uint64
|
Data uint64
|
||||||
BSS uint64
|
BSS uint64
|
||||||
}
|
}
|
||||||
|
|
||||||
// Return the list of package names (ProgramSize.Packages) sorted
|
// sortedPackageNames returns the list of package names (ProgramSize.Packages)
|
||||||
// alphabetically.
|
// sorted alphabetically.
|
||||||
func (ps *ProgramSize) SortedPackageNames() []string {
|
func (ps *programSize) sortedPackageNames() []string {
|
||||||
names := make([]string, 0, len(ps.Packages))
|
names := make([]string, 0, len(ps.Packages))
|
||||||
for name := range ps.Packages {
|
for name := range ps.Packages {
|
||||||
names = append(names, name)
|
names = append(names, name)
|
||||||
@@ -26,8 +26,9 @@ func (ps *ProgramSize) SortedPackageNames() []string {
|
|||||||
return names
|
return names
|
||||||
}
|
}
|
||||||
|
|
||||||
// The size of a package, calculated from the linked object file.
|
// packageSize contains the size of a package, calculated from the linked object
|
||||||
type PackageSize struct {
|
// file.
|
||||||
|
type packageSize struct {
|
||||||
Code uint64
|
Code uint64
|
||||||
ROData uint64
|
ROData uint64
|
||||||
Data uint64
|
Data uint64
|
||||||
@@ -35,12 +36,12 @@ type PackageSize struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Flash usage in regular microcontrollers.
|
// Flash usage in regular microcontrollers.
|
||||||
func (ps *PackageSize) Flash() uint64 {
|
func (ps *packageSize) Flash() uint64 {
|
||||||
return ps.Code + ps.ROData + ps.Data
|
return ps.Code + ps.ROData + ps.Data
|
||||||
}
|
}
|
||||||
|
|
||||||
// Static RAM usage in regular microcontrollers.
|
// Static RAM usage in regular microcontrollers.
|
||||||
func (ps *PackageSize) RAM() uint64 {
|
func (ps *packageSize) RAM() uint64 {
|
||||||
return ps.Data + ps.BSS
|
return ps.Data + ps.BSS
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -64,8 +65,9 @@ func (l symbolList) Swap(i, j int) {
|
|||||||
l[i], l[j] = l[j], l[i]
|
l[i], l[j] = l[j], l[i]
|
||||||
}
|
}
|
||||||
|
|
||||||
// Calculate program/data size breakdown of each package for a given ELF file.
|
// loadProgramSize calculate a program/data size breakdown of each package for a
|
||||||
func Sizes(path string) (*ProgramSize, error) {
|
// given ELF file.
|
||||||
|
func loadProgramSize(path string) (*programSize, error) {
|
||||||
file, err := elf.Open(path)
|
file, err := elf.Open(path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@@ -115,7 +117,7 @@ func Sizes(path string) (*ProgramSize, error) {
|
|||||||
}
|
}
|
||||||
sort.Sort(symbolList(symbols))
|
sort.Sort(symbolList(symbols))
|
||||||
|
|
||||||
sizes := map[string]*PackageSize{}
|
sizes := map[string]*packageSize{}
|
||||||
var lastSymbolValue uint64
|
var lastSymbolValue uint64
|
||||||
for _, symbol := range symbols {
|
for _, symbol := range symbols {
|
||||||
symType := elf.ST_TYPE(symbol.Info)
|
symType := elf.ST_TYPE(symbol.Info)
|
||||||
@@ -129,7 +131,7 @@ func Sizes(path string) (*ProgramSize, error) {
|
|||||||
}
|
}
|
||||||
pkgSize := sizes[pkgName]
|
pkgSize := sizes[pkgName]
|
||||||
if pkgSize == nil {
|
if pkgSize == nil {
|
||||||
pkgSize = &PackageSize{}
|
pkgSize = &packageSize{}
|
||||||
sizes[pkgName] = pkgSize
|
sizes[pkgName] = pkgSize
|
||||||
}
|
}
|
||||||
if lastSymbolValue != symbol.Value || lastSymbolValue == 0 {
|
if lastSymbolValue != symbol.Value || lastSymbolValue == 0 {
|
||||||
@@ -148,7 +150,7 @@ func Sizes(path string) (*ProgramSize, error) {
|
|||||||
lastSymbolValue = symbol.Value
|
lastSymbolValue = symbol.Value
|
||||||
}
|
}
|
||||||
|
|
||||||
sum := &PackageSize{}
|
sum := &packageSize{}
|
||||||
for _, pkg := range sizes {
|
for _, pkg := range sizes {
|
||||||
sum.Code += pkg.Code
|
sum.Code += pkg.Code
|
||||||
sum.ROData += pkg.ROData
|
sum.ROData += pkg.ROData
|
||||||
@@ -156,5 +158,5 @@ func Sizes(path string) (*ProgramSize, error) {
|
|||||||
sum.BSS += pkg.BSS
|
sum.BSS += pkg.BSS
|
||||||
}
|
}
|
||||||
|
|
||||||
return &ProgramSize{Packages: sizes, Code: sumCode, Data: sumData, BSS: sumBSS, Sum: sum}, nil
|
return &programSize{Packages: sizes, Code: sumCode, Data: sumData, BSS: sumBSS, Sum: sum}, nil
|
||||||
}
|
}
|
||||||
+21
-20
@@ -1,10 +1,11 @@
|
|||||||
// Converts firmware files from BIN to UF2 format before flashing.
|
package builder
|
||||||
|
|
||||||
|
// This file converts firmware files from BIN to UF2 format before flashing.
|
||||||
//
|
//
|
||||||
// For more information about the UF2 firmware file format, please see:
|
// For more information about the UF2 firmware file format, please see:
|
||||||
// https://github.com/Microsoft/uf2
|
// https://github.com/Microsoft/uf2
|
||||||
//
|
//
|
||||||
//
|
//
|
||||||
package main
|
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
@@ -12,24 +13,24 @@ import (
|
|||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
)
|
)
|
||||||
|
|
||||||
// ConvertELFFileToUF2File converts an ELF file to a UF2 file.
|
// convertELFFileToUF2File converts an ELF file to a UF2 file.
|
||||||
func ConvertELFFileToUF2File(infile, outfile string) error {
|
func convertELFFileToUF2File(infile, outfile string) error {
|
||||||
// Read the .text segment.
|
// Read the .text segment.
|
||||||
targetAddress, data, err := ExtractROM(infile)
|
targetAddress, data, err := extractROM(infile)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
output, _ := ConvertBinToUF2(data, uint32(targetAddress))
|
output, _ := convertBinToUF2(data, uint32(targetAddress))
|
||||||
return ioutil.WriteFile(outfile, output, 0644)
|
return ioutil.WriteFile(outfile, output, 0644)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ConvertBinToUF2 converts the binary bytes in input to UF2 formatted data.
|
// convertBinToUF2 converts the binary bytes in input to UF2 formatted data.
|
||||||
func ConvertBinToUF2(input []byte, targetAddr uint32) ([]byte, int) {
|
func convertBinToUF2(input []byte, targetAddr uint32) ([]byte, int) {
|
||||||
blocks := split(input, 256)
|
blocks := split(input, 256)
|
||||||
output := make([]byte, 0)
|
output := make([]byte, 0)
|
||||||
|
|
||||||
bl := NewUF2Block(targetAddr)
|
bl := newUF2Block(targetAddr)
|
||||||
bl.SetNumBlocks(len(blocks))
|
bl.SetNumBlocks(len(blocks))
|
||||||
|
|
||||||
for i := 0; i < len(blocks); i++ {
|
for i := 0; i < len(blocks); i++ {
|
||||||
@@ -49,8 +50,8 @@ const (
|
|||||||
uf2MagicEnd = 0x0AB16F30 // Ditto
|
uf2MagicEnd = 0x0AB16F30 // Ditto
|
||||||
)
|
)
|
||||||
|
|
||||||
// UF2Block is the structure used for each UF2 code block sent to device.
|
// uf2Block is the structure used for each UF2 code block sent to device.
|
||||||
type UF2Block struct {
|
type uf2Block struct {
|
||||||
magicStart0 uint32
|
magicStart0 uint32
|
||||||
magicStart1 uint32
|
magicStart1 uint32
|
||||||
flags uint32
|
flags uint32
|
||||||
@@ -63,9 +64,9 @@ type UF2Block struct {
|
|||||||
magicEnd uint32
|
magicEnd uint32
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewUF2Block returns a new UF2Block struct that has been correctly populated
|
// newUF2Block returns a new uf2Block struct that has been correctly populated
|
||||||
func NewUF2Block(targetAddr uint32) *UF2Block {
|
func newUF2Block(targetAddr uint32) *uf2Block {
|
||||||
return &UF2Block{magicStart0: uf2MagicStart0,
|
return &uf2Block{magicStart0: uf2MagicStart0,
|
||||||
magicStart1: uf2MagicStart1,
|
magicStart1: uf2MagicStart1,
|
||||||
magicEnd: uf2MagicEnd,
|
magicEnd: uf2MagicEnd,
|
||||||
targetAddr: targetAddr,
|
targetAddr: targetAddr,
|
||||||
@@ -76,8 +77,8 @@ func NewUF2Block(targetAddr uint32) *UF2Block {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Bytes converts the UF2Block to a slice of bytes that can be written to file.
|
// Bytes converts the uf2Block to a slice of bytes that can be written to file.
|
||||||
func (b *UF2Block) Bytes() []byte {
|
func (b *uf2Block) Bytes() []byte {
|
||||||
buf := bytes.NewBuffer(make([]byte, 0, 512))
|
buf := bytes.NewBuffer(make([]byte, 0, 512))
|
||||||
binary.Write(buf, binary.LittleEndian, b.magicStart0)
|
binary.Write(buf, binary.LittleEndian, b.magicStart0)
|
||||||
binary.Write(buf, binary.LittleEndian, b.magicStart1)
|
binary.Write(buf, binary.LittleEndian, b.magicStart1)
|
||||||
@@ -94,23 +95,23 @@ func (b *UF2Block) Bytes() []byte {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// IncrementAddress moves the target address pointer forward by count bytes.
|
// IncrementAddress moves the target address pointer forward by count bytes.
|
||||||
func (b *UF2Block) IncrementAddress(count uint32) {
|
func (b *uf2Block) IncrementAddress(count uint32) {
|
||||||
b.targetAddr += b.payloadSize
|
b.targetAddr += b.payloadSize
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetData sets the data to be used for the current block.
|
// SetData sets the data to be used for the current block.
|
||||||
func (b *UF2Block) SetData(d []byte) {
|
func (b *uf2Block) SetData(d []byte) {
|
||||||
b.data = make([]byte, 476)
|
b.data = make([]byte, 476)
|
||||||
copy(b.data[:], d)
|
copy(b.data[:], d)
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetBlockNo sets the current block number to be used.
|
// SetBlockNo sets the current block number to be used.
|
||||||
func (b *UF2Block) SetBlockNo(bn int) {
|
func (b *uf2Block) SetBlockNo(bn int) {
|
||||||
b.blockNo = uint32(bn)
|
b.blockNo = uint32(bn)
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetNumBlocks sets the total number of blocks for this UF2 file.
|
// SetNumBlocks sets the total number of blocks for this UF2 file.
|
||||||
func (b *UF2Block) SetNumBlocks(total int) {
|
func (b *uf2Block) SetNumBlocks(total int) {
|
||||||
b.numBlocks = uint32(total)
|
b.numBlocks = uint32(total)
|
||||||
}
|
}
|
||||||
|
|
||||||
+402
-70
@@ -12,12 +12,14 @@ package cgo
|
|||||||
// source file parsing.
|
// source file parsing.
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"go/ast"
|
"go/ast"
|
||||||
"go/token"
|
"go/token"
|
||||||
"sort"
|
"sort"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"github.com/google/shlex"
|
||||||
"golang.org/x/tools/go/ast/astutil"
|
"golang.org/x/tools/go/ast/astutil"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -42,7 +44,7 @@ type cgoPackage struct {
|
|||||||
// constantInfo stores some information about a CGo constant found by libclang
|
// constantInfo stores some information about a CGo constant found by libclang
|
||||||
// and declared in the Go AST.
|
// and declared in the Go AST.
|
||||||
type constantInfo struct {
|
type constantInfo struct {
|
||||||
expr *ast.BasicLit
|
expr ast.Expr
|
||||||
pos token.Pos
|
pos token.Pos
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -69,9 +71,11 @@ type typedefInfo struct {
|
|||||||
// elaboratedTypeInfo contains some information about an elaborated type
|
// elaboratedTypeInfo contains some information about an elaborated type
|
||||||
// (struct, union) found in the C AST.
|
// (struct, union) found in the C AST.
|
||||||
type elaboratedTypeInfo struct {
|
type elaboratedTypeInfo struct {
|
||||||
typeExpr *ast.StructType
|
typeExpr *ast.StructType
|
||||||
pos token.Pos
|
pos token.Pos
|
||||||
bitfields []bitfieldInfo
|
bitfields []bitfieldInfo
|
||||||
|
unionSize int64 // union size in bytes, nonzero when union getters/setters should be created
|
||||||
|
unionAlign int64 // union alignment in bytes
|
||||||
}
|
}
|
||||||
|
|
||||||
// bitfieldInfo contains information about a single bitfield in a struct. It
|
// bitfieldInfo contains information about a single bitfield in a struct. It
|
||||||
@@ -131,6 +135,7 @@ var builtinAliases = map[string]struct{}{
|
|||||||
// somehow from C. This is done by adding some typedefs to get the size of each
|
// somehow from C. This is done by adding some typedefs to get the size of each
|
||||||
// type.
|
// type.
|
||||||
const cgoTypes = `
|
const cgoTypes = `
|
||||||
|
# 1 "<cgo>"
|
||||||
typedef char _Cgo_char;
|
typedef char _Cgo_char;
|
||||||
typedef signed char _Cgo_schar;
|
typedef signed char _Cgo_schar;
|
||||||
typedef unsigned char _Cgo_uchar;
|
typedef unsigned char _Cgo_uchar;
|
||||||
@@ -184,6 +189,7 @@ func Process(files []*ast.File, dir string, fset *token.FileSet, cflags []string
|
|||||||
Name: files[0].Name.Name,
|
Name: files[0].Name.Name,
|
||||||
},
|
},
|
||||||
Decls: []ast.Decl{
|
Decls: []ast.Decl{
|
||||||
|
// import "unsafe"
|
||||||
&ast.GenDecl{
|
&ast.GenDecl{
|
||||||
TokPos: p.generatedPos,
|
TokPos: p.generatedPos,
|
||||||
Tok: token.IMPORT,
|
Tok: token.IMPORT,
|
||||||
@@ -191,6 +197,32 @@ func Process(files []*ast.File, dir string, fset *token.FileSet, cflags []string
|
|||||||
unsafeImport,
|
unsafeImport,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
// var _ unsafe.Pointer
|
||||||
|
// This avoids type errors when the unsafe package is never used.
|
||||||
|
&ast.GenDecl{
|
||||||
|
Tok: token.VAR,
|
||||||
|
Specs: []ast.Spec{
|
||||||
|
&ast.ValueSpec{
|
||||||
|
Names: []*ast.Ident{
|
||||||
|
&ast.Ident{
|
||||||
|
Name: "_",
|
||||||
|
Obj: &ast.Object{
|
||||||
|
Kind: ast.Var,
|
||||||
|
Name: "_",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Type: &ast.SelectorExpr{
|
||||||
|
X: &ast.Ident{
|
||||||
|
Name: "unsafe",
|
||||||
|
},
|
||||||
|
Sel: &ast.Ident{
|
||||||
|
Name: "Pointer",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
Imports: []*ast.ImportSpec{unsafeImport},
|
Imports: []*ast.ImportSpec{unsafeImport},
|
||||||
}
|
}
|
||||||
@@ -204,6 +236,7 @@ func Process(files []*ast.File, dir string, fset *token.FileSet, cflags []string
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Find `import "C"` statements in the file.
|
// Find `import "C"` statements in the file.
|
||||||
|
var statements []*ast.GenDecl
|
||||||
for _, f := range files {
|
for _, f := range files {
|
||||||
for i := 0; i < len(f.Decls); i++ {
|
for i := 0; i < len(f.Decls); i++ {
|
||||||
decl := f.Decls[i]
|
decl := f.Decls[i]
|
||||||
@@ -220,19 +253,16 @@ func Process(files []*ast.File, dir string, fset *token.FileSet, cflags []string
|
|||||||
}
|
}
|
||||||
path, err := strconv.Unquote(spec.Path.Value)
|
path, err := strconv.Unquote(spec.Path.Value)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
// This should not happen. An import path that is not properly
|
||||||
|
// quoted should not exist in a correct AST.
|
||||||
panic("could not parse import path: " + err.Error())
|
panic("could not parse import path: " + err.Error())
|
||||||
}
|
}
|
||||||
if path != "C" {
|
if path != "C" {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
cgoComment := genDecl.Doc.Text()
|
|
||||||
|
|
||||||
pos := genDecl.Pos()
|
// Found a CGo statement.
|
||||||
if genDecl.Doc != nil {
|
statements = append(statements, genDecl)
|
||||||
pos = genDecl.Doc.Pos()
|
|
||||||
}
|
|
||||||
position := fset.PositionFor(pos, true)
|
|
||||||
p.parseFragment(cgoComment+cgoTypes, cflags, position.Filename, position.Line)
|
|
||||||
|
|
||||||
// Remove this import declaration.
|
// Remove this import declaration.
|
||||||
f.Decls = append(f.Decls[:i], f.Decls[i+1:]...)
|
f.Decls = append(f.Decls[:i], f.Decls[i+1:]...)
|
||||||
@@ -243,6 +273,93 @@ func Process(files []*ast.File, dir string, fset *token.FileSet, cflags []string
|
|||||||
//ast.Print(fset, f)
|
//ast.Print(fset, f)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Find all #cgo lines.
|
||||||
|
for _, genDecl := range statements {
|
||||||
|
if genDecl.Doc == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
for _, comment := range genDecl.Doc.List {
|
||||||
|
for {
|
||||||
|
// Extract the #cgo line, and replace it with spaces.
|
||||||
|
// Replacing with spaces makes sure that error locations are
|
||||||
|
// still correct, while not interfering with parsing in any way.
|
||||||
|
lineStart := strings.Index(comment.Text, "#cgo ")
|
||||||
|
if lineStart < 0 {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
lineLen := strings.IndexByte(comment.Text[lineStart:], '\n')
|
||||||
|
if lineLen < 0 {
|
||||||
|
lineLen = len(comment.Text) - lineStart
|
||||||
|
}
|
||||||
|
lineEnd := lineStart + lineLen
|
||||||
|
line := comment.Text[lineStart:lineEnd]
|
||||||
|
spaces := make([]byte, len(line))
|
||||||
|
for i := range spaces {
|
||||||
|
spaces[i] = ' '
|
||||||
|
}
|
||||||
|
lenBefore := len(comment.Text)
|
||||||
|
comment.Text = comment.Text[:lineStart] + string(spaces) + comment.Text[lineEnd:]
|
||||||
|
if len(comment.Text) != lenBefore {
|
||||||
|
println(lenBefore, len(comment.Text))
|
||||||
|
panic("length of preamble changed!")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the text before the colon in the #cgo directive.
|
||||||
|
colon := strings.IndexByte(line, ':')
|
||||||
|
if colon < 0 {
|
||||||
|
p.addErrorAfter(comment.Slash, comment.Text[:lineStart], "missing colon in #cgo line")
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Extract the fields before the colon. These fields are a list
|
||||||
|
// of build tags and the C environment variable.
|
||||||
|
fields := strings.Fields(line[4:colon])
|
||||||
|
if len(fields) == 0 {
|
||||||
|
p.addErrorAfter(comment.Slash, comment.Text[:lineStart+colon-1], "invalid #cgo line")
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(fields) > 1 {
|
||||||
|
p.addErrorAfter(comment.Slash, comment.Text[:lineStart+5], "not implemented: build constraints in #cgo line")
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
name := fields[len(fields)-1]
|
||||||
|
value := line[colon+1:]
|
||||||
|
switch name {
|
||||||
|
case "CFLAGS":
|
||||||
|
flags, err := shlex.Split(value)
|
||||||
|
if err != nil {
|
||||||
|
// TODO: find the exact location where the error happened.
|
||||||
|
p.addErrorAfter(comment.Slash, comment.Text[:lineStart+colon+1], "failed to parse flags in #cgo line: "+err.Error())
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if err := checkCompilerFlags(name, flags); err != nil {
|
||||||
|
p.addErrorAfter(comment.Slash, comment.Text[:lineStart+colon+1], err.Error())
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
cflags = append(cflags, flags...)
|
||||||
|
default:
|
||||||
|
startPos := strings.LastIndex(line[4:colon], name) + 4
|
||||||
|
p.addErrorAfter(comment.Slash, comment.Text[:lineStart+startPos], "invalid #cgo line: "+name)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Process all CGo imports.
|
||||||
|
for _, genDecl := range statements {
|
||||||
|
cgoComment := genDecl.Doc.Text()
|
||||||
|
|
||||||
|
pos := genDecl.Pos()
|
||||||
|
if genDecl.Doc != nil {
|
||||||
|
pos = genDecl.Doc.Pos()
|
||||||
|
}
|
||||||
|
position := fset.PositionFor(pos, true)
|
||||||
|
p.parseFragment(cgoComment+cgoTypes, cflags, position.Filename, position.Line)
|
||||||
|
}
|
||||||
|
|
||||||
// Declare functions found by libclang.
|
// Declare functions found by libclang.
|
||||||
p.addFuncDecls()
|
p.addFuncDecls()
|
||||||
|
|
||||||
@@ -343,18 +460,18 @@ func (p *cgoPackage) addFuncPtrDecls() {
|
|||||||
if len(p.functions) == 0 {
|
if len(p.functions) == 0 {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
gen := &ast.GenDecl{
|
|
||||||
TokPos: token.NoPos,
|
|
||||||
Tok: token.VAR,
|
|
||||||
Lparen: token.NoPos,
|
|
||||||
Rparen: token.NoPos,
|
|
||||||
}
|
|
||||||
names := make([]string, 0, len(p.functions))
|
names := make([]string, 0, len(p.functions))
|
||||||
for name := range p.functions {
|
for name := range p.functions {
|
||||||
names = append(names, name)
|
names = append(names, name)
|
||||||
}
|
}
|
||||||
sort.Strings(names)
|
sort.Strings(names)
|
||||||
for _, name := range names {
|
for _, name := range names {
|
||||||
|
gen := &ast.GenDecl{
|
||||||
|
TokPos: token.NoPos,
|
||||||
|
Tok: token.VAR,
|
||||||
|
Lparen: token.NoPos,
|
||||||
|
Rparen: token.NoPos,
|
||||||
|
}
|
||||||
fn := p.functions[name]
|
fn := p.functions[name]
|
||||||
obj := &ast.Object{
|
obj := &ast.Object{
|
||||||
Kind: ast.Typ,
|
Kind: ast.Typ,
|
||||||
@@ -379,34 +496,32 @@ func (p *cgoPackage) addFuncPtrDecls() {
|
|||||||
}
|
}
|
||||||
obj.Decl = valueSpec
|
obj.Decl = valueSpec
|
||||||
gen.Specs = append(gen.Specs, valueSpec)
|
gen.Specs = append(gen.Specs, valueSpec)
|
||||||
|
p.generated.Decls = append(p.generated.Decls, gen)
|
||||||
}
|
}
|
||||||
p.generated.Decls = append(p.generated.Decls, gen)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// addConstDecls declares external C constants in the Go source.
|
// addConstDecls declares external C constants in the Go source.
|
||||||
// It adds code like the following to the AST:
|
// It adds code like the following to the AST:
|
||||||
//
|
//
|
||||||
// const (
|
// const C.CONST_INT = 5
|
||||||
// C.CONST_INT = 5
|
// const C.CONST_FLOAT = 5.8
|
||||||
// C.CONST_FLOAT = 5.8
|
// // ...
|
||||||
// // ...
|
|
||||||
// )
|
|
||||||
func (p *cgoPackage) addConstDecls() {
|
func (p *cgoPackage) addConstDecls() {
|
||||||
if len(p.constants) == 0 {
|
if len(p.constants) == 0 {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
gen := &ast.GenDecl{
|
|
||||||
TokPos: token.NoPos,
|
|
||||||
Tok: token.CONST,
|
|
||||||
Lparen: token.NoPos,
|
|
||||||
Rparen: token.NoPos,
|
|
||||||
}
|
|
||||||
names := make([]string, 0, len(p.constants))
|
names := make([]string, 0, len(p.constants))
|
||||||
for name := range p.constants {
|
for name := range p.constants {
|
||||||
names = append(names, name)
|
names = append(names, name)
|
||||||
}
|
}
|
||||||
sort.Strings(names)
|
sort.Strings(names)
|
||||||
for _, name := range names {
|
for _, name := range names {
|
||||||
|
gen := &ast.GenDecl{
|
||||||
|
TokPos: token.NoPos,
|
||||||
|
Tok: token.CONST,
|
||||||
|
Lparen: token.NoPos,
|
||||||
|
Rparen: token.NoPos,
|
||||||
|
}
|
||||||
constVal := p.constants[name]
|
constVal := p.constants[name]
|
||||||
obj := &ast.Object{
|
obj := &ast.Object{
|
||||||
Kind: ast.Con,
|
Kind: ast.Con,
|
||||||
@@ -422,34 +537,32 @@ func (p *cgoPackage) addConstDecls() {
|
|||||||
}
|
}
|
||||||
obj.Decl = valueSpec
|
obj.Decl = valueSpec
|
||||||
gen.Specs = append(gen.Specs, valueSpec)
|
gen.Specs = append(gen.Specs, valueSpec)
|
||||||
|
p.generated.Decls = append(p.generated.Decls, gen)
|
||||||
}
|
}
|
||||||
p.generated.Decls = append(p.generated.Decls, gen)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// addVarDecls declares external C globals in the Go source.
|
// addVarDecls declares external C globals in the Go source.
|
||||||
// It adds code like the following to the AST:
|
// It adds code like the following to the AST:
|
||||||
//
|
//
|
||||||
// var (
|
// var C.globalInt int
|
||||||
// C.globalInt int
|
// var C.globalBool bool
|
||||||
// C.globalBool bool
|
// // ...
|
||||||
// // ...
|
|
||||||
// )
|
|
||||||
func (p *cgoPackage) addVarDecls() {
|
func (p *cgoPackage) addVarDecls() {
|
||||||
if len(p.globals) == 0 {
|
if len(p.globals) == 0 {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
gen := &ast.GenDecl{
|
|
||||||
TokPos: token.NoPos,
|
|
||||||
Tok: token.VAR,
|
|
||||||
Lparen: token.NoPos,
|
|
||||||
Rparen: token.NoPos,
|
|
||||||
}
|
|
||||||
names := make([]string, 0, len(p.globals))
|
names := make([]string, 0, len(p.globals))
|
||||||
for name := range p.globals {
|
for name := range p.globals {
|
||||||
names = append(names, name)
|
names = append(names, name)
|
||||||
}
|
}
|
||||||
sort.Strings(names)
|
sort.Strings(names)
|
||||||
for _, name := range names {
|
for _, name := range names {
|
||||||
|
gen := &ast.GenDecl{
|
||||||
|
TokPos: token.NoPos,
|
||||||
|
Tok: token.VAR,
|
||||||
|
Lparen: token.NoPos,
|
||||||
|
Rparen: token.NoPos,
|
||||||
|
}
|
||||||
global := p.globals[name]
|
global := p.globals[name]
|
||||||
obj := &ast.Object{
|
obj := &ast.Object{
|
||||||
Kind: ast.Var,
|
Kind: ast.Var,
|
||||||
@@ -465,31 +578,29 @@ func (p *cgoPackage) addVarDecls() {
|
|||||||
}
|
}
|
||||||
obj.Decl = valueSpec
|
obj.Decl = valueSpec
|
||||||
gen.Specs = append(gen.Specs, valueSpec)
|
gen.Specs = append(gen.Specs, valueSpec)
|
||||||
|
p.generated.Decls = append(p.generated.Decls, gen)
|
||||||
}
|
}
|
||||||
p.generated.Decls = append(p.generated.Decls, gen)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// addTypeAliases aliases some built-in Go types with their equivalent C types.
|
// addTypeAliases aliases some built-in Go types with their equivalent C types.
|
||||||
// It adds code like the following to the AST:
|
// It adds code like the following to the AST:
|
||||||
//
|
//
|
||||||
// type (
|
// type C.int8_t = int8
|
||||||
// C.int8_t = int8
|
// type C.int16_t = int16
|
||||||
// C.int16_t = int16
|
// // ...
|
||||||
// // ...
|
|
||||||
// )
|
|
||||||
func (p *cgoPackage) addTypeAliases() {
|
func (p *cgoPackage) addTypeAliases() {
|
||||||
aliasKeys := make([]string, 0, len(cgoAliases))
|
aliasKeys := make([]string, 0, len(cgoAliases))
|
||||||
for key := range cgoAliases {
|
for key := range cgoAliases {
|
||||||
aliasKeys = append(aliasKeys, key)
|
aliasKeys = append(aliasKeys, key)
|
||||||
}
|
}
|
||||||
sort.Strings(aliasKeys)
|
sort.Strings(aliasKeys)
|
||||||
gen := &ast.GenDecl{
|
|
||||||
TokPos: token.NoPos,
|
|
||||||
Tok: token.TYPE,
|
|
||||||
Lparen: token.NoPos,
|
|
||||||
Rparen: token.NoPos,
|
|
||||||
}
|
|
||||||
for _, typeName := range aliasKeys {
|
for _, typeName := range aliasKeys {
|
||||||
|
gen := &ast.GenDecl{
|
||||||
|
TokPos: token.NoPos,
|
||||||
|
Tok: token.TYPE,
|
||||||
|
Lparen: token.NoPos,
|
||||||
|
Rparen: token.NoPos,
|
||||||
|
}
|
||||||
goTypeName := cgoAliases[typeName]
|
goTypeName := cgoAliases[typeName]
|
||||||
obj := &ast.Object{
|
obj := &ast.Object{
|
||||||
Kind: ast.Typ,
|
Kind: ast.Typ,
|
||||||
@@ -509,24 +620,24 @@ func (p *cgoPackage) addTypeAliases() {
|
|||||||
}
|
}
|
||||||
obj.Decl = typeSpec
|
obj.Decl = typeSpec
|
||||||
gen.Specs = append(gen.Specs, typeSpec)
|
gen.Specs = append(gen.Specs, typeSpec)
|
||||||
|
p.generated.Decls = append(p.generated.Decls, gen)
|
||||||
}
|
}
|
||||||
p.generated.Decls = append(p.generated.Decls, gen)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *cgoPackage) addTypedefs() {
|
func (p *cgoPackage) addTypedefs() {
|
||||||
if len(p.typedefs) == 0 {
|
if len(p.typedefs) == 0 {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
gen := &ast.GenDecl{
|
|
||||||
TokPos: token.NoPos,
|
|
||||||
Tok: token.TYPE,
|
|
||||||
}
|
|
||||||
names := make([]string, 0, len(p.typedefs))
|
names := make([]string, 0, len(p.typedefs))
|
||||||
for name := range p.typedefs {
|
for name := range p.typedefs {
|
||||||
names = append(names, name)
|
names = append(names, name)
|
||||||
}
|
}
|
||||||
sort.Strings(names)
|
sort.Strings(names)
|
||||||
for _, name := range names {
|
for _, name := range names {
|
||||||
|
gen := &ast.GenDecl{
|
||||||
|
TokPos: token.NoPos,
|
||||||
|
Tok: token.TYPE,
|
||||||
|
}
|
||||||
typedef := p.typedefs[name]
|
typedef := p.typedefs[name]
|
||||||
typeName := "C." + name
|
typeName := "C." + name
|
||||||
isAlias := true
|
isAlias := true
|
||||||
@@ -555,8 +666,8 @@ func (p *cgoPackage) addTypedefs() {
|
|||||||
}
|
}
|
||||||
obj.Decl = typeSpec
|
obj.Decl = typeSpec
|
||||||
gen.Specs = append(gen.Specs, typeSpec)
|
gen.Specs = append(gen.Specs, typeSpec)
|
||||||
|
p.generated.Decls = append(p.generated.Decls, gen)
|
||||||
}
|
}
|
||||||
p.generated.Decls = append(p.generated.Decls, gen)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// addElaboratedTypes adds C elaborated types as aliases. These are the "struct
|
// addElaboratedTypes adds C elaborated types as aliases. These are the "struct
|
||||||
@@ -568,29 +679,47 @@ func (p *cgoPackage) addElaboratedTypes() {
|
|||||||
if len(p.elaboratedTypes) == 0 {
|
if len(p.elaboratedTypes) == 0 {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
gen := &ast.GenDecl{
|
|
||||||
TokPos: token.NoPos,
|
|
||||||
Tok: token.TYPE,
|
|
||||||
}
|
|
||||||
names := make([]string, 0, len(p.elaboratedTypes))
|
names := make([]string, 0, len(p.elaboratedTypes))
|
||||||
for name := range p.elaboratedTypes {
|
for name := range p.elaboratedTypes {
|
||||||
names = append(names, name)
|
names = append(names, name)
|
||||||
}
|
}
|
||||||
sort.Strings(names)
|
sort.Strings(names)
|
||||||
for _, name := range names {
|
for _, name := range names {
|
||||||
|
gen := &ast.GenDecl{
|
||||||
|
TokPos: token.NoPos,
|
||||||
|
Tok: token.TYPE,
|
||||||
|
}
|
||||||
typ := p.elaboratedTypes[name]
|
typ := p.elaboratedTypes[name]
|
||||||
typeName := "C." + name
|
typeName := "C." + name
|
||||||
obj := &ast.Object{
|
obj := &ast.Object{
|
||||||
Kind: ast.Typ,
|
Kind: ast.Typ,
|
||||||
Name: typeName,
|
Name: typeName,
|
||||||
}
|
}
|
||||||
|
typeExpr := typ.typeExpr
|
||||||
|
if typ.unionSize != 0 {
|
||||||
|
// Create getters/setters.
|
||||||
|
for _, field := range typ.typeExpr.Fields.List {
|
||||||
|
if len(field.Names) != 1 {
|
||||||
|
p.addError(typ.pos, fmt.Sprintf("union must have field with a single name, it has %d names", len(field.Names)))
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
p.createUnionAccessor(field, typeName)
|
||||||
|
}
|
||||||
|
// Convert to a single-field struct type.
|
||||||
|
typeExpr = p.makeUnionField(typ)
|
||||||
|
if typeExpr == nil {
|
||||||
|
// There was an error, that was already added to the list of
|
||||||
|
// errors.
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
typeSpec := &ast.TypeSpec{
|
typeSpec := &ast.TypeSpec{
|
||||||
Name: &ast.Ident{
|
Name: &ast.Ident{
|
||||||
NamePos: typ.pos,
|
NamePos: typ.pos,
|
||||||
Name: typeName,
|
Name: typeName,
|
||||||
Obj: obj,
|
Obj: obj,
|
||||||
},
|
},
|
||||||
Type: typ.typeExpr,
|
Type: typeExpr,
|
||||||
}
|
}
|
||||||
obj.Decl = typeSpec
|
obj.Decl = typeSpec
|
||||||
gen.Specs = append(gen.Specs, typeSpec)
|
gen.Specs = append(gen.Specs, typeSpec)
|
||||||
@@ -599,8 +728,185 @@ func (p *cgoPackage) addElaboratedTypes() {
|
|||||||
p.createBitfieldGetter(bitfield, typeName)
|
p.createBitfieldGetter(bitfield, typeName)
|
||||||
p.createBitfieldSetter(bitfield, typeName)
|
p.createBitfieldSetter(bitfield, typeName)
|
||||||
}
|
}
|
||||||
|
p.generated.Decls = append(p.generated.Decls, gen)
|
||||||
}
|
}
|
||||||
p.generated.Decls = append(p.generated.Decls, gen)
|
}
|
||||||
|
|
||||||
|
// makeUnionField creates a new struct from an existing *elaboratedTypeInfo,
|
||||||
|
// that has just a single field that must be accessed through special accessors.
|
||||||
|
// It returns nil when there is an error. In case of an error, that error has
|
||||||
|
// already been added to the list of errors using p.addError.
|
||||||
|
func (p *cgoPackage) makeUnionField(typ *elaboratedTypeInfo) *ast.StructType {
|
||||||
|
unionFieldTypeName, ok := map[int64]string{
|
||||||
|
1: "uint8",
|
||||||
|
2: "uint16",
|
||||||
|
4: "uint32",
|
||||||
|
8: "uint64",
|
||||||
|
}[typ.unionAlign]
|
||||||
|
if !ok {
|
||||||
|
p.addError(typ.typeExpr.Struct, fmt.Sprintf("expected union alignment to be one of 1, 2, 4, or 8, but got %d", typ.unionAlign))
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
var unionFieldType ast.Expr = &ast.Ident{
|
||||||
|
NamePos: token.NoPos,
|
||||||
|
Name: unionFieldTypeName,
|
||||||
|
}
|
||||||
|
if typ.unionSize != typ.unionAlign {
|
||||||
|
// A plain struct{uintX} isn't enough, we have to make a
|
||||||
|
// struct{[N]uintX} to make the union big enough.
|
||||||
|
if typ.unionSize/typ.unionAlign*typ.unionAlign != typ.unionSize {
|
||||||
|
p.addError(typ.typeExpr.Struct, fmt.Sprintf("union alignment (%d) must be a multiple of union alignment (%d)", typ.unionSize, typ.unionAlign))
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
unionFieldType = &ast.ArrayType{
|
||||||
|
Len: &ast.BasicLit{
|
||||||
|
Kind: token.INT,
|
||||||
|
Value: strconv.FormatInt(typ.unionSize/typ.unionAlign, 10),
|
||||||
|
},
|
||||||
|
Elt: unionFieldType,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return &ast.StructType{
|
||||||
|
Struct: typ.typeExpr.Struct,
|
||||||
|
Fields: &ast.FieldList{
|
||||||
|
Opening: typ.typeExpr.Fields.Opening,
|
||||||
|
List: []*ast.Field{&ast.Field{
|
||||||
|
Names: []*ast.Ident{
|
||||||
|
&ast.Ident{
|
||||||
|
NamePos: typ.typeExpr.Fields.Opening,
|
||||||
|
Name: "$union",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Type: unionFieldType,
|
||||||
|
}},
|
||||||
|
Closing: typ.typeExpr.Fields.Closing,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// createUnionAccessor creates a function that returns a typed pointer to a
|
||||||
|
// union field for each field in a union. For example:
|
||||||
|
//
|
||||||
|
// func (union *C.union_1) unionfield_d() *float64 {
|
||||||
|
// return (*float64)(unsafe.Pointer(&union.$union))
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// Where C.union_1 is defined as:
|
||||||
|
//
|
||||||
|
// type C.union_1 struct{
|
||||||
|
// $union uint64
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// The returned pointer can be used to get or set the field, or get the pointer
|
||||||
|
// to a subfield.
|
||||||
|
func (p *cgoPackage) createUnionAccessor(field *ast.Field, typeName string) {
|
||||||
|
if len(field.Names) != 1 {
|
||||||
|
panic("number of names in union field must be exactly 1")
|
||||||
|
}
|
||||||
|
fieldName := field.Names[0]
|
||||||
|
pos := fieldName.NamePos
|
||||||
|
|
||||||
|
// The method receiver.
|
||||||
|
receiver := &ast.SelectorExpr{
|
||||||
|
X: &ast.Ident{
|
||||||
|
NamePos: pos,
|
||||||
|
Name: "union",
|
||||||
|
Obj: nil,
|
||||||
|
},
|
||||||
|
Sel: &ast.Ident{
|
||||||
|
NamePos: pos,
|
||||||
|
Name: "$union",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the address of the $union field.
|
||||||
|
receiverPtr := &ast.UnaryExpr{
|
||||||
|
Op: token.AND,
|
||||||
|
X: receiver,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cast to unsafe.Pointer.
|
||||||
|
sourcePointer := &ast.CallExpr{
|
||||||
|
Fun: &ast.SelectorExpr{
|
||||||
|
X: &ast.Ident{Name: "unsafe"},
|
||||||
|
Sel: &ast.Ident{Name: "Pointer"},
|
||||||
|
},
|
||||||
|
Args: []ast.Expr{receiverPtr},
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cast to the target pointer type.
|
||||||
|
targetPointer := &ast.CallExpr{
|
||||||
|
Lparen: pos,
|
||||||
|
Fun: &ast.ParenExpr{
|
||||||
|
Lparen: pos,
|
||||||
|
X: &ast.StarExpr{
|
||||||
|
X: field.Type,
|
||||||
|
},
|
||||||
|
Rparen: pos,
|
||||||
|
},
|
||||||
|
Args: []ast.Expr{sourcePointer},
|
||||||
|
Rparen: pos,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create the accessor function.
|
||||||
|
accessor := &ast.FuncDecl{
|
||||||
|
Recv: &ast.FieldList{
|
||||||
|
Opening: pos,
|
||||||
|
List: []*ast.Field{
|
||||||
|
&ast.Field{
|
||||||
|
Names: []*ast.Ident{
|
||||||
|
&ast.Ident{
|
||||||
|
NamePos: pos,
|
||||||
|
Name: "union",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Type: &ast.StarExpr{
|
||||||
|
Star: pos,
|
||||||
|
X: &ast.Ident{
|
||||||
|
NamePos: pos,
|
||||||
|
Name: typeName,
|
||||||
|
Obj: nil,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Closing: pos,
|
||||||
|
},
|
||||||
|
Name: &ast.Ident{
|
||||||
|
NamePos: pos,
|
||||||
|
Name: "unionfield_" + fieldName.Name,
|
||||||
|
},
|
||||||
|
Type: &ast.FuncType{
|
||||||
|
Func: pos,
|
||||||
|
Params: &ast.FieldList{
|
||||||
|
Opening: pos,
|
||||||
|
Closing: pos,
|
||||||
|
},
|
||||||
|
Results: &ast.FieldList{
|
||||||
|
List: []*ast.Field{
|
||||||
|
&ast.Field{
|
||||||
|
Type: &ast.StarExpr{
|
||||||
|
Star: pos,
|
||||||
|
X: field.Type,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Body: &ast.BlockStmt{
|
||||||
|
Lbrace: pos,
|
||||||
|
List: []ast.Stmt{
|
||||||
|
&ast.ReturnStmt{
|
||||||
|
Return: pos,
|
||||||
|
Results: []ast.Expr{
|
||||||
|
targetPointer,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Rbrace: pos,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
p.generated.Decls = append(p.generated.Decls, accessor)
|
||||||
}
|
}
|
||||||
|
|
||||||
// createBitfieldGetter creates a bitfield getter function like the following:
|
// createBitfieldGetter creates a bitfield getter function like the following:
|
||||||
@@ -905,16 +1211,16 @@ func (p *cgoPackage) addEnumTypes() {
|
|||||||
if len(p.enums) == 0 {
|
if len(p.enums) == 0 {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
gen := &ast.GenDecl{
|
|
||||||
TokPos: token.NoPos,
|
|
||||||
Tok: token.TYPE,
|
|
||||||
}
|
|
||||||
names := make([]string, 0, len(p.enums))
|
names := make([]string, 0, len(p.enums))
|
||||||
for name := range p.enums {
|
for name := range p.enums {
|
||||||
names = append(names, name)
|
names = append(names, name)
|
||||||
}
|
}
|
||||||
sort.Strings(names)
|
sort.Strings(names)
|
||||||
for _, name := range names {
|
for _, name := range names {
|
||||||
|
gen := &ast.GenDecl{
|
||||||
|
TokPos: token.NoPos,
|
||||||
|
Tok: token.TYPE,
|
||||||
|
}
|
||||||
typ := p.enums[name]
|
typ := p.enums[name]
|
||||||
typeName := "C.enum_" + name
|
typeName := "C.enum_" + name
|
||||||
obj := &ast.Object{
|
obj := &ast.Object{
|
||||||
@@ -931,8 +1237,8 @@ func (p *cgoPackage) addEnumTypes() {
|
|||||||
}
|
}
|
||||||
obj.Decl = typeSpec
|
obj.Decl = typeSpec
|
||||||
gen.Specs = append(gen.Specs, typeSpec)
|
gen.Specs = append(gen.Specs, typeSpec)
|
||||||
|
p.generated.Decls = append(p.generated.Decls, gen)
|
||||||
}
|
}
|
||||||
p.generated.Decls = append(p.generated.Decls, gen)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// findMissingCGoNames traverses the AST and finds all C.something names. Only
|
// findMissingCGoNames traverses the AST and finds all C.something names. Only
|
||||||
@@ -995,3 +1301,29 @@ func (p *cgoPackage) walker(cursor *astutil.Cursor) bool {
|
|||||||
}
|
}
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// renameFieldKeywords renames all reserved words in Go to some other field name
|
||||||
|
// with a "_" prefix. For example, it renames `type` to `_type`.
|
||||||
|
//
|
||||||
|
// See: https://golang.org/cmd/cgo/#hdr-Go_references_to_C
|
||||||
|
func renameFieldKeywords(fieldList *ast.FieldList) {
|
||||||
|
renameFieldName(fieldList, "type")
|
||||||
|
}
|
||||||
|
|
||||||
|
// renameFieldName renames a given field name to a name with a "_" prepended. It
|
||||||
|
// makes sure to do the same thing for any field sharing the same name.
|
||||||
|
func renameFieldName(fieldList *ast.FieldList, name string) {
|
||||||
|
var ident *ast.Ident
|
||||||
|
for _, f := range fieldList.List {
|
||||||
|
for _, n := range f.Names {
|
||||||
|
if n.Name == name {
|
||||||
|
ident = n
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if ident == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
renameFieldName(fieldList, "_"+name)
|
||||||
|
ident.Name = "_" + ident.Name
|
||||||
|
}
|
||||||
|
|||||||
+131
@@ -0,0 +1,131 @@
|
|||||||
|
package cgo
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"flag"
|
||||||
|
"fmt"
|
||||||
|
"go/ast"
|
||||||
|
"go/format"
|
||||||
|
"go/parser"
|
||||||
|
"go/token"
|
||||||
|
"go/types"
|
||||||
|
"io/ioutil"
|
||||||
|
"path/filepath"
|
||||||
|
"runtime"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Pass -update to go test to update the output of the test files.
|
||||||
|
var flagUpdate = flag.Bool("update", false, "Update images based on test output.")
|
||||||
|
|
||||||
|
func TestCGo(t *testing.T) {
|
||||||
|
var cflags = []string{"--target=armv6m-none-eabi"}
|
||||||
|
|
||||||
|
for _, name := range []string{"basic", "errors", "types", "flags"} {
|
||||||
|
name := name // avoid a race condition
|
||||||
|
t.Run(name, func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
// Read the AST in memory.
|
||||||
|
path := filepath.Join("testdata", name+".go")
|
||||||
|
fset := token.NewFileSet()
|
||||||
|
f, err := parser.ParseFile(fset, path, nil, parser.ParseComments)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal("could not parse Go source file:", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Process the AST with CGo.
|
||||||
|
cgoAST, cgoErrors := Process([]*ast.File{f}, "testdata", fset, cflags)
|
||||||
|
|
||||||
|
// Check the AST for type errors.
|
||||||
|
var typecheckErrors []error
|
||||||
|
config := types.Config{
|
||||||
|
Error: func(err error) {
|
||||||
|
typecheckErrors = append(typecheckErrors, err)
|
||||||
|
},
|
||||||
|
Importer: simpleImporter{},
|
||||||
|
Sizes: types.SizesFor("gccgo", "arm"),
|
||||||
|
}
|
||||||
|
_, err = config.Check("", fset, []*ast.File{f, cgoAST}, nil)
|
||||||
|
if err != nil && len(typecheckErrors) == 0 {
|
||||||
|
// Only report errors when no type errors are found (an
|
||||||
|
// unexpected condition).
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Store the (formatted) output in a buffer. Format it, so it
|
||||||
|
// becomes easier to read (and will hopefully change less with CGo
|
||||||
|
// changes).
|
||||||
|
buf := &bytes.Buffer{}
|
||||||
|
if len(cgoErrors) != 0 {
|
||||||
|
buf.WriteString("// CGo errors:\n")
|
||||||
|
for _, err := range cgoErrors {
|
||||||
|
buf.WriteString(formatDiagnostic(err))
|
||||||
|
}
|
||||||
|
buf.WriteString("\n")
|
||||||
|
}
|
||||||
|
if len(typecheckErrors) != 0 {
|
||||||
|
buf.WriteString("// Type checking errors after CGo processing:\n")
|
||||||
|
for _, err := range typecheckErrors {
|
||||||
|
buf.WriteString(formatDiagnostic(err))
|
||||||
|
}
|
||||||
|
buf.WriteString("\n")
|
||||||
|
}
|
||||||
|
err = format.Node(buf, fset, cgoAST)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("could not write out CGo AST: %v", err)
|
||||||
|
}
|
||||||
|
actual := strings.Replace(string(buf.Bytes()), "\r\n", "\n", -1)
|
||||||
|
|
||||||
|
// Read the file with the expected output, to compare against.
|
||||||
|
outfile := filepath.Join("testdata", name+".out.go")
|
||||||
|
expectedBytes, err := ioutil.ReadFile(outfile)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("could not read expected output: %v", err)
|
||||||
|
}
|
||||||
|
expected := strings.Replace(string(expectedBytes), "\r\n", "\n", -1)
|
||||||
|
|
||||||
|
// Check whether the output is as expected.
|
||||||
|
if expected != actual {
|
||||||
|
// It is not. Test failed.
|
||||||
|
if *flagUpdate {
|
||||||
|
// Update the file with the expected data.
|
||||||
|
err := ioutil.WriteFile(outfile, []byte(actual), 0666)
|
||||||
|
if err != nil {
|
||||||
|
t.Error("could not write updated output file:", err)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
t.Errorf("output did not match:\n%s", string(actual))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// simpleImporter implements the types.Importer interface, but only allows
|
||||||
|
// importing the unsafe package.
|
||||||
|
type simpleImporter struct {
|
||||||
|
}
|
||||||
|
|
||||||
|
// Import implements the Importer interface. For testing usage only: it only
|
||||||
|
// supports importing the unsafe package.
|
||||||
|
func (i simpleImporter) Import(path string) (*types.Package, error) {
|
||||||
|
switch path {
|
||||||
|
case "unsafe":
|
||||||
|
return types.Unsafe, nil
|
||||||
|
default:
|
||||||
|
return nil, fmt.Errorf("importer not implemented for package %s", path)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// formatDiagnostics formats the error message to be an indented comment. It
|
||||||
|
// also fixes Windows path name issues (backward slashes).
|
||||||
|
func formatDiagnostic(err error) string {
|
||||||
|
msg := err.Error()
|
||||||
|
if runtime.GOOS == "windows" {
|
||||||
|
// Fix Windows path slashes.
|
||||||
|
msg = strings.Replace(msg, "testdata\\", "testdata/", -1)
|
||||||
|
}
|
||||||
|
return "// " + msg + "\n"
|
||||||
|
}
|
||||||
+194
@@ -0,0 +1,194 @@
|
|||||||
|
package cgo
|
||||||
|
|
||||||
|
// This file implements a parser of a subset of the C language, just enough to
|
||||||
|
// parse common #define statements to Go constant expressions.
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"go/ast"
|
||||||
|
"go/scanner"
|
||||||
|
"go/token"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// parseConst parses the given string as a C constant.
|
||||||
|
func parseConst(pos token.Pos, fset *token.FileSet, value string) (ast.Expr, *scanner.Error) {
|
||||||
|
t := newTokenizer(pos, fset, value)
|
||||||
|
expr, err := parseConstExpr(t)
|
||||||
|
if t.token != token.EOF {
|
||||||
|
return nil, &scanner.Error{
|
||||||
|
Pos: t.fset.Position(t.pos),
|
||||||
|
Msg: "unexpected token " + t.token.String(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return expr, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// parseConstExpr parses a stream of C tokens to a Go expression.
|
||||||
|
func parseConstExpr(t *tokenizer) (ast.Expr, *scanner.Error) {
|
||||||
|
switch t.token {
|
||||||
|
case token.LPAREN:
|
||||||
|
lparen := t.pos
|
||||||
|
t.Next()
|
||||||
|
x, err := parseConstExpr(t)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if t.token != token.RPAREN {
|
||||||
|
return nil, unexpectedToken(t, token.RPAREN)
|
||||||
|
}
|
||||||
|
expr := &ast.ParenExpr{
|
||||||
|
Lparen: lparen,
|
||||||
|
X: x,
|
||||||
|
Rparen: t.pos,
|
||||||
|
}
|
||||||
|
t.Next()
|
||||||
|
return expr, nil
|
||||||
|
case token.INT, token.FLOAT, token.STRING, token.CHAR:
|
||||||
|
expr := &ast.BasicLit{
|
||||||
|
ValuePos: t.pos,
|
||||||
|
Kind: t.token,
|
||||||
|
Value: t.value,
|
||||||
|
}
|
||||||
|
t.Next()
|
||||||
|
return expr, nil
|
||||||
|
case token.EOF:
|
||||||
|
return nil, &scanner.Error{
|
||||||
|
Pos: t.fset.Position(t.pos),
|
||||||
|
Msg: "empty constant",
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
return nil, &scanner.Error{
|
||||||
|
Pos: t.fset.Position(t.pos),
|
||||||
|
Msg: fmt.Sprintf("unexpected token %s", t.token),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// unexpectedToken returns an error of the form "unexpected token FOO, expected
|
||||||
|
// BAR".
|
||||||
|
func unexpectedToken(t *tokenizer, expected token.Token) *scanner.Error {
|
||||||
|
return &scanner.Error{
|
||||||
|
Pos: t.fset.Position(t.pos),
|
||||||
|
Msg: fmt.Sprintf("unexpected token %s, expected %s", t.token, expected),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// tokenizer reads C source code and converts it to Go tokens.
|
||||||
|
type tokenizer struct {
|
||||||
|
pos token.Pos
|
||||||
|
fset *token.FileSet
|
||||||
|
token token.Token
|
||||||
|
value string
|
||||||
|
buf string
|
||||||
|
}
|
||||||
|
|
||||||
|
// newTokenizer initializes a new tokenizer, positioned at the first token in
|
||||||
|
// the string.
|
||||||
|
func newTokenizer(start token.Pos, fset *token.FileSet, buf string) *tokenizer {
|
||||||
|
t := &tokenizer{
|
||||||
|
pos: start,
|
||||||
|
fset: fset,
|
||||||
|
buf: buf,
|
||||||
|
token: token.ILLEGAL,
|
||||||
|
}
|
||||||
|
t.Next() // Parse the first token.
|
||||||
|
return t
|
||||||
|
}
|
||||||
|
|
||||||
|
// Next consumes the next token in the stream. There is no return value, read
|
||||||
|
// the next token from the pos, token and value properties.
|
||||||
|
func (t *tokenizer) Next() {
|
||||||
|
t.pos += token.Pos(len(t.value))
|
||||||
|
for {
|
||||||
|
if len(t.buf) == 0 {
|
||||||
|
t.token = token.EOF
|
||||||
|
return
|
||||||
|
}
|
||||||
|
c := t.buf[0]
|
||||||
|
switch {
|
||||||
|
case c == ' ' || c == '\f' || c == '\n' || c == '\r' || c == '\t' || c == '\v':
|
||||||
|
// Skip whitespace.
|
||||||
|
// Based on this source, not sure whether it represents C whitespace:
|
||||||
|
// https://en.cppreference.com/w/cpp/string/byte/isspace
|
||||||
|
t.pos++
|
||||||
|
t.buf = t.buf[1:]
|
||||||
|
case c == '(' || c == ')':
|
||||||
|
// Single-character tokens.
|
||||||
|
switch c {
|
||||||
|
case '(':
|
||||||
|
t.token = token.LPAREN
|
||||||
|
case ')':
|
||||||
|
t.token = token.RPAREN
|
||||||
|
}
|
||||||
|
t.value = t.buf[:1]
|
||||||
|
t.buf = t.buf[1:]
|
||||||
|
return
|
||||||
|
case c >= '0' && c <= '9':
|
||||||
|
// Numeric constant (int, float, etc.).
|
||||||
|
// Find the last non-numeric character.
|
||||||
|
tokenLen := len(t.buf)
|
||||||
|
hasDot := false
|
||||||
|
for i, c := range t.buf {
|
||||||
|
if c == '.' {
|
||||||
|
hasDot = true
|
||||||
|
}
|
||||||
|
if (c >= '0' && c <= '9') || c == '.' || (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') {
|
||||||
|
tokenLen = i + 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
t.value = t.buf[:tokenLen]
|
||||||
|
t.buf = t.buf[tokenLen:]
|
||||||
|
if hasDot {
|
||||||
|
// Integer constants are more complicated than this but this is
|
||||||
|
// a close approximation.
|
||||||
|
// https://en.cppreference.com/w/cpp/language/integer_literal
|
||||||
|
t.token = token.FLOAT
|
||||||
|
t.value = strings.TrimRight(t.value, "f")
|
||||||
|
} else {
|
||||||
|
t.token = token.INT
|
||||||
|
t.value = strings.TrimRight(t.value, "uUlL")
|
||||||
|
}
|
||||||
|
return
|
||||||
|
case c == '"':
|
||||||
|
// String constant. Find the first '"' character that is not
|
||||||
|
// preceded by a backslash.
|
||||||
|
escape := false
|
||||||
|
tokenLen := len(t.buf)
|
||||||
|
for i, c := range t.buf {
|
||||||
|
if i != 0 && c == '"' && !escape {
|
||||||
|
tokenLen = i + 1
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if !escape {
|
||||||
|
escape = c == '\\'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
t.token = token.STRING
|
||||||
|
t.value = t.buf[:tokenLen]
|
||||||
|
t.buf = t.buf[tokenLen:]
|
||||||
|
return
|
||||||
|
case c == '\'':
|
||||||
|
// Char (rune) constant. Find the first '\'' character that is not
|
||||||
|
// preceded by a backslash.
|
||||||
|
escape := false
|
||||||
|
tokenLen := len(t.buf)
|
||||||
|
for i, c := range t.buf {
|
||||||
|
if i != 0 && c == '\'' && !escape {
|
||||||
|
tokenLen = i + 1
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if !escape {
|
||||||
|
escape = c == '\\'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
t.token = token.CHAR
|
||||||
|
t.value = t.buf[:tokenLen]
|
||||||
|
t.buf = t.buf[tokenLen:]
|
||||||
|
return
|
||||||
|
default:
|
||||||
|
t.token = token.ILLEGAL
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,59 @@
|
|||||||
|
package cgo
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"go/format"
|
||||||
|
"go/token"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestParseConst(t *testing.T) {
|
||||||
|
// Test converting a C constant to a Go constant.
|
||||||
|
for _, tc := range []struct {
|
||||||
|
C string
|
||||||
|
Go string
|
||||||
|
}{
|
||||||
|
{`5`, `5`},
|
||||||
|
{`(5)`, `(5)`},
|
||||||
|
{`(((5)))`, `(5)`},
|
||||||
|
{`)`, `error: 1:1: unexpected token )`},
|
||||||
|
{`5)`, `error: 1:2: unexpected token )`},
|
||||||
|
{" \t)", `error: 1:4: unexpected token )`},
|
||||||
|
{`5.8f`, `5.8`},
|
||||||
|
{`foo`, `error: 1:1: unexpected token ILLEGAL`}, // identifiers unimplemented
|
||||||
|
{``, `error: 1:1: empty constant`}, // empty constants not allowed in Go
|
||||||
|
{`"foo"`, `"foo"`},
|
||||||
|
{`"a\\n"`, `"a\\n"`},
|
||||||
|
{`"a\n"`, `"a\n"`},
|
||||||
|
{`"a\""`, `"a\""`},
|
||||||
|
{`'a'`, `'a'`},
|
||||||
|
{`0b10`, `0b10`},
|
||||||
|
{`0x1234_5678`, `0x1234_5678`},
|
||||||
|
} {
|
||||||
|
fset := token.NewFileSet()
|
||||||
|
startPos := fset.AddFile("", -1, 1000).Pos(0)
|
||||||
|
expr, err := parseConst(startPos, fset, tc.C)
|
||||||
|
s := "<invalid>"
|
||||||
|
if err != nil {
|
||||||
|
if !strings.HasPrefix(tc.Go, "error: ") {
|
||||||
|
t.Errorf("expected value %#v for C constant %#v but got error %#v", tc.Go, tc.C, err.Error())
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
s = "error: " + err.Error()
|
||||||
|
} else if expr != nil {
|
||||||
|
// Serialize the Go constant to a string, for more readable test
|
||||||
|
// cases.
|
||||||
|
buf := &bytes.Buffer{}
|
||||||
|
err := format.Node(buf, fset, expr)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("could not format expr from C constant %#v: %v", tc.C, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
s = buf.String()
|
||||||
|
}
|
||||||
|
if s != tc.Go {
|
||||||
|
t.Errorf("C constant %#v was parsed to %#v while expecting %#v", tc.C, s, tc.Go)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+175
-162
@@ -15,7 +15,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
/*
|
/*
|
||||||
#include <clang-c/Index.h> // if this fails, install libclang-8-dev
|
#include <clang-c/Index.h> // if this fails, install libclang-9-dev
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
|
|
||||||
@@ -109,7 +109,8 @@ func (p *cgoPackage) parseFragment(fragment string, cflags []string, posFilename
|
|||||||
C.CXTranslationUnit_DetailedPreprocessingRecord,
|
C.CXTranslationUnit_DetailedPreprocessingRecord,
|
||||||
&unit)
|
&unit)
|
||||||
if errCode != 0 {
|
if errCode != 0 {
|
||||||
panic("loader: failed to parse source with libclang")
|
// This is probably a bug in the usage of libclang.
|
||||||
|
panic("cgo: failed to parse source with libclang")
|
||||||
}
|
}
|
||||||
defer C.clang_disposeTranslationUnit(unit)
|
defer C.clang_disposeTranslationUnit(unit)
|
||||||
|
|
||||||
@@ -118,27 +119,8 @@ func (p *cgoPackage) parseFragment(fragment string, cflags []string, posFilename
|
|||||||
spelling := getString(C.clang_getDiagnosticSpelling(diagnostic))
|
spelling := getString(C.clang_getDiagnosticSpelling(diagnostic))
|
||||||
severity := diagnosticSeverity[C.clang_getDiagnosticSeverity(diagnostic)]
|
severity := diagnosticSeverity[C.clang_getDiagnosticSeverity(diagnostic)]
|
||||||
location := C.clang_getDiagnosticLocation(diagnostic)
|
location := C.clang_getDiagnosticLocation(diagnostic)
|
||||||
var libclangFilename C.CXString
|
pos := p.getClangLocationPosition(location, unit)
|
||||||
var line C.unsigned
|
p.addError(pos, severity+": "+spelling)
|
||||||
var column C.unsigned
|
|
||||||
C.clang_getPresumedLocation(location, &libclangFilename, &line, &column)
|
|
||||||
filename := getString(libclangFilename)
|
|
||||||
if filepath.IsAbs(filename) {
|
|
||||||
// Relative paths for readability, like other Go parser errors.
|
|
||||||
relpath, err := filepath.Rel(p.dir, filename)
|
|
||||||
if err == nil {
|
|
||||||
filename = relpath
|
|
||||||
}
|
|
||||||
}
|
|
||||||
p.errors = append(p.errors, &scanner.Error{
|
|
||||||
Pos: token.Position{
|
|
||||||
Filename: filename,
|
|
||||||
Offset: 0, // not provided by clang_getPresumedLocation
|
|
||||||
Line: int(line),
|
|
||||||
Column: int(column),
|
|
||||||
},
|
|
||||||
Msg: severity + ": " + spelling,
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
for i := 0; i < numDiagnostics; i++ {
|
for i := 0; i < numDiagnostics; i++ {
|
||||||
diagnostic := C.clang_getDiagnostic(unit, C.uint(i))
|
diagnostic := C.clang_getDiagnostic(unit, C.uint(i))
|
||||||
@@ -150,7 +132,6 @@ func (p *cgoPackage) parseFragment(fragment string, cflags []string, posFilename
|
|||||||
addDiagnostic(C.clang_getDiagnosticInSet(diagnostics, C.uint(j)))
|
addDiagnostic(C.clang_getDiagnosticInSet(diagnostics, C.uint(j)))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ref := storedRefs.Put(p)
|
ref := storedRefs.Put(p)
|
||||||
@@ -237,14 +218,17 @@ func tinygo_clang_globals_visitor(c, parent C.GoCXCursor, client_data C.CXClient
|
|||||||
var startOffset, endOffset C.unsigned
|
var startOffset, endOffset C.unsigned
|
||||||
C.clang_getExpansionLocation(start, &file, nil, nil, &startOffset)
|
C.clang_getExpansionLocation(start, &file, nil, nil, &startOffset)
|
||||||
if file == nil {
|
if file == nil {
|
||||||
panic("could not find file where macro is defined")
|
p.addError(pos, "internal error: could not find file where macro is defined")
|
||||||
|
break
|
||||||
}
|
}
|
||||||
C.clang_getExpansionLocation(end, &endFile, nil, nil, &endOffset)
|
C.clang_getExpansionLocation(end, &endFile, nil, nil, &endOffset)
|
||||||
if file != endFile {
|
if file != endFile {
|
||||||
panic("expected start and end location of a #define to be in the same file")
|
p.addError(pos, "internal error: expected start and end location of a macro to be in the same file")
|
||||||
|
break
|
||||||
}
|
}
|
||||||
if startOffset > endOffset {
|
if startOffset > endOffset {
|
||||||
panic("startOffset > endOffset")
|
p.addError(pos, "internal error: start offset of macro is after end offset")
|
||||||
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
// read file contents and extract the relevant byte range
|
// read file contents and extract the relevant byte range
|
||||||
@@ -252,59 +236,29 @@ func tinygo_clang_globals_visitor(c, parent C.GoCXCursor, client_data C.CXClient
|
|||||||
var size C.size_t
|
var size C.size_t
|
||||||
sourcePtr := C.clang_getFileContents(tu, file, &size)
|
sourcePtr := C.clang_getFileContents(tu, file, &size)
|
||||||
if endOffset >= C.uint(size) {
|
if endOffset >= C.uint(size) {
|
||||||
panic("endOffset lies after end of file")
|
p.addError(pos, "internal error: end offset of macro lies after end of file")
|
||||||
|
break
|
||||||
}
|
}
|
||||||
source := string(((*[1 << 28]byte)(unsafe.Pointer(sourcePtr)))[startOffset:endOffset:endOffset])
|
source := string(((*[1 << 28]byte)(unsafe.Pointer(sourcePtr)))[startOffset:endOffset:endOffset])
|
||||||
if !strings.HasPrefix(source, name) {
|
if !strings.HasPrefix(source, name) {
|
||||||
panic(fmt.Sprintf("expected #define value to start with %#v, got %#v", name, source))
|
p.addError(pos, fmt.Sprintf("internal error: expected macro value to start with %#v, got %#v", name, source))
|
||||||
|
break
|
||||||
}
|
}
|
||||||
value := strings.TrimSpace(source[len(name):])
|
value := source[len(name):]
|
||||||
for len(value) != 0 && value[0] == '(' && value[len(value)-1] == ')' {
|
// Try to convert this #define into a Go constant expression.
|
||||||
value = strings.TrimSpace(value[1 : len(value)-1])
|
expr, err := parseConst(pos+token.Pos(len(name)), p.fset, value)
|
||||||
|
if err != nil {
|
||||||
|
p.errors = append(p.errors, err)
|
||||||
}
|
}
|
||||||
if len(value) == 0 {
|
if expr != nil {
|
||||||
// Pretend it doesn't exist at all.
|
// Parsing was successful.
|
||||||
return C.CXChildVisit_Continue
|
p.constants[name] = constantInfo{expr, pos}
|
||||||
}
|
|
||||||
// For information about integer literals:
|
|
||||||
// https://en.cppreference.com/w/cpp/language/integer_literal
|
|
||||||
if value[0] == '"' {
|
|
||||||
// string constant
|
|
||||||
p.constants[name] = constantInfo{&ast.BasicLit{pos, token.STRING, value}, pos}
|
|
||||||
return C.CXChildVisit_Continue
|
|
||||||
}
|
|
||||||
if value[0] == '\'' {
|
|
||||||
// char constant
|
|
||||||
p.constants[name] = constantInfo{&ast.BasicLit{pos, token.CHAR, value}, pos}
|
|
||||||
return C.CXChildVisit_Continue
|
|
||||||
}
|
|
||||||
// assume it's a number (int or float)
|
|
||||||
value = strings.Replace(value, "'", "", -1) // remove ' chars
|
|
||||||
value = strings.TrimRight(value, "lu") // remove llu suffixes etc.
|
|
||||||
// find the first non-number
|
|
||||||
nonnum := byte(0)
|
|
||||||
for i := 0; i < len(value); i++ {
|
|
||||||
if value[i] < '0' || value[i] > '9' {
|
|
||||||
nonnum = value[i]
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// determine number type based on the first non-number
|
|
||||||
switch nonnum {
|
|
||||||
case 0:
|
|
||||||
// no non-number found, must be an integer
|
|
||||||
p.constants[name] = constantInfo{&ast.BasicLit{pos, token.INT, value}, pos}
|
|
||||||
case 'x', 'X':
|
|
||||||
// hex integer constant
|
|
||||||
// TODO: may also be a floating point number per C++17.
|
|
||||||
p.constants[name] = constantInfo{&ast.BasicLit{pos, token.INT, value}, pos}
|
|
||||||
case '.', 'e':
|
|
||||||
// float constant
|
|
||||||
value = strings.TrimRight(value, "fFlL")
|
|
||||||
p.constants[name] = constantInfo{&ast.BasicLit{pos, token.FLOAT, value}, pos}
|
|
||||||
default:
|
|
||||||
// unknown type, ignore
|
|
||||||
}
|
}
|
||||||
|
case C.CXCursor_EnumDecl:
|
||||||
|
// Visit all enums, because the fields may be used even when the enum
|
||||||
|
// type itself is not.
|
||||||
|
typ := C.tinygo_clang_getCursorType(c)
|
||||||
|
p.makeASTType(typ, pos)
|
||||||
}
|
}
|
||||||
return C.CXChildVisit_Continue
|
return C.CXChildVisit_Continue
|
||||||
}
|
}
|
||||||
@@ -316,11 +270,16 @@ func getString(clangString C.CXString) (s string) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// getCursorPosition returns a usable token.Pos from a libclang cursor. If the
|
// getCursorPosition returns a usable token.Pos from a libclang cursor.
|
||||||
// file for this cursor has not been seen before, it is read from libclang
|
|
||||||
// (which already has the file in memory) and added to the token.FileSet.
|
|
||||||
func (p *cgoPackage) getCursorPosition(cursor C.GoCXCursor) token.Pos {
|
func (p *cgoPackage) getCursorPosition(cursor C.GoCXCursor) token.Pos {
|
||||||
location := C.tinygo_clang_getCursorLocation(cursor)
|
return p.getClangLocationPosition(C.tinygo_clang_getCursorLocation(cursor), C.tinygo_clang_Cursor_getTranslationUnit(cursor))
|
||||||
|
}
|
||||||
|
|
||||||
|
// getClangLocationPosition returns a usable token.Pos based on a libclang
|
||||||
|
// location and translation unit. If the file for this cursor has not been seen
|
||||||
|
// before, it is read from libclang (which already has the file in memory) and
|
||||||
|
// added to the token.FileSet.
|
||||||
|
func (p *cgoPackage) getClangLocationPosition(location C.CXSourceLocation, tu C.CXTranslationUnit) token.Pos {
|
||||||
var file C.CXFile
|
var file C.CXFile
|
||||||
var line C.unsigned
|
var line C.unsigned
|
||||||
var column C.unsigned
|
var column C.unsigned
|
||||||
@@ -334,7 +293,6 @@ func (p *cgoPackage) getCursorPosition(cursor C.GoCXCursor) token.Pos {
|
|||||||
if _, ok := p.tokenFiles[filename]; !ok {
|
if _, ok := p.tokenFiles[filename]; !ok {
|
||||||
// File has not been seen before in this package, add line information
|
// File has not been seen before in this package, add line information
|
||||||
// now by reading the file from libclang.
|
// now by reading the file from libclang.
|
||||||
tu := C.tinygo_clang_Cursor_getTranslationUnit(cursor)
|
|
||||||
var size C.size_t
|
var size C.size_t
|
||||||
sourcePtr := C.clang_getFileContents(tu, file, &size)
|
sourcePtr := C.clang_getFileContents(tu, file, &size)
|
||||||
source := ((*[1 << 28]byte)(unsafe.Pointer(sourcePtr)))[:size:size]
|
source := ((*[1 << 28]byte)(unsafe.Pointer(sourcePtr)))[:size:size]
|
||||||
@@ -348,7 +306,62 @@ func (p *cgoPackage) getCursorPosition(cursor C.GoCXCursor) token.Pos {
|
|||||||
f.SetLines(lines)
|
f.SetLines(lines)
|
||||||
p.tokenFiles[filename] = f
|
p.tokenFiles[filename] = f
|
||||||
}
|
}
|
||||||
return p.tokenFiles[filename].Pos(int(offset))
|
positionFile := p.tokenFiles[filename]
|
||||||
|
|
||||||
|
// Check for alternative line/column information (set with a line directive).
|
||||||
|
var filename2String C.CXString
|
||||||
|
var line2 C.unsigned
|
||||||
|
var column2 C.unsigned
|
||||||
|
C.clang_getPresumedLocation(location, &filename2String, &line2, &column2)
|
||||||
|
filename2 := getString(filename2String)
|
||||||
|
if filename2 != filename || line2 != line || column2 != column {
|
||||||
|
// The location was changed with a preprocessor directive.
|
||||||
|
// TODO: this only works for locations that are added in order. Adding
|
||||||
|
// line/column info to a file that already has line/column info after
|
||||||
|
// the given offset is ignored.
|
||||||
|
positionFile.AddLineColumnInfo(int(offset), filename2, int(line2), int(column2))
|
||||||
|
}
|
||||||
|
|
||||||
|
return positionFile.Pos(int(offset))
|
||||||
|
}
|
||||||
|
|
||||||
|
// addError is a utility function to add an error to the list of errors. It will
|
||||||
|
// convert the token position to a line/column position first, and call
|
||||||
|
// addErrorAt.
|
||||||
|
func (p *cgoPackage) addError(pos token.Pos, msg string) {
|
||||||
|
p.addErrorAt(p.fset.PositionFor(pos, true), msg)
|
||||||
|
}
|
||||||
|
|
||||||
|
// addErrorAfter is like addError, but adds the text `after` to the source
|
||||||
|
// location.
|
||||||
|
func (p *cgoPackage) addErrorAfter(pos token.Pos, after, msg string) {
|
||||||
|
position := p.fset.PositionFor(pos, true)
|
||||||
|
lines := strings.Split(after, "\n")
|
||||||
|
if len(lines) != 1 {
|
||||||
|
// Adjust lines.
|
||||||
|
// For why we can't just do pos+token.Pos(len(after)), see:
|
||||||
|
// https://github.com/golang/go/issues/35803
|
||||||
|
position.Line += len(lines) - 1
|
||||||
|
position.Column = len(lines[len(lines)-1]) + 1
|
||||||
|
} else {
|
||||||
|
position.Column += len(after)
|
||||||
|
}
|
||||||
|
p.addErrorAt(position, msg)
|
||||||
|
}
|
||||||
|
|
||||||
|
// addErrorAt is a utility function to add an error to the list of errors.
|
||||||
|
func (p *cgoPackage) addErrorAt(position token.Position, msg string) {
|
||||||
|
if filepath.IsAbs(position.Filename) {
|
||||||
|
// Relative paths for readability, like other Go parser errors.
|
||||||
|
relpath, err := filepath.Rel(p.dir, position.Filename)
|
||||||
|
if err == nil {
|
||||||
|
position.Filename = relpath
|
||||||
|
}
|
||||||
|
}
|
||||||
|
p.errors = append(p.errors, scanner.Error{
|
||||||
|
Pos: position,
|
||||||
|
Msg: msg,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// makeASTType return the ast.Expr for the given libclang type. In other words,
|
// makeASTType return the ast.Expr for the given libclang type. In other words,
|
||||||
@@ -459,7 +472,7 @@ func (p *cgoPackage) makeASTType(typ C.CXType, pos token.Pos) ast.Expr {
|
|||||||
// This happens for some very special purpose architectures
|
// This happens for some very special purpose architectures
|
||||||
// (DSPs etc.) that are not currently targeted.
|
// (DSPs etc.) that are not currently targeted.
|
||||||
// https://www.embecosm.com/2017/04/18/non-8-bit-char-support-in-clang-and-llvm/
|
// https://www.embecosm.com/2017/04/18/non-8-bit-char-support-in-clang-and-llvm/
|
||||||
panic("unknown char width")
|
p.addError(pos, fmt.Sprintf("unknown char width: %d", typeSize))
|
||||||
}
|
}
|
||||||
switch underlyingType.kind {
|
switch underlyingType.kind {
|
||||||
case C.CXType_Char_S:
|
case C.CXType_Char_S:
|
||||||
@@ -508,48 +521,44 @@ func (p *cgoPackage) makeASTType(typ C.CXType, pos token.Pos) ast.Expr {
|
|||||||
case C.CXType_Enum:
|
case C.CXType_Enum:
|
||||||
return p.makeASTType(underlying, pos)
|
return p.makeASTType(underlying, pos)
|
||||||
default:
|
default:
|
||||||
panic("unknown elaborated type")
|
typeKindSpelling := getString(C.clang_getTypeKindSpelling(underlying.kind))
|
||||||
|
p.addError(pos, fmt.Sprintf("unknown elaborated type (libclang type kind %s)", typeKindSpelling))
|
||||||
|
typeName = "<unknown>"
|
||||||
}
|
}
|
||||||
case C.CXType_Record:
|
case C.CXType_Record:
|
||||||
cursor := C.tinygo_clang_getTypeDeclaration(typ)
|
cursor := C.tinygo_clang_getTypeDeclaration(typ)
|
||||||
name := getString(C.tinygo_clang_getCursorSpelling(cursor))
|
name := getString(C.tinygo_clang_getCursorSpelling(cursor))
|
||||||
|
var cgoRecordPrefix string
|
||||||
|
switch C.tinygo_clang_getCursorKind(cursor) {
|
||||||
|
case C.CXCursor_StructDecl:
|
||||||
|
cgoRecordPrefix = "struct_"
|
||||||
|
case C.CXCursor_UnionDecl:
|
||||||
|
cgoRecordPrefix = "union_"
|
||||||
|
default:
|
||||||
|
// makeASTRecordType will create an appropriate error.
|
||||||
|
cgoRecordPrefix = "record_"
|
||||||
|
}
|
||||||
if name == "" {
|
if name == "" {
|
||||||
// Anonymous record, probably inside a typedef.
|
// Anonymous record, probably inside a typedef.
|
||||||
typeExpr, bitfieldList := p.makeASTRecordType(cursor, pos)
|
typeInfo := p.makeASTRecordType(cursor, pos)
|
||||||
if bitfieldList != nil {
|
if typeInfo.bitfields != nil || typeInfo.unionSize != 0 {
|
||||||
// This struct has bitfields, so we have to declare it as a
|
// This record is a union or is a struct with bitfields, so we
|
||||||
// named type (for bitfield getters/setters to work).
|
// have to declare it as a named type (for getters/setters to
|
||||||
|
// work).
|
||||||
p.anonStructNum++
|
p.anonStructNum++
|
||||||
cgoName := "struct_" + strconv.Itoa(p.anonStructNum)
|
cgoName := cgoRecordPrefix + strconv.Itoa(p.anonStructNum)
|
||||||
p.elaboratedTypes[cgoName] = &elaboratedTypeInfo{
|
p.elaboratedTypes[cgoName] = typeInfo
|
||||||
typeExpr: typeExpr,
|
|
||||||
pos: pos,
|
|
||||||
bitfields: bitfieldList,
|
|
||||||
}
|
|
||||||
return &ast.Ident{
|
return &ast.Ident{
|
||||||
NamePos: pos,
|
NamePos: pos,
|
||||||
Name: "C." + cgoName,
|
Name: "C." + cgoName,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return typeExpr
|
return typeInfo.typeExpr
|
||||||
} else {
|
} else {
|
||||||
var cgoName string
|
cgoName := cgoRecordPrefix + name
|
||||||
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 {
|
if _, ok := p.elaboratedTypes[cgoName]; !ok {
|
||||||
p.elaboratedTypes[cgoName] = nil // predeclare (to avoid endless recursion)
|
p.elaboratedTypes[cgoName] = nil // predeclare (to avoid endless recursion)
|
||||||
typeExpr, bitfieldList := p.makeASTRecordType(cursor, pos)
|
p.elaboratedTypes[cgoName] = p.makeASTRecordType(cursor, pos)
|
||||||
p.elaboratedTypes[cgoName] = &elaboratedTypeInfo{
|
|
||||||
typeExpr: typeExpr,
|
|
||||||
pos: pos,
|
|
||||||
bitfields: bitfieldList,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return &ast.Ident{
|
return &ast.Ident{
|
||||||
NamePos: pos,
|
NamePos: pos,
|
||||||
@@ -585,14 +594,10 @@ func (p *cgoPackage) makeASTType(typ C.CXType, pos token.Pos) ast.Expr {
|
|||||||
}
|
}
|
||||||
if typeName == "" {
|
if typeName == "" {
|
||||||
// Report this as an error.
|
// Report this as an error.
|
||||||
spelling := getString(C.clang_getTypeSpelling(typ))
|
typeSpelling := getString(C.clang_getTypeSpelling(typ))
|
||||||
p.errors = append(p.errors, scanner.Error{
|
typeKindSpelling := getString(C.clang_getTypeKindSpelling(typ.kind))
|
||||||
Pos: p.fset.PositionFor(pos, true),
|
p.addError(pos, fmt.Sprintf("unknown C type: %v (libclang type kind %s)", typeSpelling, typeKindSpelling))
|
||||||
Msg: fmt.Sprintf("unknown C type: %v (libclang type kind %d)", spelling, typ.kind),
|
typeName = "C.<unknown>"
|
||||||
})
|
|
||||||
// Fallback, probably incorrect but at least the error points to an odd
|
|
||||||
// type name.
|
|
||||||
typeName = "C." + spelling
|
|
||||||
}
|
}
|
||||||
return &ast.Ident{
|
return &ast.Ident{
|
||||||
NamePos: pos,
|
NamePos: pos,
|
||||||
@@ -601,9 +606,8 @@ func (p *cgoPackage) makeASTType(typ C.CXType, pos token.Pos) ast.Expr {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// makeASTRecordType parses a C record (struct or union) and translates it into
|
// 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
|
// a Go struct type.
|
||||||
// zero-lengt "C union" field, which cannot be written in Go directly.
|
func (p *cgoPackage) makeASTRecordType(cursor C.GoCXCursor, pos token.Pos) *elaboratedTypeInfo {
|
||||||
func (p *cgoPackage) makeASTRecordType(cursor C.GoCXCursor, pos token.Pos) (*ast.StructType, []bitfieldInfo) {
|
|
||||||
fieldList := &ast.FieldList{
|
fieldList := &ast.FieldList{
|
||||||
Opening: pos,
|
Opening: pos,
|
||||||
Closing: pos,
|
Closing: pos,
|
||||||
@@ -620,53 +624,53 @@ func (p *cgoPackage) makeASTRecordType(cursor C.GoCXCursor, pos token.Pos) (*ast
|
|||||||
}{fieldList, p, &inBitfield, &bitfieldNum, &bitfieldList})
|
}{fieldList, p, &inBitfield, &bitfieldNum, &bitfieldList})
|
||||||
defer storedRefs.Remove(ref)
|
defer storedRefs.Remove(ref)
|
||||||
C.tinygo_clang_visitChildren(cursor, C.CXCursorVisitor(C.tinygo_clang_struct_visitor), C.CXClientData(ref))
|
C.tinygo_clang_visitChildren(cursor, C.CXCursorVisitor(C.tinygo_clang_struct_visitor), C.CXClientData(ref))
|
||||||
|
renameFieldKeywords(fieldList)
|
||||||
switch C.tinygo_clang_getCursorKind(cursor) {
|
switch C.tinygo_clang_getCursorKind(cursor) {
|
||||||
case C.CXCursor_StructDecl:
|
case C.CXCursor_StructDecl:
|
||||||
return &ast.StructType{
|
return &elaboratedTypeInfo{
|
||||||
Struct: pos,
|
typeExpr: &ast.StructType{
|
||||||
Fields: fieldList,
|
Struct: pos,
|
||||||
}, bitfieldList
|
Fields: fieldList,
|
||||||
|
},
|
||||||
|
pos: pos,
|
||||||
|
bitfields: bitfieldList,
|
||||||
|
}
|
||||||
case C.CXCursor_UnionDecl:
|
case C.CXCursor_UnionDecl:
|
||||||
|
typeInfo := &elaboratedTypeInfo{
|
||||||
|
typeExpr: &ast.StructType{
|
||||||
|
Struct: pos,
|
||||||
|
Fields: fieldList,
|
||||||
|
},
|
||||||
|
pos: pos,
|
||||||
|
bitfields: bitfieldList,
|
||||||
|
}
|
||||||
|
if len(fieldList.List) <= 1 {
|
||||||
|
// Useless union, treat it as a regular struct.
|
||||||
|
return typeInfo
|
||||||
|
}
|
||||||
if bitfieldList != nil {
|
if bitfieldList != nil {
|
||||||
// This is valid C... but please don't do this.
|
// This is valid C... but please don't do this.
|
||||||
p.errors = append(p.errors, scanner.Error{
|
p.addError(pos, "bitfield in a union is not supported")
|
||||||
Pos: p.fset.PositionFor(pos, true),
|
|
||||||
Msg: fmt.Sprintf("bitfield in a union is not supported"),
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
if len(fieldList.List) > 1 {
|
typ := C.tinygo_clang_getCursorType(cursor)
|
||||||
// Insert a special field at the front (of zero width) as a
|
alignInBytes := int64(C.clang_Type_getAlignOf(typ))
|
||||||
// marker that this is struct is actually a union. This is done
|
sizeInBytes := int64(C.clang_Type_getSizeOf(typ))
|
||||||
// by giving the field a name that cannot be expressed directly
|
if sizeInBytes == 0 {
|
||||||
// in Go.
|
p.addError(pos, "zero-length union is not supported")
|
||||||
// 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{
|
typeInfo.unionSize = sizeInBytes
|
||||||
Struct: pos,
|
typeInfo.unionAlign = alignInBytes
|
||||||
Fields: fieldList,
|
return typeInfo
|
||||||
}, bitfieldList
|
|
||||||
default:
|
default:
|
||||||
panic("unknown record declaration")
|
cursorKind := C.tinygo_clang_getCursorKind(cursor)
|
||||||
|
cursorKindSpelling := getString(C.clang_getCursorKindSpelling(cursorKind))
|
||||||
|
p.addError(pos, fmt.Sprintf("expected StructDecl or UnionDecl, not %s", cursorKindSpelling))
|
||||||
|
return &elaboratedTypeInfo{
|
||||||
|
typeExpr: &ast.StructType{
|
||||||
|
Struct: pos,
|
||||||
|
},
|
||||||
|
pos: pos,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -684,8 +688,17 @@ func tinygo_clang_struct_visitor(c, parent C.GoCXCursor, client_data C.CXClientD
|
|||||||
inBitfield := passed.inBitfield
|
inBitfield := passed.inBitfield
|
||||||
bitfieldNum := passed.bitfieldNum
|
bitfieldNum := passed.bitfieldNum
|
||||||
bitfieldList := passed.bitfieldList
|
bitfieldList := passed.bitfieldList
|
||||||
if C.tinygo_clang_getCursorKind(c) != C.CXCursor_FieldDecl {
|
pos := p.getCursorPosition(c)
|
||||||
panic("expected field inside cursor")
|
switch cursorKind := C.tinygo_clang_getCursorKind(c); cursorKind {
|
||||||
|
case C.CXCursor_FieldDecl:
|
||||||
|
// Expected. This is a regular field.
|
||||||
|
case C.CXCursor_StructDecl, C.CXCursor_UnionDecl:
|
||||||
|
// Ignore. The next field will be the struct/union itself.
|
||||||
|
return C.CXChildVisit_Continue
|
||||||
|
default:
|
||||||
|
cursorKindSpelling := getString(C.clang_getCursorKindSpelling(cursorKind))
|
||||||
|
p.addError(pos, fmt.Sprintf("expected FieldDecl in struct or union, not %s", cursorKindSpelling))
|
||||||
|
return C.CXChildVisit_Continue
|
||||||
}
|
}
|
||||||
name := getString(C.tinygo_clang_getCursorSpelling(c))
|
name := getString(C.tinygo_clang_getCursorSpelling(c))
|
||||||
if name == "" {
|
if name == "" {
|
||||||
@@ -694,7 +707,6 @@ func tinygo_clang_struct_visitor(c, parent C.GoCXCursor, client_data C.CXClientD
|
|||||||
return C.CXChildVisit_Continue
|
return C.CXChildVisit_Continue
|
||||||
}
|
}
|
||||||
typ := C.tinygo_clang_getCursorType(c)
|
typ := C.tinygo_clang_getCursorType(c)
|
||||||
pos := p.getCursorPosition(c)
|
|
||||||
field := &ast.Field{
|
field := &ast.Field{
|
||||||
Type: p.makeASTType(typ, p.getCursorPosition(c)),
|
Type: p.makeASTType(typ, p.getCursorPosition(c)),
|
||||||
}
|
}
|
||||||
@@ -703,7 +715,8 @@ func tinygo_clang_struct_visitor(c, parent C.GoCXCursor, client_data C.CXClientD
|
|||||||
bitfieldOffset := offsetof % alignOf
|
bitfieldOffset := offsetof % alignOf
|
||||||
if bitfieldOffset != 0 {
|
if bitfieldOffset != 0 {
|
||||||
if C.tinygo_clang_Cursor_isBitField(c) != 1 {
|
if C.tinygo_clang_Cursor_isBitField(c) != 1 {
|
||||||
panic("expected a bitfield")
|
p.addError(pos, "expected a bitfield")
|
||||||
|
return C.CXChildVisit_Continue
|
||||||
}
|
}
|
||||||
if !*inBitfield {
|
if !*inBitfield {
|
||||||
*bitfieldNum++
|
*bitfieldNum++
|
||||||
|
|||||||
@@ -3,9 +3,9 @@
|
|||||||
package cgo
|
package cgo
|
||||||
|
|
||||||
/*
|
/*
|
||||||
#cgo linux CFLAGS: -I/usr/lib/llvm-8/include
|
#cgo linux CFLAGS: -I/usr/lib/llvm-9/include
|
||||||
#cgo darwin CFLAGS: -I/usr/local/opt/llvm/include
|
#cgo darwin CFLAGS: -I/usr/local/opt/llvm@9/include
|
||||||
#cgo linux LDFLAGS: -L/usr/lib/llvm-8/lib -lclang
|
#cgo linux LDFLAGS: -L/usr/lib/llvm-9/lib -lclang
|
||||||
#cgo darwin LDFLAGS: -L/usr/local/opt/llvm/lib -lclang -lffi
|
#cgo darwin LDFLAGS: -L/usr/local/opt/llvm@9/lib -lclang -lffi
|
||||||
*/
|
*/
|
||||||
import "C"
|
import "C"
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
// are slightly different from the ones defined in libclang.go, but they
|
// are slightly different from the ones defined in libclang.go, but they
|
||||||
// should be ABI compatible.
|
// should be ABI compatible.
|
||||||
|
|
||||||
#include <clang-c/Index.h> // if this fails, install libclang-8-dev
|
#include <clang-c/Index.h> // if this fails, install libclang-9-dev
|
||||||
|
|
||||||
CXCursor tinygo_clang_getTranslationUnitCursor(CXTranslationUnit tu) {
|
CXCursor tinygo_clang_getTranslationUnitCursor(CXTranslationUnit tu) {
|
||||||
return clang_getTranslationUnitCursor(tu);
|
return clang_getTranslationUnitCursor(tu);
|
||||||
|
|||||||
+301
@@ -0,0 +1,301 @@
|
|||||||
|
// Copyright 2018 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
// This file has been copied from the Go 1.13 release tree.
|
||||||
|
|
||||||
|
// Checking of compiler and linker flags.
|
||||||
|
// We must avoid flags like -fplugin=, which can allow
|
||||||
|
// arbitrary code execution during the build.
|
||||||
|
// Do not make changes here without carefully
|
||||||
|
// considering the implications.
|
||||||
|
// (That's why the code is isolated in a file named security.go.)
|
||||||
|
//
|
||||||
|
// Note that -Wl,foo means split foo on commas and pass to
|
||||||
|
// the linker, so that -Wl,-foo,bar means pass -foo bar to
|
||||||
|
// the linker. Similarly -Wa,foo for the assembler and so on.
|
||||||
|
// If any of these are permitted, the wildcard portion must
|
||||||
|
// disallow commas.
|
||||||
|
//
|
||||||
|
// Note also that GNU binutils accept any argument @foo
|
||||||
|
// as meaning "read more flags from the file foo", so we must
|
||||||
|
// guard against any command-line argument beginning with @,
|
||||||
|
// even things like "-I @foo".
|
||||||
|
// We use safeArg (which is even more conservative)
|
||||||
|
// to reject these.
|
||||||
|
//
|
||||||
|
// Even worse, gcc -I@foo (one arg) turns into cc1 -I @foo (two args),
|
||||||
|
// so although gcc doesn't expand the @foo, cc1 will.
|
||||||
|
// So out of paranoia, we reject @ at the beginning of every
|
||||||
|
// flag argument that might be split into its own argument.
|
||||||
|
|
||||||
|
package cgo
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"regexp"
|
||||||
|
"strings"
|
||||||
|
"unicode/utf8"
|
||||||
|
)
|
||||||
|
|
||||||
|
var re = regexp.MustCompile
|
||||||
|
|
||||||
|
var validCompilerFlags = []*regexp.Regexp{
|
||||||
|
re(`-D([A-Za-z_].*)`),
|
||||||
|
re(`-F([^@\-].*)`),
|
||||||
|
re(`-I([^@\-].*)`),
|
||||||
|
re(`-O`),
|
||||||
|
re(`-O([^@\-].*)`),
|
||||||
|
re(`-W`),
|
||||||
|
re(`-W([^@,]+)`), // -Wall but not -Wa,-foo.
|
||||||
|
re(`-Wa,-mbig-obj`),
|
||||||
|
re(`-Wp,-D([A-Za-z_].*)`),
|
||||||
|
re(`-ansi`),
|
||||||
|
re(`-f(no-)?asynchronous-unwind-tables`),
|
||||||
|
re(`-f(no-)?blocks`),
|
||||||
|
re(`-f(no-)builtin-[a-zA-Z0-9_]*`),
|
||||||
|
re(`-f(no-)?common`),
|
||||||
|
re(`-f(no-)?constant-cfstrings`),
|
||||||
|
re(`-fdiagnostics-show-note-include-stack`),
|
||||||
|
re(`-f(no-)?eliminate-unused-debug-types`),
|
||||||
|
re(`-f(no-)?exceptions`),
|
||||||
|
re(`-f(no-)?fast-math`),
|
||||||
|
re(`-f(no-)?inline-functions`),
|
||||||
|
re(`-finput-charset=([^@\-].*)`),
|
||||||
|
re(`-f(no-)?fat-lto-objects`),
|
||||||
|
re(`-f(no-)?keep-inline-dllexport`),
|
||||||
|
re(`-f(no-)?lto`),
|
||||||
|
re(`-fmacro-backtrace-limit=(.+)`),
|
||||||
|
re(`-fmessage-length=(.+)`),
|
||||||
|
re(`-f(no-)?modules`),
|
||||||
|
re(`-f(no-)?objc-arc`),
|
||||||
|
re(`-f(no-)?objc-nonfragile-abi`),
|
||||||
|
re(`-f(no-)?objc-legacy-dispatch`),
|
||||||
|
re(`-f(no-)?omit-frame-pointer`),
|
||||||
|
re(`-f(no-)?openmp(-simd)?`),
|
||||||
|
re(`-f(no-)?permissive`),
|
||||||
|
re(`-f(no-)?(pic|PIC|pie|PIE)`),
|
||||||
|
re(`-f(no-)?plt`),
|
||||||
|
re(`-f(no-)?rtti`),
|
||||||
|
re(`-f(no-)?split-stack`),
|
||||||
|
re(`-f(no-)?stack-(.+)`),
|
||||||
|
re(`-f(no-)?strict-aliasing`),
|
||||||
|
re(`-f(un)signed-char`),
|
||||||
|
re(`-f(no-)?use-linker-plugin`), // safe if -B is not used; we don't permit -B
|
||||||
|
re(`-f(no-)?visibility-inlines-hidden`),
|
||||||
|
re(`-fsanitize=(.+)`),
|
||||||
|
re(`-ftemplate-depth-(.+)`),
|
||||||
|
re(`-fvisibility=(.+)`),
|
||||||
|
re(`-g([^@\-].*)?`),
|
||||||
|
re(`-m32`),
|
||||||
|
re(`-m64`),
|
||||||
|
re(`-m(abi|arch|cpu|fpu|tune)=([^@\-].*)`),
|
||||||
|
re(`-m(no-)?v?aes`),
|
||||||
|
re(`-marm`),
|
||||||
|
re(`-m(no-)?avx[0-9a-z]*`),
|
||||||
|
re(`-mfloat-abi=([^@\-].*)`),
|
||||||
|
re(`-mfpmath=[0-9a-z,+]*`),
|
||||||
|
re(`-m(no-)?avx[0-9a-z.]*`),
|
||||||
|
re(`-m(no-)?ms-bitfields`),
|
||||||
|
re(`-m(no-)?stack-(.+)`),
|
||||||
|
re(`-mmacosx-(.+)`),
|
||||||
|
re(`-mios-simulator-version-min=(.+)`),
|
||||||
|
re(`-miphoneos-version-min=(.+)`),
|
||||||
|
re(`-mtvos-simulator-version-min=(.+)`),
|
||||||
|
re(`-mtvos-version-min=(.+)`),
|
||||||
|
re(`-mwatchos-simulator-version-min=(.+)`),
|
||||||
|
re(`-mwatchos-version-min=(.+)`),
|
||||||
|
re(`-mnop-fun-dllimport`),
|
||||||
|
re(`-m(no-)?sse[0-9.]*`),
|
||||||
|
re(`-m(no-)?ssse3`),
|
||||||
|
re(`-mthumb(-interwork)?`),
|
||||||
|
re(`-mthreads`),
|
||||||
|
re(`-mwindows`),
|
||||||
|
re(`--param=ssp-buffer-size=[0-9]*`),
|
||||||
|
re(`-pedantic(-errors)?`),
|
||||||
|
re(`-pipe`),
|
||||||
|
re(`-pthread`),
|
||||||
|
re(`-?-std=([^@\-].*)`),
|
||||||
|
re(`-?-stdlib=([^@\-].*)`),
|
||||||
|
re(`--sysroot=([^@\-].*)`),
|
||||||
|
re(`-w`),
|
||||||
|
re(`-x([^@\-].*)`),
|
||||||
|
re(`-v`),
|
||||||
|
}
|
||||||
|
|
||||||
|
var validCompilerFlagsWithNextArg = []string{
|
||||||
|
"-arch",
|
||||||
|
"-D",
|
||||||
|
"-I",
|
||||||
|
"-framework",
|
||||||
|
"-isysroot",
|
||||||
|
"-isystem",
|
||||||
|
"--sysroot",
|
||||||
|
"-target",
|
||||||
|
"-x",
|
||||||
|
}
|
||||||
|
|
||||||
|
var validLinkerFlags = []*regexp.Regexp{
|
||||||
|
re(`-F([^@\-].*)`),
|
||||||
|
re(`-l([^@\-].*)`),
|
||||||
|
re(`-L([^@\-].*)`),
|
||||||
|
re(`-O`),
|
||||||
|
re(`-O([^@\-].*)`),
|
||||||
|
re(`-f(no-)?(pic|PIC|pie|PIE)`),
|
||||||
|
re(`-f(no-)?openmp(-simd)?`),
|
||||||
|
re(`-fsanitize=([^@\-].*)`),
|
||||||
|
re(`-flat_namespace`),
|
||||||
|
re(`-g([^@\-].*)?`),
|
||||||
|
re(`-headerpad_max_install_names`),
|
||||||
|
re(`-m(abi|arch|cpu|fpu|tune)=([^@\-].*)`),
|
||||||
|
re(`-mfloat-abi=([^@\-].*)`),
|
||||||
|
re(`-mmacosx-(.+)`),
|
||||||
|
re(`-mios-simulator-version-min=(.+)`),
|
||||||
|
re(`-miphoneos-version-min=(.+)`),
|
||||||
|
re(`-mthreads`),
|
||||||
|
re(`-mwindows`),
|
||||||
|
re(`-(pic|PIC|pie|PIE)`),
|
||||||
|
re(`-pthread`),
|
||||||
|
re(`-rdynamic`),
|
||||||
|
re(`-shared`),
|
||||||
|
re(`-?-static([-a-z0-9+]*)`),
|
||||||
|
re(`-?-stdlib=([^@\-].*)`),
|
||||||
|
re(`-v`),
|
||||||
|
|
||||||
|
// Note that any wildcards in -Wl need to exclude comma,
|
||||||
|
// since -Wl splits its argument at commas and passes
|
||||||
|
// them all to the linker uninterpreted. Allowing comma
|
||||||
|
// in a wildcard would allow tunnelling arbitrary additional
|
||||||
|
// linker arguments through one of these.
|
||||||
|
re(`-Wl,--(no-)?allow-multiple-definition`),
|
||||||
|
re(`-Wl,--(no-)?allow-shlib-undefined`),
|
||||||
|
re(`-Wl,--(no-)?as-needed`),
|
||||||
|
re(`-Wl,-Bdynamic`),
|
||||||
|
re(`-Wl,-berok`),
|
||||||
|
re(`-Wl,-Bstatic`),
|
||||||
|
re(`-WL,-O([^@,\-][^,]*)?`),
|
||||||
|
re(`-Wl,-d[ny]`),
|
||||||
|
re(`-Wl,--disable-new-dtags`),
|
||||||
|
re(`-Wl,-e[=,][a-zA-Z0-9]*`),
|
||||||
|
re(`-Wl,--enable-new-dtags`),
|
||||||
|
re(`-Wl,--end-group`),
|
||||||
|
re(`-Wl,--(no-)?export-dynamic`),
|
||||||
|
re(`-Wl,-framework,[^,@\-][^,]+`),
|
||||||
|
re(`-Wl,-headerpad_max_install_names`),
|
||||||
|
re(`-Wl,--no-undefined`),
|
||||||
|
re(`-Wl,-R([^@\-][^,@]*$)`),
|
||||||
|
re(`-Wl,--just-symbols[=,]([^,@\-][^,@]+)`),
|
||||||
|
re(`-Wl,-rpath(-link)?[=,]([^,@\-][^,]+)`),
|
||||||
|
re(`-Wl,-s`),
|
||||||
|
re(`-Wl,-search_paths_first`),
|
||||||
|
re(`-Wl,-sectcreate,([^,@\-][^,]+),([^,@\-][^,]+),([^,@\-][^,]+)`),
|
||||||
|
re(`-Wl,--start-group`),
|
||||||
|
re(`-Wl,-?-static`),
|
||||||
|
re(`-Wl,-?-subsystem,(native|windows|console|posix|xbox)`),
|
||||||
|
re(`-Wl,-syslibroot[=,]([^,@\-][^,]+)`),
|
||||||
|
re(`-Wl,-undefined[=,]([^,@\-][^,]+)`),
|
||||||
|
re(`-Wl,-?-unresolved-symbols=[^,]+`),
|
||||||
|
re(`-Wl,--(no-)?warn-([^,]+)`),
|
||||||
|
re(`-Wl,-z,(no)?execstack`),
|
||||||
|
re(`-Wl,-z,relro`),
|
||||||
|
|
||||||
|
re(`[a-zA-Z0-9_/].*\.(a|o|obj|dll|dylib|so)`), // direct linker inputs: x.o or libfoo.so (but not -foo.o or @foo.o)
|
||||||
|
re(`\./.*\.(a|o|obj|dll|dylib|so)`),
|
||||||
|
}
|
||||||
|
|
||||||
|
var validLinkerFlagsWithNextArg = []string{
|
||||||
|
"-arch",
|
||||||
|
"-F",
|
||||||
|
"-l",
|
||||||
|
"-L",
|
||||||
|
"-framework",
|
||||||
|
"-isysroot",
|
||||||
|
"--sysroot",
|
||||||
|
"-target",
|
||||||
|
"-Wl,-framework",
|
||||||
|
"-Wl,-rpath",
|
||||||
|
"-Wl,-R",
|
||||||
|
"-Wl,--just-symbols",
|
||||||
|
"-Wl,-undefined",
|
||||||
|
}
|
||||||
|
|
||||||
|
func checkCompilerFlags(name string, list []string) error {
|
||||||
|
return checkFlags(name, list, validCompilerFlags, validCompilerFlagsWithNextArg)
|
||||||
|
}
|
||||||
|
|
||||||
|
func checkLinkerFlags(name string, list []string) error {
|
||||||
|
return checkFlags(name, list, validLinkerFlags, validLinkerFlagsWithNextArg)
|
||||||
|
}
|
||||||
|
|
||||||
|
func checkFlags(name string, list []string, valid []*regexp.Regexp, validNext []string) error {
|
||||||
|
// Let users override rules with $CGO_CFLAGS_ALLOW, $CGO_CFLAGS_DISALLOW, etc.
|
||||||
|
var (
|
||||||
|
allow *regexp.Regexp
|
||||||
|
disallow *regexp.Regexp
|
||||||
|
)
|
||||||
|
if env := os.Getenv("CGO_" + name + "_ALLOW"); env != "" {
|
||||||
|
r, err := regexp.Compile(env)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("parsing $CGO_%s_ALLOW: %v", name, err)
|
||||||
|
}
|
||||||
|
allow = r
|
||||||
|
}
|
||||||
|
if env := os.Getenv("CGO_" + name + "_DISALLOW"); env != "" {
|
||||||
|
r, err := regexp.Compile(env)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("parsing $CGO_%s_DISALLOW: %v", name, err)
|
||||||
|
}
|
||||||
|
disallow = r
|
||||||
|
}
|
||||||
|
|
||||||
|
Args:
|
||||||
|
for i := 0; i < len(list); i++ {
|
||||||
|
arg := list[i]
|
||||||
|
if disallow != nil && disallow.FindString(arg) == arg {
|
||||||
|
goto Bad
|
||||||
|
}
|
||||||
|
if allow != nil && allow.FindString(arg) == arg {
|
||||||
|
continue Args
|
||||||
|
}
|
||||||
|
for _, re := range valid {
|
||||||
|
if re.FindString(arg) == arg { // must be complete match
|
||||||
|
continue Args
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for _, x := range validNext {
|
||||||
|
if arg == x {
|
||||||
|
if i+1 < len(list) && safeArg(list[i+1]) {
|
||||||
|
i++
|
||||||
|
continue Args
|
||||||
|
}
|
||||||
|
|
||||||
|
// Permit -Wl,-framework -Wl,name.
|
||||||
|
if i+1 < len(list) &&
|
||||||
|
strings.HasPrefix(arg, "-Wl,") &&
|
||||||
|
strings.HasPrefix(list[i+1], "-Wl,") &&
|
||||||
|
safeArg(list[i+1][4:]) &&
|
||||||
|
!strings.Contains(list[i+1][4:], ",") {
|
||||||
|
i++
|
||||||
|
continue Args
|
||||||
|
}
|
||||||
|
|
||||||
|
if i+1 < len(list) {
|
||||||
|
return fmt.Errorf("invalid flag: %s %s (see https://golang.org/s/invalidflag)", arg, list[i+1])
|
||||||
|
}
|
||||||
|
return fmt.Errorf("invalid flag: %s without argument (see https://golang.org/s/invalidflag)", arg)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Bad:
|
||||||
|
return fmt.Errorf("invalid flag: %s", arg)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func safeArg(name string) bool {
|
||||||
|
if name == "" {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
c := name[0]
|
||||||
|
return '0' <= c && c <= '9' || 'A' <= c && c <= 'Z' || 'a' <= c && c <= 'z' || c == '.' || c == '_' || c == '/' || c >= utf8.RuneSelf
|
||||||
|
}
|
||||||
@@ -0,0 +1,260 @@
|
|||||||
|
// Copyright 2018 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
// This file has been copied from the Go 1.13 release tree.
|
||||||
|
|
||||||
|
package cgo
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
var goodCompilerFlags = [][]string{
|
||||||
|
{"-DFOO"},
|
||||||
|
{"-Dfoo=bar"},
|
||||||
|
{"-F/Qt"},
|
||||||
|
{"-I/"},
|
||||||
|
{"-I/etc/passwd"},
|
||||||
|
{"-I."},
|
||||||
|
{"-O"},
|
||||||
|
{"-O2"},
|
||||||
|
{"-Osmall"},
|
||||||
|
{"-W"},
|
||||||
|
{"-Wall"},
|
||||||
|
{"-fobjc-arc"},
|
||||||
|
{"-fno-objc-arc"},
|
||||||
|
{"-fomit-frame-pointer"},
|
||||||
|
{"-fno-omit-frame-pointer"},
|
||||||
|
{"-fpic"},
|
||||||
|
{"-fno-pic"},
|
||||||
|
{"-fPIC"},
|
||||||
|
{"-fno-PIC"},
|
||||||
|
{"-fpie"},
|
||||||
|
{"-fno-pie"},
|
||||||
|
{"-fPIE"},
|
||||||
|
{"-fno-PIE"},
|
||||||
|
{"-fsplit-stack"},
|
||||||
|
{"-fno-split-stack"},
|
||||||
|
{"-fstack-xxx"},
|
||||||
|
{"-fno-stack-xxx"},
|
||||||
|
{"-fsanitize=hands"},
|
||||||
|
{"-g"},
|
||||||
|
{"-ggdb"},
|
||||||
|
{"-march=souza"},
|
||||||
|
{"-mcpu=123"},
|
||||||
|
{"-mfpu=123"},
|
||||||
|
{"-mtune=happybirthday"},
|
||||||
|
{"-mstack-overflow"},
|
||||||
|
{"-mno-stack-overflow"},
|
||||||
|
{"-mmacosx-version"},
|
||||||
|
{"-mnop-fun-dllimport"},
|
||||||
|
{"-pthread"},
|
||||||
|
{"-std=c99"},
|
||||||
|
{"-xc"},
|
||||||
|
{"-D", "FOO"},
|
||||||
|
{"-D", "foo=bar"},
|
||||||
|
{"-I", "."},
|
||||||
|
{"-I", "/etc/passwd"},
|
||||||
|
{"-I", "世界"},
|
||||||
|
{"-framework", "Chocolate"},
|
||||||
|
{"-x", "c"},
|
||||||
|
{"-v"},
|
||||||
|
}
|
||||||
|
|
||||||
|
var badCompilerFlags = [][]string{
|
||||||
|
{"-D@X"},
|
||||||
|
{"-D-X"},
|
||||||
|
{"-F@dir"},
|
||||||
|
{"-F-dir"},
|
||||||
|
{"-I@dir"},
|
||||||
|
{"-I-dir"},
|
||||||
|
{"-O@1"},
|
||||||
|
{"-Wa,-foo"},
|
||||||
|
{"-W@foo"},
|
||||||
|
{"-g@gdb"},
|
||||||
|
{"-g-gdb"},
|
||||||
|
{"-march=@dawn"},
|
||||||
|
{"-march=-dawn"},
|
||||||
|
{"-std=@c99"},
|
||||||
|
{"-std=-c99"},
|
||||||
|
{"-x@c"},
|
||||||
|
{"-x-c"},
|
||||||
|
{"-D", "@foo"},
|
||||||
|
{"-D", "-foo"},
|
||||||
|
{"-I", "@foo"},
|
||||||
|
{"-I", "-foo"},
|
||||||
|
{"-framework", "-Caffeine"},
|
||||||
|
{"-framework", "@Home"},
|
||||||
|
{"-x", "--c"},
|
||||||
|
{"-x", "@obj"},
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCheckCompilerFlags(t *testing.T) {
|
||||||
|
for _, f := range goodCompilerFlags {
|
||||||
|
if err := checkCompilerFlags("test", f); err != nil {
|
||||||
|
t.Errorf("unexpected error for %q: %v", f, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for _, f := range badCompilerFlags {
|
||||||
|
if err := checkCompilerFlags("test", f); err == nil {
|
||||||
|
t.Errorf("missing error for %q", f)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var goodLinkerFlags = [][]string{
|
||||||
|
{"-Fbar"},
|
||||||
|
{"-lbar"},
|
||||||
|
{"-Lbar"},
|
||||||
|
{"-fpic"},
|
||||||
|
{"-fno-pic"},
|
||||||
|
{"-fPIC"},
|
||||||
|
{"-fno-PIC"},
|
||||||
|
{"-fpie"},
|
||||||
|
{"-fno-pie"},
|
||||||
|
{"-fPIE"},
|
||||||
|
{"-fno-PIE"},
|
||||||
|
{"-fsanitize=hands"},
|
||||||
|
{"-g"},
|
||||||
|
{"-ggdb"},
|
||||||
|
{"-march=souza"},
|
||||||
|
{"-mcpu=123"},
|
||||||
|
{"-mfpu=123"},
|
||||||
|
{"-mtune=happybirthday"},
|
||||||
|
{"-pic"},
|
||||||
|
{"-pthread"},
|
||||||
|
{"-Wl,-rpath,foo"},
|
||||||
|
{"-Wl,-rpath,$ORIGIN/foo"},
|
||||||
|
{"-Wl,-R", "/foo"},
|
||||||
|
{"-Wl,-R", "foo"},
|
||||||
|
{"-Wl,-R,foo"},
|
||||||
|
{"-Wl,--just-symbols=foo"},
|
||||||
|
{"-Wl,--just-symbols,foo"},
|
||||||
|
{"-Wl,--warn-error"},
|
||||||
|
{"-Wl,--no-warn-error"},
|
||||||
|
{"foo.so"},
|
||||||
|
{"_世界.dll"},
|
||||||
|
{"./x.o"},
|
||||||
|
{"libcgosotest.dylib"},
|
||||||
|
{"-F", "framework"},
|
||||||
|
{"-l", "."},
|
||||||
|
{"-l", "/etc/passwd"},
|
||||||
|
{"-l", "世界"},
|
||||||
|
{"-L", "framework"},
|
||||||
|
{"-framework", "Chocolate"},
|
||||||
|
{"-v"},
|
||||||
|
{"-Wl,-framework", "-Wl,Chocolate"},
|
||||||
|
{"-Wl,-framework,Chocolate"},
|
||||||
|
{"-Wl,-unresolved-symbols=ignore-all"},
|
||||||
|
}
|
||||||
|
|
||||||
|
var badLinkerFlags = [][]string{
|
||||||
|
{"-DFOO"},
|
||||||
|
{"-Dfoo=bar"},
|
||||||
|
{"-W"},
|
||||||
|
{"-Wall"},
|
||||||
|
{"-fobjc-arc"},
|
||||||
|
{"-fno-objc-arc"},
|
||||||
|
{"-fomit-frame-pointer"},
|
||||||
|
{"-fno-omit-frame-pointer"},
|
||||||
|
{"-fsplit-stack"},
|
||||||
|
{"-fno-split-stack"},
|
||||||
|
{"-fstack-xxx"},
|
||||||
|
{"-fno-stack-xxx"},
|
||||||
|
{"-mstack-overflow"},
|
||||||
|
{"-mno-stack-overflow"},
|
||||||
|
{"-mnop-fun-dllimport"},
|
||||||
|
{"-std=c99"},
|
||||||
|
{"-xc"},
|
||||||
|
{"-D", "FOO"},
|
||||||
|
{"-D", "foo=bar"},
|
||||||
|
{"-I", "FOO"},
|
||||||
|
{"-L", "@foo"},
|
||||||
|
{"-L", "-foo"},
|
||||||
|
{"-x", "c"},
|
||||||
|
{"-D@X"},
|
||||||
|
{"-D-X"},
|
||||||
|
{"-I@dir"},
|
||||||
|
{"-I-dir"},
|
||||||
|
{"-O@1"},
|
||||||
|
{"-Wa,-foo"},
|
||||||
|
{"-W@foo"},
|
||||||
|
{"-g@gdb"},
|
||||||
|
{"-g-gdb"},
|
||||||
|
{"-march=@dawn"},
|
||||||
|
{"-march=-dawn"},
|
||||||
|
{"-std=@c99"},
|
||||||
|
{"-std=-c99"},
|
||||||
|
{"-x@c"},
|
||||||
|
{"-x-c"},
|
||||||
|
{"-D", "@foo"},
|
||||||
|
{"-D", "-foo"},
|
||||||
|
{"-I", "@foo"},
|
||||||
|
{"-I", "-foo"},
|
||||||
|
{"-l", "@foo"},
|
||||||
|
{"-l", "-foo"},
|
||||||
|
{"-framework", "-Caffeine"},
|
||||||
|
{"-framework", "@Home"},
|
||||||
|
{"-Wl,-framework,-Caffeine"},
|
||||||
|
{"-Wl,-framework", "-Wl,@Home"},
|
||||||
|
{"-Wl,-framework", "@Home"},
|
||||||
|
{"-Wl,-framework,Chocolate,@Home"},
|
||||||
|
{"-x", "--c"},
|
||||||
|
{"-x", "@obj"},
|
||||||
|
{"-Wl,-rpath,@foo"},
|
||||||
|
{"-Wl,-R,foo,bar"},
|
||||||
|
{"-Wl,-R,@foo"},
|
||||||
|
{"-Wl,--just-symbols,@foo"},
|
||||||
|
{"../x.o"},
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCheckLinkerFlags(t *testing.T) {
|
||||||
|
for _, f := range goodLinkerFlags {
|
||||||
|
if err := checkLinkerFlags("test", f); err != nil {
|
||||||
|
t.Errorf("unexpected error for %q: %v", f, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for _, f := range badLinkerFlags {
|
||||||
|
if err := checkLinkerFlags("test", f); err == nil {
|
||||||
|
t.Errorf("missing error for %q", f)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCheckFlagAllowDisallow(t *testing.T) {
|
||||||
|
if err := checkCompilerFlags("TEST", []string{"-disallow"}); err == nil {
|
||||||
|
t.Fatalf("missing error for -disallow")
|
||||||
|
}
|
||||||
|
os.Setenv("CGO_TEST_ALLOW", "-disallo")
|
||||||
|
if err := checkCompilerFlags("TEST", []string{"-disallow"}); err == nil {
|
||||||
|
t.Fatalf("missing error for -disallow with CGO_TEST_ALLOW=-disallo")
|
||||||
|
}
|
||||||
|
os.Setenv("CGO_TEST_ALLOW", "-disallow")
|
||||||
|
if err := checkCompilerFlags("TEST", []string{"-disallow"}); err != nil {
|
||||||
|
t.Fatalf("unexpected error for -disallow with CGO_TEST_ALLOW=-disallow: %v", err)
|
||||||
|
}
|
||||||
|
os.Unsetenv("CGO_TEST_ALLOW")
|
||||||
|
|
||||||
|
if err := checkCompilerFlags("TEST", []string{"-Wall"}); err != nil {
|
||||||
|
t.Fatalf("unexpected error for -Wall: %v", err)
|
||||||
|
}
|
||||||
|
os.Setenv("CGO_TEST_DISALLOW", "-Wall")
|
||||||
|
if err := checkCompilerFlags("TEST", []string{"-Wall"}); err == nil {
|
||||||
|
t.Fatalf("missing error for -Wall with CGO_TEST_DISALLOW=-Wall")
|
||||||
|
}
|
||||||
|
os.Setenv("CGO_TEST_ALLOW", "-Wall") // disallow wins
|
||||||
|
if err := checkCompilerFlags("TEST", []string{"-Wall"}); err == nil {
|
||||||
|
t.Fatalf("missing error for -Wall with CGO_TEST_DISALLOW=-Wall and CGO_TEST_ALLOW=-Wall")
|
||||||
|
}
|
||||||
|
|
||||||
|
os.Setenv("CGO_TEST_ALLOW", "-fplugin.*")
|
||||||
|
os.Setenv("CGO_TEST_DISALLOW", "-fplugin=lint.so")
|
||||||
|
if err := checkCompilerFlags("TEST", []string{"-fplugin=faster.so"}); err != nil {
|
||||||
|
t.Fatalf("unexpected error for -fplugin=faster.so: %v", err)
|
||||||
|
}
|
||||||
|
if err := checkCompilerFlags("TEST", []string{"-fplugin=lint.so"}); err == nil {
|
||||||
|
t.Fatalf("missing error for -fplugin=lint.so: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
Vendored
+3
@@ -0,0 +1,3 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import "C"
|
||||||
Vendored
+26
@@ -0,0 +1,26 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import "unsafe"
|
||||||
|
|
||||||
|
var _ unsafe.Pointer
|
||||||
|
|
||||||
|
type C.int16_t = int16
|
||||||
|
type C.int32_t = int32
|
||||||
|
type C.int64_t = int64
|
||||||
|
type C.int8_t = int8
|
||||||
|
type C.uint16_t = uint16
|
||||||
|
type C.uint32_t = uint32
|
||||||
|
type C.uint64_t = uint64
|
||||||
|
type C.uint8_t = uint8
|
||||||
|
type C.uintptr_t = uintptr
|
||||||
|
type C.char uint8
|
||||||
|
type C.int int32
|
||||||
|
type C.long int32
|
||||||
|
type C.longlong int64
|
||||||
|
type C.schar int8
|
||||||
|
type C.short int16
|
||||||
|
type C.uchar uint8
|
||||||
|
type C.uint uint32
|
||||||
|
type C.ulong uint32
|
||||||
|
type C.ulonglong uint64
|
||||||
|
type C.ushort uint16
|
||||||
Vendored
+33
@@ -0,0 +1,33 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
/*
|
||||||
|
#warning some warning
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
int x;
|
||||||
|
int y;
|
||||||
|
} point_t;
|
||||||
|
|
||||||
|
typedef someType noType; // undefined type
|
||||||
|
|
||||||
|
#define SOME_CONST_1 5) // invalid const syntax
|
||||||
|
#define SOME_CONST_2 6) // const not used (so no error)
|
||||||
|
#define SOME_CONST_3 1234 // const too large for byte
|
||||||
|
*/
|
||||||
|
import "C"
|
||||||
|
|
||||||
|
// Make sure that errors for the following lines won't change with future
|
||||||
|
// additions to the CGo preamble.
|
||||||
|
//line errors.go:100
|
||||||
|
var (
|
||||||
|
// constant too large
|
||||||
|
_ C.uint8_t = 2 << 10
|
||||||
|
|
||||||
|
// z member does not exist
|
||||||
|
_ C.point_t = C.point_t{z: 3}
|
||||||
|
|
||||||
|
// constant has syntax error
|
||||||
|
_ = C.SOME_CONST_1
|
||||||
|
|
||||||
|
_ byte = C.SOME_CONST_3
|
||||||
|
)
|
||||||
Vendored
+43
@@ -0,0 +1,43 @@
|
|||||||
|
// CGo errors:
|
||||||
|
// testdata/errors.go:4:2: warning: some warning
|
||||||
|
// testdata/errors.go:11:9: error: unknown type name 'someType'
|
||||||
|
// testdata/errors.go:13:23: unexpected token )
|
||||||
|
|
||||||
|
// Type checking errors after CGo processing:
|
||||||
|
// testdata/errors.go:102: 2 << 10 (untyped int constant 2048) overflows uint8
|
||||||
|
// testdata/errors.go:105: unknown field z in struct literal
|
||||||
|
// testdata/errors.go:108: undeclared name: C.SOME_CONST_1
|
||||||
|
// testdata/errors.go:110: C.SOME_CONST_3 (untyped int constant 1234) overflows byte
|
||||||
|
|
||||||
|
package main
|
||||||
|
|
||||||
|
import "unsafe"
|
||||||
|
|
||||||
|
var _ unsafe.Pointer
|
||||||
|
|
||||||
|
const C.SOME_CONST_3 = 1234
|
||||||
|
|
||||||
|
type C.int16_t = int16
|
||||||
|
type C.int32_t = int32
|
||||||
|
type C.int64_t = int64
|
||||||
|
type C.int8_t = int8
|
||||||
|
type C.uint16_t = uint16
|
||||||
|
type C.uint32_t = uint32
|
||||||
|
type C.uint64_t = uint64
|
||||||
|
type C.uint8_t = uint8
|
||||||
|
type C.uintptr_t = uintptr
|
||||||
|
type C.char uint8
|
||||||
|
type C.int int32
|
||||||
|
type C.long int32
|
||||||
|
type C.longlong int64
|
||||||
|
type C.schar int8
|
||||||
|
type C.short int16
|
||||||
|
type C.uchar uint8
|
||||||
|
type C.uint uint32
|
||||||
|
type C.ulong uint32
|
||||||
|
type C.ulonglong uint64
|
||||||
|
type C.ushort uint16
|
||||||
|
type C.point_t = struct {
|
||||||
|
x C.int
|
||||||
|
y C.int
|
||||||
|
}
|
||||||
Vendored
+26
@@ -0,0 +1,26 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
/*
|
||||||
|
// this name doesn't exist
|
||||||
|
#cgo NOFLAGS: -foo
|
||||||
|
|
||||||
|
// unknown flag
|
||||||
|
#cgo CFLAGS: -fdoes-not-exist -DNOTDEFINED
|
||||||
|
|
||||||
|
#cgo CFLAGS: -DFOO
|
||||||
|
|
||||||
|
#if defined(FOO)
|
||||||
|
#define BAR 3
|
||||||
|
#else
|
||||||
|
#define BAR 5
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if defined(NOTDEFINED)
|
||||||
|
#warning flag must not be defined
|
||||||
|
#endif
|
||||||
|
*/
|
||||||
|
import "C"
|
||||||
|
|
||||||
|
var (
|
||||||
|
_ = C.BAR
|
||||||
|
)
|
||||||
Vendored
+32
@@ -0,0 +1,32 @@
|
|||||||
|
// CGo errors:
|
||||||
|
// testdata/flags.go:5:7: invalid #cgo line: NOFLAGS
|
||||||
|
// testdata/flags.go:8:13: invalid flag: -fdoes-not-exist
|
||||||
|
|
||||||
|
package main
|
||||||
|
|
||||||
|
import "unsafe"
|
||||||
|
|
||||||
|
var _ unsafe.Pointer
|
||||||
|
|
||||||
|
const C.BAR = 3
|
||||||
|
|
||||||
|
type C.int16_t = int16
|
||||||
|
type C.int32_t = int32
|
||||||
|
type C.int64_t = int64
|
||||||
|
type C.int8_t = int8
|
||||||
|
type C.uint16_t = uint16
|
||||||
|
type C.uint32_t = uint32
|
||||||
|
type C.uint64_t = uint64
|
||||||
|
type C.uint8_t = uint8
|
||||||
|
type C.uintptr_t = uintptr
|
||||||
|
type C.char uint8
|
||||||
|
type C.int int32
|
||||||
|
type C.long int32
|
||||||
|
type C.longlong int64
|
||||||
|
type C.schar int8
|
||||||
|
type C.short int16
|
||||||
|
type C.uchar uint8
|
||||||
|
type C.uint uint32
|
||||||
|
type C.ulong uint32
|
||||||
|
type C.ulonglong uint64
|
||||||
|
type C.ushort uint16
|
||||||
Vendored
+165
@@ -0,0 +1,165 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
/*
|
||||||
|
// Simple typedef.
|
||||||
|
typedef int myint;
|
||||||
|
|
||||||
|
// Structs, with or without name.
|
||||||
|
typedef struct {
|
||||||
|
int x;
|
||||||
|
int y;
|
||||||
|
} point2d_t;
|
||||||
|
typedef struct point3d {
|
||||||
|
int x;
|
||||||
|
int y;
|
||||||
|
int z;
|
||||||
|
} point3d_t;
|
||||||
|
|
||||||
|
// Structs with reserved field names.
|
||||||
|
struct type1 {
|
||||||
|
// All these fields should be renamed.
|
||||||
|
int type;
|
||||||
|
int _type;
|
||||||
|
int __type;
|
||||||
|
};
|
||||||
|
struct type2 {
|
||||||
|
// This field should not be renamed.
|
||||||
|
int _type;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Unions.
|
||||||
|
typedef union {
|
||||||
|
// Union should be treated as a struct.
|
||||||
|
int i;
|
||||||
|
} union1_t;
|
||||||
|
typedef union {
|
||||||
|
// Union must contain a single field and have special getters/setters.
|
||||||
|
int i;
|
||||||
|
double d;
|
||||||
|
short s;
|
||||||
|
} union3_t;
|
||||||
|
typedef union union2d {
|
||||||
|
int i;
|
||||||
|
double d[2];
|
||||||
|
} union2d_t;
|
||||||
|
typedef union {
|
||||||
|
unsigned char arr[10];
|
||||||
|
} unionarray_t;
|
||||||
|
|
||||||
|
// Nested structs and unions.
|
||||||
|
typedef struct {
|
||||||
|
point2d_t begin;
|
||||||
|
point2d_t end;
|
||||||
|
int tag;
|
||||||
|
union {
|
||||||
|
point2d_t area;
|
||||||
|
point3d_t solid;
|
||||||
|
} coord;
|
||||||
|
} struct_nested_t;
|
||||||
|
typedef union {
|
||||||
|
point3d_t point;
|
||||||
|
unionarray_t array;
|
||||||
|
union3_t thing;
|
||||||
|
} union_nested_t;
|
||||||
|
|
||||||
|
// Enums. These define constant numbers. All these constants must be given the
|
||||||
|
// correct number.
|
||||||
|
typedef enum option {
|
||||||
|
optionA,
|
||||||
|
optionB,
|
||||||
|
optionC = -5,
|
||||||
|
optionD,
|
||||||
|
optionE = 10,
|
||||||
|
optionF,
|
||||||
|
optionG,
|
||||||
|
} option_t;
|
||||||
|
enum unused {
|
||||||
|
unused1 = 5,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Anonymous enum.
|
||||||
|
typedef enum {
|
||||||
|
option2A = 20,
|
||||||
|
} option2_t;
|
||||||
|
|
||||||
|
// Various types that are usually translated directly to Go types, but storing
|
||||||
|
// them in a struct reveals them.
|
||||||
|
typedef struct {
|
||||||
|
float f;
|
||||||
|
double d;
|
||||||
|
int *ptr;
|
||||||
|
} types_t;
|
||||||
|
|
||||||
|
// Arrays.
|
||||||
|
typedef int myIntArray[10];
|
||||||
|
|
||||||
|
// Bitfields.
|
||||||
|
typedef struct {
|
||||||
|
unsigned char start;
|
||||||
|
unsigned char a : 5;
|
||||||
|
unsigned char b : 1;
|
||||||
|
unsigned char c : 2;
|
||||||
|
unsigned char :0; // new field
|
||||||
|
unsigned char d : 6;
|
||||||
|
unsigned char e : 3;
|
||||||
|
// Note that C++ allows bitfields bigger than the underlying type.
|
||||||
|
} bitfield_t;
|
||||||
|
*/
|
||||||
|
import "C"
|
||||||
|
|
||||||
|
var (
|
||||||
|
// Simple typedefs.
|
||||||
|
_ C.myint
|
||||||
|
|
||||||
|
// Structs.
|
||||||
|
_ C.point2d_t
|
||||||
|
_ C.point3d_t
|
||||||
|
_ C.struct_point3d
|
||||||
|
|
||||||
|
// Structs with reserved field names.
|
||||||
|
_ C.struct_type1
|
||||||
|
_ C.struct_type2
|
||||||
|
|
||||||
|
// Unions.
|
||||||
|
_ C.union1_t
|
||||||
|
_ C.union3_t
|
||||||
|
_ C.union2d_t
|
||||||
|
_ C.unionarray_t
|
||||||
|
|
||||||
|
// Nested structs and unions.
|
||||||
|
_ C.struct_nested_t
|
||||||
|
_ C.union_nested_t
|
||||||
|
|
||||||
|
// Enums (anonymous and named).
|
||||||
|
_ C.option_t
|
||||||
|
_ C.enum_option
|
||||||
|
_ C.option2_t
|
||||||
|
|
||||||
|
// Various types.
|
||||||
|
_ C.types_t
|
||||||
|
|
||||||
|
// Arrays.
|
||||||
|
_ C.myIntArray
|
||||||
|
)
|
||||||
|
|
||||||
|
// Test bitfield accesses.
|
||||||
|
func accessBitfields() {
|
||||||
|
var x C.bitfield_t
|
||||||
|
x.start = 3
|
||||||
|
x.set_bitfield_a(4)
|
||||||
|
x.set_bitfield_b(1)
|
||||||
|
x.set_bitfield_c(2)
|
||||||
|
x.d = 10
|
||||||
|
x.e = 5
|
||||||
|
var _ C.uchar = x.bitfield_a()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test union accesses.
|
||||||
|
func accessUnion() {
|
||||||
|
var union1 C.union1_t
|
||||||
|
union1.i = 5
|
||||||
|
|
||||||
|
var union2d C.union2d_t
|
||||||
|
var _ *C.int = union2d.unionfield_i()
|
||||||
|
var _ *[2]float64 = union2d.unionfield_d()
|
||||||
|
}
|
||||||
Vendored
+119
@@ -0,0 +1,119 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import "unsafe"
|
||||||
|
|
||||||
|
var _ unsafe.Pointer
|
||||||
|
|
||||||
|
const C.option2A = 20
|
||||||
|
const C.optionA = 0
|
||||||
|
const C.optionB = 1
|
||||||
|
const C.optionC = -5
|
||||||
|
const C.optionD = -4
|
||||||
|
const C.optionE = 10
|
||||||
|
const C.optionF = 11
|
||||||
|
const C.optionG = 12
|
||||||
|
const C.unused1 = 5
|
||||||
|
|
||||||
|
type C.int16_t = int16
|
||||||
|
type C.int32_t = int32
|
||||||
|
type C.int64_t = int64
|
||||||
|
type C.int8_t = int8
|
||||||
|
type C.uint16_t = uint16
|
||||||
|
type C.uint32_t = uint32
|
||||||
|
type C.uint64_t = uint64
|
||||||
|
type C.uint8_t = uint8
|
||||||
|
type C.uintptr_t = uintptr
|
||||||
|
type C.char uint8
|
||||||
|
type C.int int32
|
||||||
|
type C.long int32
|
||||||
|
type C.longlong int64
|
||||||
|
type C.schar int8
|
||||||
|
type C.short int16
|
||||||
|
type C.uchar uint8
|
||||||
|
type C.uint uint32
|
||||||
|
type C.ulong uint32
|
||||||
|
type C.ulonglong uint64
|
||||||
|
type C.ushort uint16
|
||||||
|
type C.bitfield_t = C.struct_4
|
||||||
|
type C.myIntArray = [10]C.int
|
||||||
|
type C.myint = C.int
|
||||||
|
type C.option2_t = C.uint
|
||||||
|
type C.option_t = C.enum_option
|
||||||
|
type C.point2d_t = struct {
|
||||||
|
x C.int
|
||||||
|
y C.int
|
||||||
|
}
|
||||||
|
type C.point3d_t = C.struct_point3d
|
||||||
|
type C.struct_nested_t = struct {
|
||||||
|
begin C.point2d_t
|
||||||
|
end C.point2d_t
|
||||||
|
tag C.int
|
||||||
|
|
||||||
|
coord C.union_2
|
||||||
|
}
|
||||||
|
type C.types_t = struct {
|
||||||
|
f float32
|
||||||
|
d float64
|
||||||
|
ptr *C.int
|
||||||
|
}
|
||||||
|
type C.union1_t = struct{ i C.int }
|
||||||
|
type C.union2d_t = C.union_union2d
|
||||||
|
type C.union3_t = C.union_1
|
||||||
|
type C.union_nested_t = C.union_3
|
||||||
|
type C.unionarray_t = struct{ arr [10]C.uchar }
|
||||||
|
|
||||||
|
func (s *C.struct_4) bitfield_a() C.uchar { return s.__bitfield_1 & 0x1f }
|
||||||
|
func (s *C.struct_4) set_bitfield_a(value C.uchar) { s.__bitfield_1 = s.__bitfield_1&^0x1f | value&0x1f<<0 }
|
||||||
|
func (s *C.struct_4) bitfield_b() C.uchar {
|
||||||
|
return s.__bitfield_1 >> 5 & 0x1
|
||||||
|
}
|
||||||
|
func (s *C.struct_4) set_bitfield_b(value C.uchar) { s.__bitfield_1 = s.__bitfield_1&^0x20 | value&0x1<<5 }
|
||||||
|
func (s *C.struct_4) bitfield_c() C.uchar {
|
||||||
|
return s.__bitfield_1 >> 6
|
||||||
|
}
|
||||||
|
func (s *C.struct_4) set_bitfield_c(value C.uchar,
|
||||||
|
|
||||||
|
) { s.__bitfield_1 = s.__bitfield_1&0x3f | value<<6 }
|
||||||
|
|
||||||
|
type C.struct_4 struct {
|
||||||
|
start C.uchar
|
||||||
|
__bitfield_1 C.uchar
|
||||||
|
|
||||||
|
d C.uchar
|
||||||
|
e C.uchar
|
||||||
|
}
|
||||||
|
type C.struct_point3d struct {
|
||||||
|
x C.int
|
||||||
|
y C.int
|
||||||
|
z C.int
|
||||||
|
}
|
||||||
|
type C.struct_type1 struct {
|
||||||
|
_type C.int
|
||||||
|
__type C.int
|
||||||
|
___type C.int
|
||||||
|
}
|
||||||
|
type C.struct_type2 struct{ _type C.int }
|
||||||
|
|
||||||
|
func (union *C.union_1) unionfield_i() *C.int { return (*C.int)(unsafe.Pointer(&union.$union)) }
|
||||||
|
func (union *C.union_1) unionfield_d() *float64 { return (*float64)(unsafe.Pointer(&union.$union)) }
|
||||||
|
func (union *C.union_1) unionfield_s() *C.short { return (*C.short)(unsafe.Pointer(&union.$union)) }
|
||||||
|
|
||||||
|
type C.union_1 struct{ $union uint64 }
|
||||||
|
|
||||||
|
func (union *C.union_2) unionfield_area() *C.point2d_t { return (*C.point2d_t)(unsafe.Pointer(&union.$union)) }
|
||||||
|
func (union *C.union_2) unionfield_solid() *C.point3d_t { return (*C.point3d_t)(unsafe.Pointer(&union.$union)) }
|
||||||
|
|
||||||
|
type C.union_2 struct{ $union [3]uint32 }
|
||||||
|
|
||||||
|
func (union *C.union_3) unionfield_point() *C.point3d_t { return (*C.point3d_t)(unsafe.Pointer(&union.$union)) }
|
||||||
|
func (union *C.union_3) unionfield_array() *C.unionarray_t { return (*C.unionarray_t)(unsafe.Pointer(&union.$union)) }
|
||||||
|
func (union *C.union_3) unionfield_thing() *C.union3_t { return (*C.union3_t)(unsafe.Pointer(&union.$union)) }
|
||||||
|
|
||||||
|
type C.union_3 struct{ $union [2]uint64 }
|
||||||
|
|
||||||
|
func (union *C.union_union2d) unionfield_i() *C.int { return (*C.int)(unsafe.Pointer(&union.$union)) }
|
||||||
|
func (union *C.union_union2d) unionfield_d() *[2]float64 { return (*[2]float64)(unsafe.Pointer(&union.$union)) }
|
||||||
|
|
||||||
|
type C.union_union2d struct{ $union [2]uint64 }
|
||||||
|
type C.enum_option C.int
|
||||||
|
type C.enum_unused C.uint
|
||||||
@@ -0,0 +1,221 @@
|
|||||||
|
// Package compileopts contains the configuration for a single to-be-built
|
||||||
|
// binary.
|
||||||
|
package compileopts
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"regexp"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/tinygo-org/tinygo/goenv"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Config keeps all configuration affecting the build in a single struct.
|
||||||
|
type Config struct {
|
||||||
|
Options *Options
|
||||||
|
Target *TargetSpec
|
||||||
|
GoMinorVersion int
|
||||||
|
ClangHeaders string // Clang built-in header include path
|
||||||
|
TestConfig TestConfig
|
||||||
|
}
|
||||||
|
|
||||||
|
// Triple returns the LLVM target triple, like armv6m-none-eabi.
|
||||||
|
func (c *Config) Triple() string {
|
||||||
|
return c.Target.Triple
|
||||||
|
}
|
||||||
|
|
||||||
|
// CPU returns the LLVM CPU name, like atmega328p or arm7tdmi. It may return an
|
||||||
|
// empty string if the CPU name is not known.
|
||||||
|
func (c *Config) CPU() string {
|
||||||
|
return c.Target.CPU
|
||||||
|
}
|
||||||
|
|
||||||
|
// Features returns a list of features this CPU supports. For example, for a
|
||||||
|
// RISC-V processor, that could be ["+a", "+c", "+m"]. For many targets, an
|
||||||
|
// empty list will be returned.
|
||||||
|
func (c *Config) Features() []string {
|
||||||
|
return c.Target.Features
|
||||||
|
}
|
||||||
|
|
||||||
|
// GOOS returns the GOOS of the target. This might not always be the actual OS:
|
||||||
|
// for example, bare-metal targets will usually pretend to be linux to get the
|
||||||
|
// standard library to compile.
|
||||||
|
func (c *Config) GOOS() string {
|
||||||
|
return c.Target.GOOS
|
||||||
|
}
|
||||||
|
|
||||||
|
// GOARCH returns the GOARCH of the target. This might not always be the actual
|
||||||
|
// archtecture: for example, the AVR target is not supported by the Go standard
|
||||||
|
// library so such targets will usually pretend to be linux/arm.
|
||||||
|
func (c *Config) GOARCH() string {
|
||||||
|
return c.Target.GOARCH
|
||||||
|
}
|
||||||
|
|
||||||
|
// BuildTags returns the complete list of build tags used during this build.
|
||||||
|
func (c *Config) BuildTags() []string {
|
||||||
|
tags := append(c.Target.BuildTags, []string{"tinygo", "gc." + c.GC(), "scheduler." + c.Scheduler()}...)
|
||||||
|
for i := 1; i <= c.GoMinorVersion; i++ {
|
||||||
|
tags = append(tags, fmt.Sprintf("go1.%d", i))
|
||||||
|
}
|
||||||
|
if extraTags := strings.Fields(c.Options.Tags); len(extraTags) != 0 {
|
||||||
|
tags = append(tags, extraTags...)
|
||||||
|
}
|
||||||
|
return tags
|
||||||
|
}
|
||||||
|
|
||||||
|
// GC returns the garbage collection strategy in use on this platform. Valid
|
||||||
|
// values are "none", "leaking", and "conservative".
|
||||||
|
func (c *Config) GC() string {
|
||||||
|
if c.Options.GC != "" {
|
||||||
|
return c.Options.GC
|
||||||
|
}
|
||||||
|
if c.Target.GC != "" {
|
||||||
|
return c.Target.GC
|
||||||
|
}
|
||||||
|
return "conservative"
|
||||||
|
}
|
||||||
|
|
||||||
|
// NeedsStackObjects returns true if the compiler should insert stack objects
|
||||||
|
// that can be traced by the garbage collector.
|
||||||
|
func (c *Config) NeedsStackObjects() bool {
|
||||||
|
if c.GC() != "conservative" {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
for _, tag := range c.BuildTags() {
|
||||||
|
if tag == "baremetal" {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// Scheduler returns the scheduler implementation. Valid values are "coroutines"
|
||||||
|
// and "tasks".
|
||||||
|
func (c *Config) Scheduler() string {
|
||||||
|
if c.Options.Scheduler != "" {
|
||||||
|
return c.Options.Scheduler
|
||||||
|
}
|
||||||
|
if c.Target.Scheduler != "" {
|
||||||
|
return c.Target.Scheduler
|
||||||
|
}
|
||||||
|
// Fall back to coroutines, which are supported everywhere.
|
||||||
|
return "coroutines"
|
||||||
|
}
|
||||||
|
|
||||||
|
// PanicStrategy returns the panic strategy selected for this target. Valid
|
||||||
|
// values are "print" (print the panic value, then exit) or "trap" (issue a trap
|
||||||
|
// instruction).
|
||||||
|
func (c *Config) PanicStrategy() string {
|
||||||
|
return c.Options.PanicStrategy
|
||||||
|
}
|
||||||
|
|
||||||
|
// CFlags returns the flags to pass to the C compiler. This is necessary for CGo
|
||||||
|
// preprocessing.
|
||||||
|
func (c *Config) CFlags() []string {
|
||||||
|
cflags := append([]string{}, c.Options.CFlags...)
|
||||||
|
for _, flag := range c.Target.CFlags {
|
||||||
|
cflags = append(cflags, strings.Replace(flag, "{root}", goenv.Get("TINYGOROOT"), -1))
|
||||||
|
}
|
||||||
|
return cflags
|
||||||
|
}
|
||||||
|
|
||||||
|
// LDFlags returns the flags to pass to the linker. A few more flags are needed
|
||||||
|
// (like the one for the compiler runtime), but this represents the majority of
|
||||||
|
// the flags.
|
||||||
|
func (c *Config) LDFlags() []string {
|
||||||
|
root := goenv.Get("TINYGOROOT")
|
||||||
|
// Merge and adjust LDFlags.
|
||||||
|
ldflags := append([]string{}, c.Options.LDFlags...)
|
||||||
|
for _, flag := range c.Target.LDFlags {
|
||||||
|
ldflags = append(ldflags, strings.Replace(flag, "{root}", root, -1))
|
||||||
|
}
|
||||||
|
ldflags = append(ldflags, "-L", root)
|
||||||
|
if c.Target.GOARCH == "wasm" {
|
||||||
|
// Round heap size to next multiple of 65536 (the WebAssembly page
|
||||||
|
// size).
|
||||||
|
heapSize := (c.Options.HeapSize + (65536 - 1)) &^ (65536 - 1)
|
||||||
|
ldflags = append(ldflags, "--initial-memory="+strconv.FormatInt(heapSize, 10))
|
||||||
|
}
|
||||||
|
if c.Target.LinkerScript != "" {
|
||||||
|
ldflags = append(ldflags, "-T", c.Target.LinkerScript)
|
||||||
|
}
|
||||||
|
return ldflags
|
||||||
|
}
|
||||||
|
|
||||||
|
// ExtraFiles returns the list of extra files to be built and linked with the
|
||||||
|
// executable. This can include extra C and assembly files.
|
||||||
|
func (c *Config) ExtraFiles() []string {
|
||||||
|
return c.Target.ExtraFiles
|
||||||
|
}
|
||||||
|
|
||||||
|
// DumpSSA returns whether to dump Go SSA while compiling (-dumpssa flag). Only
|
||||||
|
// enable this for debugging.
|
||||||
|
func (c *Config) DumpSSA() bool {
|
||||||
|
return c.Options.DumpSSA
|
||||||
|
}
|
||||||
|
|
||||||
|
// VerifyIR returns whether to run extra checks on the IR. This is normally
|
||||||
|
// disabled but enabled during testing.
|
||||||
|
func (c *Config) VerifyIR() bool {
|
||||||
|
return c.Options.VerifyIR
|
||||||
|
}
|
||||||
|
|
||||||
|
// Debug returns whether to add debug symbols to the IR, for debugging with GDB
|
||||||
|
// and similar.
|
||||||
|
func (c *Config) Debug() bool {
|
||||||
|
return c.Options.Debug
|
||||||
|
}
|
||||||
|
|
||||||
|
// Programmer returns the flash method and OpenOCD interface name given a
|
||||||
|
// particular configuration. It may either be all configured in the target JSON
|
||||||
|
// file or be modified using the -programmmer command-line option.
|
||||||
|
func (c *Config) Programmer() (method, openocdInterface string) {
|
||||||
|
switch c.Options.Programmer {
|
||||||
|
case "":
|
||||||
|
// No configuration supplied.
|
||||||
|
return c.Target.FlashMethod, c.Target.OpenOCDInterface
|
||||||
|
case "openocd", "msd", "command":
|
||||||
|
// The -programmer flag only specifies the flash method.
|
||||||
|
return c.Options.Programmer, c.Target.OpenOCDInterface
|
||||||
|
default:
|
||||||
|
// The -programmer flag specifies something else, assume it specifies
|
||||||
|
// the OpenOCD interface name.
|
||||||
|
return "openocd", c.Options.Programmer
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// OpenOCDConfiguration returns a list of command line arguments to OpenOCD.
|
||||||
|
// This list of command-line arguments is based on the various OpenOCD-related
|
||||||
|
// flags in the target specification.
|
||||||
|
func (c *Config) OpenOCDConfiguration() (args []string, err error) {
|
||||||
|
_, openocdInterface := c.Programmer()
|
||||||
|
if openocdInterface == "" {
|
||||||
|
return nil, errors.New("OpenOCD programmer not set")
|
||||||
|
}
|
||||||
|
if !regexp.MustCompile("^[\\p{L}0-9_-]+$").MatchString(openocdInterface) {
|
||||||
|
return nil, fmt.Errorf("OpenOCD programmer has an invalid name: %#v", openocdInterface)
|
||||||
|
}
|
||||||
|
if c.Target.OpenOCDTarget == "" {
|
||||||
|
return nil, errors.New("OpenOCD chip not set")
|
||||||
|
}
|
||||||
|
if !regexp.MustCompile("^[\\p{L}0-9_-]+$").MatchString(c.Target.OpenOCDTarget) {
|
||||||
|
return nil, fmt.Errorf("OpenOCD target has an invalid name: %#v", c.Target.OpenOCDTarget)
|
||||||
|
}
|
||||||
|
if c.Target.OpenOCDTransport != "" && c.Target.OpenOCDTransport != "swd" {
|
||||||
|
return nil, fmt.Errorf("unknown OpenOCD transport: %#v", c.Target.OpenOCDTransport)
|
||||||
|
}
|
||||||
|
args = []string{"-f", "interface/" + openocdInterface + ".cfg"}
|
||||||
|
if c.Target.OpenOCDTransport != "" {
|
||||||
|
args = append(args, "-c", "transport select "+c.Target.OpenOCDTransport)
|
||||||
|
}
|
||||||
|
args = append(args, "-f", "target/"+c.Target.OpenOCDTarget+".cfg")
|
||||||
|
return args, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type TestConfig struct {
|
||||||
|
CompileTestBinary bool
|
||||||
|
// TODO: Filter the test functions to run, include verbose flag, etc
|
||||||
|
}
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
package compileopts
|
||||||
|
|
||||||
|
// Options contains extra options to give to the compiler. These options are
|
||||||
|
// usually passed from the command line.
|
||||||
|
type Options struct {
|
||||||
|
Target string
|
||||||
|
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 TestConfig
|
||||||
|
Programmer string
|
||||||
|
}
|
||||||
@@ -1,15 +1,13 @@
|
|||||||
package main
|
package compileopts
|
||||||
|
|
||||||
|
// This file loads a target specification from a JSON file.
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
|
||||||
"io"
|
"io"
|
||||||
"io/ioutil"
|
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"regexp"
|
|
||||||
"runtime"
|
"runtime"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
@@ -36,10 +34,10 @@ type TargetSpec struct {
|
|||||||
RTLib string `json:"rtlib"` // compiler runtime library (libgcc, compiler-rt)
|
RTLib string `json:"rtlib"` // compiler runtime library (libgcc, compiler-rt)
|
||||||
CFlags []string `json:"cflags"`
|
CFlags []string `json:"cflags"`
|
||||||
LDFlags []string `json:"ldflags"`
|
LDFlags []string `json:"ldflags"`
|
||||||
|
LinkerScript string `json:"linkerscript"`
|
||||||
ExtraFiles []string `json:"extra-files"`
|
ExtraFiles []string `json:"extra-files"`
|
||||||
Emulator []string `json:"emulator"`
|
Emulator []string `json:"emulator"`
|
||||||
FlashCommand string `json:"flash-command"`
|
FlashCommand string `json:"flash-command"`
|
||||||
OCDDaemon []string `json:"ocd-daemon"`
|
|
||||||
GDB string `json:"gdb"`
|
GDB string `json:"gdb"`
|
||||||
PortReset string `json:"flash-1200-bps-reset"`
|
PortReset string `json:"flash-1200-bps-reset"`
|
||||||
FlashMethod string `json:"flash-method"`
|
FlashMethod string `json:"flash-method"`
|
||||||
@@ -86,6 +84,9 @@ func (spec *TargetSpec) copyProperties(spec2 *TargetSpec) {
|
|||||||
}
|
}
|
||||||
spec.CFlags = append(spec.CFlags, spec2.CFlags...)
|
spec.CFlags = append(spec.CFlags, spec2.CFlags...)
|
||||||
spec.LDFlags = append(spec.LDFlags, spec2.LDFlags...)
|
spec.LDFlags = append(spec.LDFlags, spec2.LDFlags...)
|
||||||
|
if spec2.LinkerScript != "" {
|
||||||
|
spec.LinkerScript = spec2.LinkerScript
|
||||||
|
}
|
||||||
spec.ExtraFiles = append(spec.ExtraFiles, spec2.ExtraFiles...)
|
spec.ExtraFiles = append(spec.ExtraFiles, spec2.ExtraFiles...)
|
||||||
if len(spec2.Emulator) != 0 {
|
if len(spec2.Emulator) != 0 {
|
||||||
spec.Emulator = spec2.Emulator
|
spec.Emulator = spec2.Emulator
|
||||||
@@ -93,9 +94,6 @@ func (spec *TargetSpec) copyProperties(spec2 *TargetSpec) {
|
|||||||
if spec2.FlashCommand != "" {
|
if spec2.FlashCommand != "" {
|
||||||
spec.FlashCommand = spec2.FlashCommand
|
spec.FlashCommand = spec2.FlashCommand
|
||||||
}
|
}
|
||||||
if len(spec2.OCDDaemon) != 0 {
|
|
||||||
spec.OCDDaemon = spec2.OCDDaemon
|
|
||||||
}
|
|
||||||
if spec2.GDB != "" {
|
if spec2.GDB != "" {
|
||||||
spec.GDB = spec2.GDB
|
spec.GDB = spec2.GDB
|
||||||
}
|
}
|
||||||
@@ -277,131 +275,3 @@ func defaultTarget(goos, goarch, triple string) (*TargetSpec, error) {
|
|||||||
}
|
}
|
||||||
return &spec, nil
|
return &spec, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// OpenOCDConfiguration returns a list of command line arguments to OpenOCD.
|
|
||||||
// This list of command-line arguments is based on the various OpenOCD-related
|
|
||||||
// flags in the target specification.
|
|
||||||
func (spec *TargetSpec) OpenOCDConfiguration() (args []string, err error) {
|
|
||||||
if spec.OpenOCDInterface == "" {
|
|
||||||
return nil, errors.New("OpenOCD programmer not set")
|
|
||||||
}
|
|
||||||
if !regexp.MustCompile("^[\\p{L}0-9_-]+$").MatchString(spec.OpenOCDInterface) {
|
|
||||||
return nil, fmt.Errorf("OpenOCD programmer has an invalid name: %#v", spec.OpenOCDInterface)
|
|
||||||
}
|
|
||||||
if spec.OpenOCDTarget == "" {
|
|
||||||
return nil, errors.New("OpenOCD chip not set")
|
|
||||||
}
|
|
||||||
if !regexp.MustCompile("^[\\p{L}0-9_-]+$").MatchString(spec.OpenOCDTarget) {
|
|
||||||
return nil, fmt.Errorf("OpenOCD target has an invalid name: %#v", spec.OpenOCDTarget)
|
|
||||||
}
|
|
||||||
if spec.OpenOCDTransport != "" && spec.OpenOCDTransport != "swd" {
|
|
||||||
return nil, fmt.Errorf("unknown OpenOCD transport: %#v", spec.OpenOCDTransport)
|
|
||||||
}
|
|
||||||
args = []string{"-f", "interface/" + spec.OpenOCDInterface + ".cfg"}
|
|
||||||
if spec.OpenOCDTransport != "" {
|
|
||||||
args = append(args, "-c", "transport select "+spec.OpenOCDTransport)
|
|
||||||
}
|
|
||||||
args = append(args, "-f", "target/"+spec.OpenOCDTarget+".cfg")
|
|
||||||
return args, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// getGorootVersion returns the major and minor version for a given GOROOT path.
|
|
||||||
// If the goroot cannot be determined, (0, 0) is returned.
|
|
||||||
func getGorootVersion(goroot string) (major, minor int, err error) {
|
|
||||||
s, err := getGorootVersionString(goroot)
|
|
||||||
if err != nil {
|
|
||||||
return 0, 0, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if s == "" || s[:2] != "go" {
|
|
||||||
return 0, 0, errors.New("could not parse Go version: version does not start with 'go' prefix")
|
|
||||||
}
|
|
||||||
|
|
||||||
parts := strings.Split(s[2:], ".")
|
|
||||||
if len(parts) < 2 {
|
|
||||||
return 0, 0, errors.New("could not parse Go version: version has less than two parts")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Ignore the errors, we don't really handle errors here anyway.
|
|
||||||
var trailing string
|
|
||||||
n, err := fmt.Sscanf(s, "go%d.%d%s", &major, &minor, &trailing)
|
|
||||||
if n == 2 && err == io.EOF {
|
|
||||||
// Means there were no trailing characters (i.e., not an alpha/beta)
|
|
||||||
err = nil
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
return 0, 0, fmt.Errorf("failed to parse version: %s", err)
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// getGorootVersionString returns the version string as reported by the Go
|
|
||||||
// toolchain for the given GOROOT path. It is usually of the form `go1.x.y` but
|
|
||||||
// can have some variations (for beta releases, for example).
|
|
||||||
func getGorootVersionString(goroot string) (string, error) {
|
|
||||||
if data, err := ioutil.ReadFile(filepath.Join(
|
|
||||||
goroot, "src", "runtime", "internal", "sys", "zversion.go")); err == nil {
|
|
||||||
|
|
||||||
r := regexp.MustCompile("const TheVersion = `(.*)`")
|
|
||||||
matches := r.FindSubmatch(data)
|
|
||||||
if len(matches) != 2 {
|
|
||||||
return "", errors.New("Invalid go version output:\n" + string(data))
|
|
||||||
}
|
|
||||||
|
|
||||||
return string(matches[1]), nil
|
|
||||||
|
|
||||||
} else if data, err := ioutil.ReadFile(filepath.Join(goroot, "VERSION")); err == nil {
|
|
||||||
return string(data), nil
|
|
||||||
|
|
||||||
} else {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// getClangHeaderPath returns the path to the built-in Clang headers. It tries
|
|
||||||
// multiple locations, which should make it find the directory when installed in
|
|
||||||
// various ways.
|
|
||||||
func getClangHeaderPath(TINYGOROOT string) string {
|
|
||||||
// Check whether we're running from the source directory.
|
|
||||||
path := filepath.Join(TINYGOROOT, "llvm", "tools", "clang", "lib", "Headers")
|
|
||||||
if _, err := os.Stat(path); !os.IsNotExist(err) {
|
|
||||||
return path
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check whether we're running from the installation directory.
|
|
||||||
path = filepath.Join(TINYGOROOT, "lib", "clang", "include")
|
|
||||||
if _, err := os.Stat(path); !os.IsNotExist(err) {
|
|
||||||
return path
|
|
||||||
}
|
|
||||||
|
|
||||||
// It looks like we are built with a system-installed LLVM. Do a last
|
|
||||||
// attempt: try to use Clang headers relative to the clang binary.
|
|
||||||
for _, cmdName := range commands["clang"] {
|
|
||||||
binpath, err := exec.LookPath(cmdName)
|
|
||||||
if err == nil {
|
|
||||||
// This should be the command that will also be used by
|
|
||||||
// execCommand. To avoid inconsistencies, make sure we use the
|
|
||||||
// headers relative to this command.
|
|
||||||
binpath, err = filepath.EvalSymlinks(binpath)
|
|
||||||
if err != nil {
|
|
||||||
// Unexpected.
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
// Example executable:
|
|
||||||
// /usr/lib/llvm-8/bin/clang
|
|
||||||
// Example include path:
|
|
||||||
// /usr/lib/llvm-8/lib/clang/8.0.1/include/
|
|
||||||
llvmRoot := filepath.Dir(filepath.Dir(binpath))
|
|
||||||
clangVersionRoot := filepath.Join(llvmRoot, "lib", "clang")
|
|
||||||
dirnames, err := ioutil.ReadDir(clangVersionRoot)
|
|
||||||
if err != nil || len(dirnames) != 1 {
|
|
||||||
// Unexpected.
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
return filepath.Join(clangVersionRoot, dirnames[0].Name(), "include")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Could not find it.
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
package main
|
package compileopts
|
||||||
|
|
||||||
import "testing"
|
import "testing"
|
||||||
|
|
||||||
+33
-15
@@ -6,6 +6,7 @@ package compiler
|
|||||||
import (
|
import (
|
||||||
"go/types"
|
"go/types"
|
||||||
|
|
||||||
|
"github.com/tinygo-org/tinygo/compiler/llvmutil"
|
||||||
"golang.org/x/tools/go/ssa"
|
"golang.org/x/tools/go/ssa"
|
||||||
"tinygo.org/x/go-llvm"
|
"tinygo.org/x/go-llvm"
|
||||||
)
|
)
|
||||||
@@ -122,7 +123,7 @@ func (c *Compiler) emitSelect(frame *Frame, expr *ssa.Select) llvm.Value {
|
|||||||
// Store this value in an alloca and put a pointer to this alloca
|
// Store this value in an alloca and put a pointer to this alloca
|
||||||
// in the send state.
|
// in the send state.
|
||||||
sendValue := c.getValue(frame, state.Send)
|
sendValue := c.getValue(frame, state.Send)
|
||||||
alloca := c.createEntryBlockAlloca(sendValue.Type(), "select.send.value")
|
alloca := llvmutil.CreateEntryBlockAlloca(c.builder, sendValue.Type(), "select.send.value")
|
||||||
c.builder.CreateStore(sendValue, alloca)
|
c.builder.CreateStore(sendValue, alloca)
|
||||||
ptr := c.builder.CreateBitCast(alloca, c.i8ptrType, "")
|
ptr := c.builder.CreateBitCast(alloca, c.i8ptrType, "")
|
||||||
selectState = c.builder.CreateInsertValue(selectState, ptr, 1, "")
|
selectState = c.builder.CreateInsertValue(selectState, ptr, 1, "")
|
||||||
@@ -136,7 +137,7 @@ func (c *Compiler) emitSelect(frame *Frame, expr *ssa.Select) llvm.Value {
|
|||||||
recvbuf := llvm.Undef(c.i8ptrType)
|
recvbuf := llvm.Undef(c.i8ptrType)
|
||||||
if hasReceives {
|
if hasReceives {
|
||||||
allocaType := llvm.ArrayType(c.ctx.Int8Type(), int(recvbufSize))
|
allocaType := llvm.ArrayType(c.ctx.Int8Type(), int(recvbufSize))
|
||||||
recvbufAlloca := c.builder.CreateAlloca(allocaType, "select.recvbuf.alloca")
|
recvbufAlloca, _, _ := c.createTemporaryAlloca(allocaType, "select.recvbuf.alloca")
|
||||||
recvbufAlloca.SetAlignment(recvbufAlign)
|
recvbufAlloca.SetAlignment(recvbufAlign)
|
||||||
recvbuf = c.builder.CreateGEP(recvbufAlloca, []llvm.Value{
|
recvbuf = c.builder.CreateGEP(recvbufAlloca, []llvm.Value{
|
||||||
llvm.ConstInt(c.ctx.Int32Type(), 0, false),
|
llvm.ConstInt(c.ctx.Int32Type(), 0, false),
|
||||||
@@ -146,7 +147,7 @@ func (c *Compiler) emitSelect(frame *Frame, expr *ssa.Select) llvm.Value {
|
|||||||
|
|
||||||
// Create the states slice (allocated on the stack).
|
// Create the states slice (allocated on the stack).
|
||||||
statesAllocaType := llvm.ArrayType(chanSelectStateType, len(selectStates))
|
statesAllocaType := llvm.ArrayType(chanSelectStateType, len(selectStates))
|
||||||
statesAlloca := c.builder.CreateAlloca(statesAllocaType, "select.states.alloca")
|
statesAlloca, statesI8, statesSize := c.createTemporaryAlloca(statesAllocaType, "select.states.alloca")
|
||||||
for i, state := range selectStates {
|
for i, state := range selectStates {
|
||||||
// Set each slice element to the appropriate channel.
|
// Set each slice element to the appropriate channel.
|
||||||
gep := c.builder.CreateGEP(statesAlloca, []llvm.Value{
|
gep := c.builder.CreateGEP(statesAlloca, []llvm.Value{
|
||||||
@@ -161,19 +162,36 @@ func (c *Compiler) emitSelect(frame *Frame, expr *ssa.Select) llvm.Value {
|
|||||||
}, "select.states")
|
}, "select.states")
|
||||||
statesLen := llvm.ConstInt(c.uintptrType, uint64(len(selectStates)), false)
|
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.
|
// Do the select in the runtime.
|
||||||
results := c.createRuntimeCall("chanSelect", []llvm.Value{
|
var results llvm.Value
|
||||||
recvbuf,
|
if expr.Blocking {
|
||||||
statesPtr, statesLen, statesLen, // []chanSelectState
|
// Stack-allocate operation structures.
|
||||||
blockingValue,
|
// If these were simply created as a slice, they would heap-allocate.
|
||||||
}, "")
|
chBlockAllocaType := llvm.ArrayType(c.getLLVMRuntimeType("channelBlockedList"), len(selectStates))
|
||||||
|
chBlockAlloca, chBlockAllocaPtr, chBlockSize := c.createTemporaryAlloca(chBlockAllocaType, "select.block.alloca")
|
||||||
|
chBlockLen := llvm.ConstInt(c.uintptrType, uint64(len(selectStates)), false)
|
||||||
|
chBlockPtr := c.builder.CreateGEP(chBlockAlloca, []llvm.Value{
|
||||||
|
llvm.ConstInt(c.ctx.Int32Type(), 0, false),
|
||||||
|
llvm.ConstInt(c.ctx.Int32Type(), 0, false),
|
||||||
|
}, "select.block")
|
||||||
|
|
||||||
|
results = c.createRuntimeCall("chanSelect", []llvm.Value{
|
||||||
|
recvbuf,
|
||||||
|
statesPtr, statesLen, statesLen, // []chanSelectState
|
||||||
|
chBlockPtr, chBlockLen, chBlockLen, // []channelBlockList
|
||||||
|
}, "select.result")
|
||||||
|
|
||||||
|
// Terminate the lifetime of the operation structures.
|
||||||
|
c.emitLifetimeEnd(chBlockAllocaPtr, chBlockSize)
|
||||||
|
} else {
|
||||||
|
results = c.createRuntimeCall("tryChanSelect", []llvm.Value{
|
||||||
|
recvbuf,
|
||||||
|
statesPtr, statesLen, statesLen, // []chanSelectState
|
||||||
|
}, "select.result")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Terminate the lifetime of the states alloca.
|
||||||
|
c.emitLifetimeEnd(statesI8, statesSize)
|
||||||
|
|
||||||
// The result value does not include all the possible received values,
|
// The result value does not include all the possible received values,
|
||||||
// because we can't load them in advance. Instead, the *ssa.Extract
|
// because we can't load them in advance. Instead, the *ssa.Extract
|
||||||
|
|||||||
+78
-33
@@ -10,14 +10,10 @@ import (
|
|||||||
"tinygo.org/x/go-llvm"
|
"tinygo.org/x/go-llvm"
|
||||||
)
|
)
|
||||||
|
|
||||||
func (c *Compiler) checkType(t llvm.Type, checked map[llvm.Type]struct{}, specials map[llvm.TypeKind]llvm.Type) {
|
func (c *Compiler) checkType(t llvm.Type, checked map[llvm.Type]struct{}, specials map[llvm.TypeKind]llvm.Type) error {
|
||||||
if t.IsNil() {
|
|
||||||
panic(t)
|
|
||||||
}
|
|
||||||
|
|
||||||
// prevent infinite recursion for self-referential types
|
// prevent infinite recursion for self-referential types
|
||||||
if _, ok := checked[t]; ok {
|
if _, ok := checked[t]; ok {
|
||||||
return
|
return nil
|
||||||
}
|
}
|
||||||
checked[t] = struct{}{}
|
checked[t] = struct{}{}
|
||||||
|
|
||||||
@@ -27,10 +23,10 @@ func (c *Compiler) checkType(t llvm.Type, checked map[llvm.Type]struct{}, specia
|
|||||||
// this is correct
|
// this is correct
|
||||||
case t.Context() == llvm.GlobalContext():
|
case t.Context() == llvm.GlobalContext():
|
||||||
// somewhere we accidentally used the global context instead of a real context
|
// somewhere we accidentally used the global context instead of a real context
|
||||||
panic(fmt.Errorf("type %q uses global context", t.String()))
|
return fmt.Errorf("type %q uses global context", t.String())
|
||||||
default:
|
default:
|
||||||
// we used some other context by accident
|
// 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))
|
return 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
|
// if this is a composite type, check the components of the type
|
||||||
@@ -40,7 +36,7 @@ func (c *Compiler) checkType(t llvm.Type, checked map[llvm.Type]struct{}, specia
|
|||||||
if s, ok := specials[t.TypeKind()]; !ok {
|
if s, ok := specials[t.TypeKind()]; !ok {
|
||||||
specials[t.TypeKind()] = t
|
specials[t.TypeKind()] = t
|
||||||
} else if s != t {
|
} else if s != t {
|
||||||
panic(fmt.Errorf("duplicate special type %q: %v and %v", t.TypeKind().String(), t, s))
|
return 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:
|
case llvm.FloatTypeKind, llvm.DoubleTypeKind, llvm.X86_FP80TypeKind, llvm.FP128TypeKind, llvm.PPC_FP128TypeKind:
|
||||||
// floating point numbers are primitives - nothing to recurse
|
// floating point numbers are primitives - nothing to recurse
|
||||||
@@ -48,81 +44,130 @@ func (c *Compiler) checkType(t llvm.Type, checked map[llvm.Type]struct{}, specia
|
|||||||
// integers are primitives - nothing to recurse
|
// integers are primitives - nothing to recurse
|
||||||
case llvm.FunctionTypeKind:
|
case llvm.FunctionTypeKind:
|
||||||
// check arguments and return(s)
|
// check arguments and return(s)
|
||||||
for _, v := range t.ParamTypes() {
|
for i, v := range t.ParamTypes() {
|
||||||
c.checkType(v, checked, specials)
|
if err := c.checkType(v, checked, specials); err != nil {
|
||||||
|
return fmt.Errorf("failed to verify argument %d of type %s: %s", i, t.String(), err.Error())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if err := c.checkType(t.ReturnType(), checked, specials); err != nil {
|
||||||
|
return fmt.Errorf("failed to verify return type of type %s: %s", t.String(), err.Error())
|
||||||
}
|
}
|
||||||
c.checkType(t.ReturnType(), checked, specials)
|
|
||||||
case llvm.StructTypeKind:
|
case llvm.StructTypeKind:
|
||||||
// check all elements
|
// check all elements
|
||||||
for _, v := range t.StructElementTypes() {
|
for i, v := range t.StructElementTypes() {
|
||||||
c.checkType(v, checked, specials)
|
if err := c.checkType(v, checked, specials); err != nil {
|
||||||
|
return fmt.Errorf("failed to verify type of field %d of struct type %s: %s", i, t.String(), err.Error())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
case llvm.ArrayTypeKind:
|
case llvm.ArrayTypeKind:
|
||||||
// check element type
|
// check element type
|
||||||
c.checkType(t.ElementType(), checked, specials)
|
if err := c.checkType(t.ElementType(), checked, specials); err != nil {
|
||||||
|
return fmt.Errorf("failed to verify element type of array type %s: %s", t.String(), err.Error())
|
||||||
|
}
|
||||||
case llvm.PointerTypeKind:
|
case llvm.PointerTypeKind:
|
||||||
// check underlying type
|
// check underlying type
|
||||||
c.checkType(t.ElementType(), checked, specials)
|
if err := c.checkType(t.ElementType(), checked, specials); err != nil {
|
||||||
|
return fmt.Errorf("failed to verify underlying type of pointer type %s: %s", t.String(), err.Error())
|
||||||
|
}
|
||||||
case llvm.VectorTypeKind:
|
case llvm.VectorTypeKind:
|
||||||
// check element type
|
// check element type
|
||||||
c.checkType(t.ElementType(), checked, specials)
|
if err := c.checkType(t.ElementType(), checked, specials); err != nil {
|
||||||
|
return fmt.Errorf("failed to verify element type of vector type %s: %s", t.String(), err.Error())
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
return fmt.Errorf("unrecognized kind %q of type %s", t.TypeKind(), t.String())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Compiler) checkValue(v llvm.Value, types map[llvm.Type]struct{}, specials map[llvm.TypeKind]llvm.Type) {
|
func (c *Compiler) checkValue(v llvm.Value, types map[llvm.Type]struct{}, specials map[llvm.TypeKind]llvm.Type) error {
|
||||||
// check type
|
// check type
|
||||||
c.checkType(v.Type(), types, specials)
|
if err := c.checkType(v.Type(), types, specials); err != nil {
|
||||||
|
return fmt.Errorf("failed to verify type of value: %s", err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
// check if this is an undefined void
|
||||||
|
if v.IsUndef() && v.Type().TypeKind() == llvm.VoidTypeKind {
|
||||||
|
return errors.New("encountered undefined void value")
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Compiler) checkInstruction(inst llvm.Value, types map[llvm.Type]struct{}, specials map[llvm.TypeKind]llvm.Type) {
|
func (c *Compiler) checkInstruction(inst llvm.Value, types map[llvm.Type]struct{}, specials map[llvm.TypeKind]llvm.Type) error {
|
||||||
// check value properties
|
// check value properties
|
||||||
c.checkValue(inst, types, specials)
|
if err := c.checkValue(inst, types, specials); err != nil {
|
||||||
|
return fmt.Errorf("failed to validate value of instruction %q: %s", inst.Name(), err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
// check operands
|
// check operands
|
||||||
for i := 0; i < inst.OperandsCount(); i++ {
|
for i := 0; i < inst.OperandsCount(); i++ {
|
||||||
c.checkValue(inst.Operand(i), types, specials)
|
if err := c.checkValue(inst.Operand(i), types, specials); err != nil {
|
||||||
|
return fmt.Errorf("failed to validate argument %d of instruction %q: %s", i, inst.Name(), err.Error())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Compiler) checkBasicBlock(bb llvm.BasicBlock, types map[llvm.Type]struct{}, specials map[llvm.TypeKind]llvm.Type) {
|
func (c *Compiler) checkBasicBlock(bb llvm.BasicBlock, types map[llvm.Type]struct{}, specials map[llvm.TypeKind]llvm.Type) error {
|
||||||
// check basic block value and type
|
// check basic block value and type
|
||||||
c.checkValue(bb.AsValue(), types, specials)
|
if err := c.checkValue(bb.AsValue(), types, specials); err != nil {
|
||||||
|
return fmt.Errorf("failed to validate value of basic block %s: %s", bb.AsValue().Name(), err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
// check instructions
|
// check instructions
|
||||||
for inst := bb.FirstInstruction(); !inst.IsNil(); inst = llvm.NextInstruction(inst) {
|
for inst := bb.FirstInstruction(); !inst.IsNil(); inst = llvm.NextInstruction(inst) {
|
||||||
c.checkInstruction(inst, types, specials)
|
if err := c.checkInstruction(inst, types, specials); err != nil {
|
||||||
|
return fmt.Errorf("failed to validate basic block %q: %s", bb.AsValue().Name(), err.Error())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Compiler) checkFunction(fn llvm.Value, types map[llvm.Type]struct{}, specials map[llvm.TypeKind]llvm.Type) {
|
func (c *Compiler) checkFunction(fn llvm.Value, types map[llvm.Type]struct{}, specials map[llvm.TypeKind]llvm.Type) error {
|
||||||
// check function value and type
|
// check function value and type
|
||||||
c.checkValue(fn, types, specials)
|
if err := c.checkValue(fn, types, specials); err != nil {
|
||||||
|
return fmt.Errorf("failed to validate value of function %s: %s", fn.Name(), err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
// check basic blocks
|
// check basic blocks
|
||||||
for bb := fn.FirstBasicBlock(); !bb.IsNil(); bb = llvm.NextBasicBlock(bb) {
|
for bb := fn.FirstBasicBlock(); !bb.IsNil(); bb = llvm.NextBasicBlock(bb) {
|
||||||
c.checkBasicBlock(bb, types, specials)
|
if err := c.checkBasicBlock(bb, types, specials); err != nil {
|
||||||
|
return fmt.Errorf("failed to validate basic block of function %s: %s", fn.Name(), err.Error())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Compiler) checkModule() {
|
func (c *Compiler) checkModule() error {
|
||||||
// check for any context mismatches
|
// check for any context mismatches
|
||||||
switch {
|
switch {
|
||||||
case c.mod.Context() == c.ctx:
|
case c.mod.Context() == c.ctx:
|
||||||
// this is correct
|
// this is correct
|
||||||
case c.mod.Context() == llvm.GlobalContext():
|
case c.mod.Context() == llvm.GlobalContext():
|
||||||
// somewhere we accidentally used the global context instead of a real context
|
// somewhere we accidentally used the global context instead of a real context
|
||||||
panic(errors.New("module uses global context"))
|
return errors.New("module uses global context")
|
||||||
default:
|
default:
|
||||||
// we used some other context by accident
|
// 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))
|
return fmt.Errorf("module uses context %v instead of the main context %v", c.mod.Context(), c.ctx)
|
||||||
}
|
}
|
||||||
|
|
||||||
types := map[llvm.Type]struct{}{}
|
types := map[llvm.Type]struct{}{}
|
||||||
specials := map[llvm.TypeKind]llvm.Type{}
|
specials := map[llvm.TypeKind]llvm.Type{}
|
||||||
for fn := c.mod.FirstFunction(); !fn.IsNil(); fn = llvm.NextFunction(fn) {
|
for fn := c.mod.FirstFunction(); !fn.IsNil(); fn = llvm.NextFunction(fn) {
|
||||||
c.checkFunction(fn, types, specials)
|
if err := c.checkFunction(fn, types, specials); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
}
|
}
|
||||||
for g := c.mod.FirstGlobal(); !g.IsNil(); g = llvm.NextGlobal(g) {
|
for g := c.mod.FirstGlobal(); !g.IsNil(); g = llvm.NextGlobal(g) {
|
||||||
c.checkValue(g, types, specials)
|
if err := c.checkValue(g, types, specials); err != nil {
|
||||||
|
return fmt.Errorf("failed to verify global %s of module: %s", g.Name(), err.Error())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
+61
-156
@@ -14,6 +14,9 @@ import (
|
|||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"github.com/tinygo-org/tinygo/compileopts"
|
||||||
|
"github.com/tinygo-org/tinygo/compiler/llvmutil"
|
||||||
|
"github.com/tinygo-org/tinygo/goenv"
|
||||||
"github.com/tinygo-org/tinygo/ir"
|
"github.com/tinygo-org/tinygo/ir"
|
||||||
"github.com/tinygo-org/tinygo/loader"
|
"github.com/tinygo-org/tinygo/loader"
|
||||||
"golang.org/x/tools/go/ssa"
|
"golang.org/x/tools/go/ssa"
|
||||||
@@ -57,36 +60,8 @@ var coroFunctionsUsedInTransforms = []string{
|
|||||||
"runtime.llvmCoroRefHolder",
|
"runtime.llvmCoroRefHolder",
|
||||||
}
|
}
|
||||||
|
|
||||||
// Configure the compiler.
|
|
||||||
type Config struct {
|
|
||||||
Triple string // LLVM target triple, e.g. x86_64-unknown-linux-gnu (empty string means default)
|
|
||||||
CPU string // LLVM CPU name, e.g. atmega328p (empty string means default)
|
|
||||||
Features []string // LLVM CPU features
|
|
||||||
GOOS string //
|
|
||||||
GOARCH string //
|
|
||||||
GC string // garbage collection strategy
|
|
||||||
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 {
|
type Compiler struct {
|
||||||
Config
|
*compileopts.Config
|
||||||
mod llvm.Module
|
mod llvm.Module
|
||||||
ctx llvm.Context
|
ctx llvm.Context
|
||||||
builder llvm.Builder
|
builder llvm.Builder
|
||||||
@@ -129,36 +104,27 @@ type Phi struct {
|
|||||||
llvm llvm.Value
|
llvm llvm.Value
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewCompiler(pkgName string, config Config) (*Compiler, error) {
|
func NewCompiler(pkgName string, config *compileopts.Config) (*Compiler, error) {
|
||||||
if config.Triple == "" {
|
|
||||||
config.Triple = llvm.DefaultTargetTriple()
|
|
||||||
}
|
|
||||||
if len(config.BuildTags) == 0 {
|
|
||||||
config.BuildTags = []string{config.GOOS, config.GOARCH}
|
|
||||||
}
|
|
||||||
c := &Compiler{
|
c := &Compiler{
|
||||||
Config: config,
|
Config: config,
|
||||||
difiles: make(map[string]llvm.Metadata),
|
difiles: make(map[string]llvm.Metadata),
|
||||||
ditypes: make(map[types.Type]llvm.Metadata),
|
ditypes: make(map[types.Type]llvm.Metadata),
|
||||||
}
|
}
|
||||||
|
|
||||||
target, err := llvm.GetTargetFromTriple(config.Triple)
|
target, err := llvm.GetTargetFromTriple(config.Triple())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
features := ""
|
features := strings.Join(config.Features(), ",")
|
||||||
if len(config.Features) > 0 {
|
c.machine = target.CreateTargetMachine(config.Triple(), config.CPU(), features, llvm.CodeGenLevelDefault, llvm.RelocStatic, llvm.CodeModelDefault)
|
||||||
features = strings.Join(config.Features, `,`)
|
|
||||||
}
|
|
||||||
c.machine = target.CreateTargetMachine(config.Triple, config.CPU, features, llvm.CodeGenLevelDefault, llvm.RelocStatic, llvm.CodeModelDefault)
|
|
||||||
c.targetData = c.machine.CreateTargetData()
|
c.targetData = c.machine.CreateTargetData()
|
||||||
|
|
||||||
c.ctx = llvm.NewContext()
|
c.ctx = llvm.NewContext()
|
||||||
c.mod = c.ctx.NewModule(pkgName)
|
c.mod = c.ctx.NewModule(pkgName)
|
||||||
c.mod.SetTarget(config.Triple)
|
c.mod.SetTarget(config.Triple())
|
||||||
c.mod.SetDataLayout(c.targetData.String())
|
c.mod.SetDataLayout(c.targetData.String())
|
||||||
c.builder = c.ctx.NewBuilder()
|
c.builder = c.ctx.NewBuilder()
|
||||||
if c.Debug {
|
if c.Debug() {
|
||||||
c.dibuilder = llvm.NewDIBuilder(c.mod)
|
c.dibuilder = llvm.NewDIBuilder(c.mod)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -191,35 +157,16 @@ func (c *Compiler) Module() llvm.Module {
|
|||||||
return c.mod
|
return c.mod
|
||||||
}
|
}
|
||||||
|
|
||||||
// selectGC picks an appropriate GC strategy if none was provided.
|
|
||||||
func (c *Compiler) selectGC() string {
|
|
||||||
if c.GC != "" {
|
|
||||||
return c.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"
|
|
||||||
}
|
|
||||||
|
|
||||||
// getFunctionsUsedInTransforms gets a list of all special functions that should be preserved during transforms and optimization.
|
// getFunctionsUsedInTransforms gets a list of all special functions that should be preserved during transforms and optimization.
|
||||||
func (c *Compiler) getFunctionsUsedInTransforms() []string {
|
func (c *Compiler) getFunctionsUsedInTransforms() []string {
|
||||||
fnused := functionsUsedInTransforms
|
fnused := functionsUsedInTransforms
|
||||||
switch c.selectScheduler() {
|
switch c.Scheduler() {
|
||||||
case "coroutines":
|
case "coroutines":
|
||||||
fnused = append(append([]string{}, fnused...), coroFunctionsUsedInTransforms...)
|
fnused = append(append([]string{}, fnused...), coroFunctionsUsedInTransforms...)
|
||||||
case "tasks":
|
case "tasks":
|
||||||
fnused = append(append([]string{}, fnused...), taskFunctionsUsedInTransforms...)
|
fnused = append(append([]string{}, fnused...), taskFunctionsUsedInTransforms...)
|
||||||
default:
|
default:
|
||||||
panic(fmt.Errorf("invalid scheduler %q", c.selectScheduler()))
|
panic(fmt.Errorf("invalid scheduler %q", c.Scheduler()))
|
||||||
}
|
}
|
||||||
return fnused
|
return fnused
|
||||||
}
|
}
|
||||||
@@ -229,38 +176,37 @@ func (c *Compiler) getFunctionsUsedInTransforms() []string {
|
|||||||
func (c *Compiler) Compile(mainPath string) []error {
|
func (c *Compiler) Compile(mainPath string) []error {
|
||||||
// Prefix the GOPATH with the system GOROOT, as GOROOT is already set to
|
// Prefix the GOPATH with the system GOROOT, as GOROOT is already set to
|
||||||
// the TinyGo root.
|
// the TinyGo root.
|
||||||
overlayGopath := c.GOPATH
|
overlayGopath := goenv.Get("GOPATH")
|
||||||
if overlayGopath == "" {
|
if overlayGopath == "" {
|
||||||
overlayGopath = c.GOROOT
|
overlayGopath = goenv.Get("GOROOT")
|
||||||
} else {
|
} else {
|
||||||
overlayGopath = c.GOROOT + string(filepath.ListSeparator) + overlayGopath
|
overlayGopath = goenv.Get("GOROOT") + string(filepath.ListSeparator) + overlayGopath
|
||||||
}
|
}
|
||||||
|
|
||||||
wd, err := os.Getwd()
|
wd, err := os.Getwd()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return []error{err}
|
return []error{err}
|
||||||
}
|
}
|
||||||
buildTags := append([]string{"tinygo", "gc." + c.selectGC(), "scheduler." + c.selectScheduler()}, c.BuildTags...)
|
|
||||||
lprogram := &loader.Program{
|
lprogram := &loader.Program{
|
||||||
Build: &build.Context{
|
Build: &build.Context{
|
||||||
GOARCH: c.GOARCH,
|
GOARCH: c.GOARCH(),
|
||||||
GOOS: c.GOOS,
|
GOOS: c.GOOS(),
|
||||||
GOROOT: c.GOROOT,
|
GOROOT: goenv.Get("GOROOT"),
|
||||||
GOPATH: c.GOPATH,
|
GOPATH: goenv.Get("GOPATH"),
|
||||||
CgoEnabled: true,
|
CgoEnabled: true,
|
||||||
UseAllFiles: false,
|
UseAllFiles: false,
|
||||||
Compiler: "gc", // must be one of the recognized compilers
|
Compiler: "gc", // must be one of the recognized compilers
|
||||||
BuildTags: buildTags,
|
BuildTags: c.BuildTags(),
|
||||||
},
|
},
|
||||||
OverlayBuild: &build.Context{
|
OverlayBuild: &build.Context{
|
||||||
GOARCH: c.GOARCH,
|
GOARCH: c.GOARCH(),
|
||||||
GOOS: c.GOOS,
|
GOOS: c.GOOS(),
|
||||||
GOROOT: c.TINYGOROOT,
|
GOROOT: goenv.Get("TINYGOROOT"),
|
||||||
GOPATH: overlayGopath,
|
GOPATH: overlayGopath,
|
||||||
CgoEnabled: true,
|
CgoEnabled: true,
|
||||||
UseAllFiles: false,
|
UseAllFiles: false,
|
||||||
Compiler: "gc", // must be one of the recognized compilers
|
Compiler: "gc", // must be one of the recognized compilers
|
||||||
BuildTags: buildTags,
|
BuildTags: c.BuildTags(),
|
||||||
},
|
},
|
||||||
OverlayPath: func(path string) string {
|
OverlayPath: func(path string) string {
|
||||||
// Return the (overlay) import path when it should be overlaid, and
|
// Return the (overlay) import path when it should be overlaid, and
|
||||||
@@ -277,7 +223,7 @@ func (c *Compiler) Compile(mainPath string) []error {
|
|||||||
if strings.HasPrefix(path, "device/") || strings.HasPrefix(path, "examples/") {
|
if strings.HasPrefix(path, "device/") || strings.HasPrefix(path, "examples/") {
|
||||||
return path
|
return path
|
||||||
} else if path == "syscall" {
|
} else if path == "syscall" {
|
||||||
for _, tag := range c.BuildTags {
|
for _, tag := range c.BuildTags() {
|
||||||
if tag == "baremetal" || tag == "darwin" {
|
if tag == "baremetal" || tag == "darwin" {
|
||||||
return path
|
return path
|
||||||
}
|
}
|
||||||
@@ -294,8 +240,8 @@ func (c *Compiler) Compile(mainPath string) []error {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
Dir: wd,
|
Dir: wd,
|
||||||
TINYGOROOT: c.TINYGOROOT,
|
TINYGOROOT: goenv.Get("TINYGOROOT"),
|
||||||
CFlags: c.CFlags,
|
CFlags: c.CFlags(),
|
||||||
ClangHeaders: c.ClangHeaders,
|
ClangHeaders: c.ClangHeaders,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -327,7 +273,7 @@ func (c *Compiler) Compile(mainPath string) []error {
|
|||||||
c.ir.SimpleDCE()
|
c.ir.SimpleDCE()
|
||||||
|
|
||||||
// Initialize debug information.
|
// Initialize debug information.
|
||||||
if c.Debug {
|
if c.Debug() {
|
||||||
c.cu = c.dibuilder.CreateCompileUnit(llvm.DICompileUnit{
|
c.cu = c.dibuilder.CreateCompileUnit(llvm.DICompileUnit{
|
||||||
Language: 0xb, // DW_LANG_C99 (0xc, off-by-one?)
|
Language: 0xb, // DW_LANG_C99 (0xc, off-by-one?)
|
||||||
File: mainPath,
|
File: mainPath,
|
||||||
@@ -371,7 +317,7 @@ func (c *Compiler) Compile(mainPath string) []error {
|
|||||||
initFn := c.ir.GetFunction(c.ir.Program.ImportedPackage("runtime").Members["initAll"].(*ssa.Function))
|
initFn := c.ir.GetFunction(c.ir.Program.ImportedPackage("runtime").Members["initAll"].(*ssa.Function))
|
||||||
initFn.LLVMFn.SetLinkage(llvm.InternalLinkage)
|
initFn.LLVMFn.SetLinkage(llvm.InternalLinkage)
|
||||||
initFn.LLVMFn.SetUnnamedAddr(true)
|
initFn.LLVMFn.SetUnnamedAddr(true)
|
||||||
if c.Debug {
|
if c.Debug() {
|
||||||
difunc := c.attachDebugInfo(initFn)
|
difunc := c.attachDebugInfo(initFn)
|
||||||
pos := c.ir.Program.Fset.Position(initFn.Pos())
|
pos := c.ir.Program.Fset.Position(initFn.Pos())
|
||||||
c.builder.SetCurrentDebugLocation(uint(pos.Line), uint(pos.Column), difunc, llvm.Metadata{})
|
c.builder.SetCurrentDebugLocation(uint(pos.Line), uint(pos.Column), difunc, llvm.Metadata{})
|
||||||
@@ -435,7 +381,7 @@ func (c *Compiler) Compile(mainPath string) []error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// see: https://reviews.llvm.org/D18355
|
// see: https://reviews.llvm.org/D18355
|
||||||
if c.Debug {
|
if c.Debug() {
|
||||||
c.mod.AddNamedMetadataOperand("llvm.module.flags",
|
c.mod.AddNamedMetadataOperand("llvm.module.flags",
|
||||||
c.ctx.MDNode([]llvm.Metadata{
|
c.ctx.MDNode([]llvm.Metadata{
|
||||||
llvm.ConstInt(c.ctx.Int32Type(), 1, false).ConstantAsMetadata(), // Error on mismatch
|
llvm.ConstInt(c.ctx.Int32Type(), 1, false).ConstantAsMetadata(), // Error on mismatch
|
||||||
@@ -548,33 +494,6 @@ func (c *Compiler) getLLVMType(goType types.Type) llvm.Type {
|
|||||||
for i := 0; i < typ.NumFields(); i++ {
|
for i := 0; i < typ.NumFields(); i++ {
|
||||||
members[i] = c.getLLVMType(typ.Field(i).Type())
|
members[i] = c.getLLVMType(typ.Field(i).Type())
|
||||||
}
|
}
|
||||||
if len(members) > 2 && typ.Field(0).Name() == "C union" {
|
|
||||||
// Not a normal struct but a C union emitted by cgo.
|
|
||||||
// Such a field name cannot be entered in regular Go code, this must
|
|
||||||
// be manually inserted in the AST so this is safe.
|
|
||||||
maxAlign := 0
|
|
||||||
maxSize := uint64(0)
|
|
||||||
mainType := members[0]
|
|
||||||
for _, member := range members {
|
|
||||||
align := c.targetData.ABITypeAlignment(member)
|
|
||||||
size := c.targetData.TypeAllocSize(member)
|
|
||||||
if align > maxAlign {
|
|
||||||
maxAlign = align
|
|
||||||
mainType = member
|
|
||||||
} else if align == maxAlign && size > maxSize {
|
|
||||||
maxAlign = align
|
|
||||||
maxSize = size
|
|
||||||
mainType = member
|
|
||||||
} else if size > maxSize {
|
|
||||||
maxSize = size
|
|
||||||
}
|
|
||||||
}
|
|
||||||
members = []llvm.Type{mainType}
|
|
||||||
mainTypeSize := c.targetData.TypeAllocSize(mainType)
|
|
||||||
if mainTypeSize < maxSize {
|
|
||||||
members = append(members, llvm.ArrayType(c.ctx.Int8Type(), int(maxSize-mainTypeSize)))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return c.ctx.StructType(members, false)
|
return c.ctx.StructType(members, false)
|
||||||
case *types.Tuple:
|
case *types.Tuple:
|
||||||
members := make([]llvm.Type, typ.Len())
|
members := make([]llvm.Type, typ.Len())
|
||||||
@@ -883,7 +802,7 @@ func (c *Compiler) attachDebugInfoRaw(f *ir.Function, llvmFn llvm.Value, suffix,
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (c *Compiler) parseFunc(frame *Frame) {
|
func (c *Compiler) parseFunc(frame *Frame) {
|
||||||
if c.DumpSSA {
|
if c.DumpSSA() {
|
||||||
fmt.Printf("\nfunc %s:\n", frame.fn.Function)
|
fmt.Printf("\nfunc %s:\n", frame.fn.Function)
|
||||||
}
|
}
|
||||||
if !frame.fn.LLVMFn.IsDeclaration() {
|
if !frame.fn.LLVMFn.IsDeclaration() {
|
||||||
@@ -894,7 +813,7 @@ func (c *Compiler) parseFunc(frame *Frame) {
|
|||||||
frame.fn.LLVMFn.SetLinkage(llvm.InternalLinkage)
|
frame.fn.LLVMFn.SetLinkage(llvm.InternalLinkage)
|
||||||
frame.fn.LLVMFn.SetUnnamedAddr(true)
|
frame.fn.LLVMFn.SetUnnamedAddr(true)
|
||||||
}
|
}
|
||||||
if frame.fn.IsInterrupt() && strings.HasPrefix(c.Triple, "avr") {
|
if frame.fn.IsInterrupt() && strings.HasPrefix(c.Triple(), "avr") {
|
||||||
frame.fn.LLVMFn.SetFunctionCallConv(85) // CallingConv::AVR_SIGNAL
|
frame.fn.LLVMFn.SetFunctionCallConv(85) // CallingConv::AVR_SIGNAL
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -911,7 +830,7 @@ func (c *Compiler) parseFunc(frame *Frame) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Add debug info, if needed.
|
// Add debug info, if needed.
|
||||||
if c.Debug {
|
if c.Debug() {
|
||||||
if frame.fn.Synthetic == "package initializer" {
|
if frame.fn.Synthetic == "package initializer" {
|
||||||
// Package initializers have no debug info. Create some fake debug
|
// Package initializers have no debug info. Create some fake debug
|
||||||
// info to at least have *something*.
|
// info to at least have *something*.
|
||||||
@@ -945,7 +864,7 @@ func (c *Compiler) parseFunc(frame *Frame) {
|
|||||||
frame.locals[param] = c.collapseFormalParam(llvmType, fields)
|
frame.locals[param] = c.collapseFormalParam(llvmType, fields)
|
||||||
|
|
||||||
// Add debug information to this parameter (if available)
|
// Add debug information to this parameter (if available)
|
||||||
if c.Debug && frame.fn.Syntax() != nil {
|
if c.Debug() && frame.fn.Syntax() != nil {
|
||||||
pos := c.ir.Program.Fset.Position(frame.fn.Syntax().Pos())
|
pos := c.ir.Program.Fset.Position(frame.fn.Syntax().Pos())
|
||||||
diType := c.getDIType(param.Type())
|
diType := c.getDIType(param.Type())
|
||||||
dbgParam := c.dibuilder.CreateParameterVariable(frame.difunc, llvm.DIParameterVariable{
|
dbgParam := c.dibuilder.CreateParameterVariable(frame.difunc, llvm.DIParameterVariable{
|
||||||
@@ -1007,7 +926,7 @@ func (c *Compiler) parseFunc(frame *Frame) {
|
|||||||
|
|
||||||
// Fill blocks with instructions.
|
// Fill blocks with instructions.
|
||||||
for _, block := range frame.fn.DomPreorder() {
|
for _, block := range frame.fn.DomPreorder() {
|
||||||
if c.DumpSSA {
|
if c.DumpSSA() {
|
||||||
fmt.Printf("%d: %s:\n", block.Index, block.Comment)
|
fmt.Printf("%d: %s:\n", block.Index, block.Comment)
|
||||||
}
|
}
|
||||||
c.builder.SetInsertPointAtEnd(frame.blockEntries[block])
|
c.builder.SetInsertPointAtEnd(frame.blockEntries[block])
|
||||||
@@ -1016,7 +935,7 @@ func (c *Compiler) parseFunc(frame *Frame) {
|
|||||||
if _, ok := instr.(*ssa.DebugRef); ok {
|
if _, ok := instr.(*ssa.DebugRef); ok {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if c.DumpSSA {
|
if c.DumpSSA() {
|
||||||
if val, ok := instr.(ssa.Value); ok && val.Name() != "" {
|
if val, ok := instr.(ssa.Value); ok && val.Name() != "" {
|
||||||
fmt.Printf("\t%s = %s\n", val.Name(), val.String())
|
fmt.Printf("\t%s = %s\n", val.Name(), val.String())
|
||||||
} else {
|
} else {
|
||||||
@@ -1042,7 +961,7 @@ func (c *Compiler) parseFunc(frame *Frame) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (c *Compiler) parseInstr(frame *Frame, instr ssa.Instruction) {
|
func (c *Compiler) parseInstr(frame *Frame, instr ssa.Instruction) {
|
||||||
if c.Debug {
|
if c.Debug() {
|
||||||
pos := c.ir.Program.Fset.Position(instr.Pos())
|
pos := c.ir.Program.Fset.Position(instr.Pos())
|
||||||
c.builder.SetCurrentDebugLocation(uint(pos.Line), uint(pos.Column), frame.difunc, llvm.Metadata{})
|
c.builder.SetCurrentDebugLocation(uint(pos.Line), uint(pos.Column), frame.difunc, llvm.Metadata{})
|
||||||
}
|
}
|
||||||
@@ -1060,7 +979,7 @@ func (c *Compiler) parseInstr(frame *Frame, instr ssa.Instruction) {
|
|||||||
frame.locals[instr] = llvm.Undef(c.getLLVMType(instr.Type()))
|
frame.locals[instr] = llvm.Undef(c.getLLVMType(instr.Type()))
|
||||||
} else {
|
} else {
|
||||||
frame.locals[instr] = value
|
frame.locals[instr] = value
|
||||||
if len(*instr.Referrers()) != 0 && c.needsStackObjects() {
|
if len(*instr.Referrers()) != 0 && c.NeedsStackObjects() {
|
||||||
c.trackExpr(frame, instr, value)
|
c.trackExpr(frame, instr, value)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1080,28 +999,34 @@ func (c *Compiler) parseInstr(frame *Frame, instr ssa.Instruction) {
|
|||||||
// Static callee is known. This makes it easier to start a new
|
// Static callee is known. This makes it easier to start a new
|
||||||
// goroutine.
|
// goroutine.
|
||||||
calleeFn := c.ir.GetFunction(callee)
|
calleeFn := c.ir.GetFunction(callee)
|
||||||
if !calleeFn.IsExported() && c.selectScheduler() != "tasks" {
|
var context llvm.Value
|
||||||
// For coroutine scheduling, this is only required when calling
|
switch value := instr.Call.Value.(type) {
|
||||||
// an external function.
|
case *ssa.Function:
|
||||||
// For tasks, because all params are stored in a single object,
|
// Goroutine call is regular function call. No context is necessary.
|
||||||
// no unnecessary parameters should be stored anyway.
|
context = llvm.Undef(c.i8ptrType)
|
||||||
params = append(params, llvm.Undef(c.i8ptrType)) // context parameter
|
case *ssa.MakeClosure:
|
||||||
params = append(params, llvm.ConstPointerNull(c.i8ptrType)) // parent coroutine handle
|
// A goroutine call on a func value, but the callee is trivial to find. For
|
||||||
|
// example: immediately applied functions.
|
||||||
|
funcValue := c.getValue(frame, value)
|
||||||
|
context = c.extractFuncContext(funcValue)
|
||||||
|
default:
|
||||||
|
panic("StaticCallee returned an unexpected value")
|
||||||
}
|
}
|
||||||
|
params = append(params, context) // context parameter
|
||||||
c.emitStartGoroutine(calleeFn.LLVMFn, params)
|
c.emitStartGoroutine(calleeFn.LLVMFn, params)
|
||||||
} else if !instr.Call.IsInvoke() {
|
} else if !instr.Call.IsInvoke() {
|
||||||
// This is a function pointer.
|
// This is a function pointer.
|
||||||
// At the moment, two extra params are passed to the newly started
|
// At the moment, two extra params are passed to the newly started
|
||||||
// goroutine:
|
// goroutine:
|
||||||
// * The function context, for closures.
|
// * The function context, for closures.
|
||||||
// * The parent handle (for coroutines) or the function pointer
|
// * The function pointer (for tasks).
|
||||||
// itself (for tasks).
|
|
||||||
funcPtr, context := c.decodeFuncValue(c.getValue(frame, instr.Call.Value), instr.Call.Value.Type().(*types.Signature))
|
funcPtr, context := c.decodeFuncValue(c.getValue(frame, instr.Call.Value), instr.Call.Value.Type().(*types.Signature))
|
||||||
params = append(params, context) // context parameter
|
params = append(params, context) // context parameter
|
||||||
switch c.selectScheduler() {
|
switch c.Scheduler() {
|
||||||
case "coroutines":
|
case "coroutines":
|
||||||
params = append(params, llvm.ConstPointerNull(c.i8ptrType)) // parent coroutine handle
|
// There are no additional parameters needed for the goroutine start operation.
|
||||||
case "tasks":
|
case "tasks":
|
||||||
|
// Add the function pointer as a parameter to start the goroutine.
|
||||||
params = append(params, funcPtr)
|
params = append(params, funcPtr)
|
||||||
default:
|
default:
|
||||||
panic("unknown scheduler type")
|
panic("unknown scheduler type")
|
||||||
@@ -1462,7 +1387,7 @@ func (c *Compiler) parseExpr(frame *Frame, expr ssa.Value) (llvm.Value, error) {
|
|||||||
buf = c.builder.CreateBitCast(buf, llvm.PointerType(typ, 0), "")
|
buf = c.builder.CreateBitCast(buf, llvm.PointerType(typ, 0), "")
|
||||||
return buf, nil
|
return buf, nil
|
||||||
} else {
|
} else {
|
||||||
buf := c.createEntryBlockAlloca(typ, expr.Comment)
|
buf := llvmutil.CreateEntryBlockAlloca(c.builder, typ, expr.Comment)
|
||||||
if c.targetData.TypeAllocSize(typ) != 0 {
|
if c.targetData.TypeAllocSize(typ) != 0 {
|
||||||
c.builder.CreateStore(llvm.ConstNull(typ), buf) // zero-initialize var
|
c.builder.CreateStore(llvm.ConstNull(typ), buf) // zero-initialize var
|
||||||
}
|
}
|
||||||
@@ -1527,18 +1452,6 @@ func (c *Compiler) parseExpr(frame *Frame, expr ssa.Value) (llvm.Value, error) {
|
|||||||
return c.builder.CreateExtractValue(value, expr.Index, ""), nil
|
return c.builder.CreateExtractValue(value, expr.Index, ""), nil
|
||||||
case *ssa.Field:
|
case *ssa.Field:
|
||||||
value := c.getValue(frame, expr.X)
|
value := c.getValue(frame, expr.X)
|
||||||
if s := expr.X.Type().Underlying().(*types.Struct); s.NumFields() > 2 && s.Field(0).Name() == "C union" {
|
|
||||||
// Extract a field from a CGo union.
|
|
||||||
// This could be done directly, but as this is a very infrequent
|
|
||||||
// operation it's much easier to bitcast it through an alloca.
|
|
||||||
resultType := c.getLLVMType(expr.Type())
|
|
||||||
alloca, allocaPtr, allocaSize := c.createTemporaryAlloca(value.Type(), "union.alloca")
|
|
||||||
c.builder.CreateStore(value, alloca)
|
|
||||||
bitcast := c.builder.CreateBitCast(alloca, llvm.PointerType(resultType, 0), "union.bitcast")
|
|
||||||
result := c.builder.CreateLoad(bitcast, "union.result")
|
|
||||||
c.emitLifetimeEnd(allocaPtr, allocaSize)
|
|
||||||
return result, nil
|
|
||||||
}
|
|
||||||
result := c.builder.CreateExtractValue(value, expr.Field, "")
|
result := c.builder.CreateExtractValue(value, expr.Field, "")
|
||||||
return result, nil
|
return result, nil
|
||||||
case *ssa.FieldAddr:
|
case *ssa.FieldAddr:
|
||||||
@@ -1548,20 +1461,12 @@ func (c *Compiler) parseExpr(frame *Frame, expr ssa.Value) (llvm.Value, error) {
|
|||||||
// > pointer of type *T to x. [...] If the evaluation of x would cause a
|
// > pointer of type *T to x. [...] If the evaluation of x would cause a
|
||||||
// > run-time panic, then the evaluation of &x does too.
|
// > run-time panic, then the evaluation of &x does too.
|
||||||
c.emitNilCheck(frame, val, "gep")
|
c.emitNilCheck(frame, val, "gep")
|
||||||
if s := expr.X.Type().(*types.Pointer).Elem().Underlying().(*types.Struct); s.NumFields() > 2 && s.Field(0).Name() == "C union" {
|
// Do a GEP on the pointer to get the field address.
|
||||||
// This is not a regular struct but actually an union.
|
indices := []llvm.Value{
|
||||||
// That simplifies things, as we can just bitcast the pointer to the
|
llvm.ConstInt(c.ctx.Int32Type(), 0, false),
|
||||||
// right type.
|
llvm.ConstInt(c.ctx.Int32Type(), uint64(expr.Field), false),
|
||||||
ptrType := c.getLLVMType(expr.Type())
|
|
||||||
return c.builder.CreateBitCast(val, ptrType, ""), nil
|
|
||||||
} else {
|
|
||||||
// Do a GEP on the pointer to get the field address.
|
|
||||||
indices := []llvm.Value{
|
|
||||||
llvm.ConstInt(c.ctx.Int32Type(), 0, false),
|
|
||||||
llvm.ConstInt(c.ctx.Int32Type(), uint64(expr.Field), false),
|
|
||||||
}
|
|
||||||
return c.builder.CreateInBoundsGEP(val, indices, ""), nil
|
|
||||||
}
|
}
|
||||||
|
return c.builder.CreateInBoundsGEP(val, indices, ""), nil
|
||||||
case *ssa.Function:
|
case *ssa.Function:
|
||||||
panic("function is not an expression")
|
panic("function is not an expression")
|
||||||
case *ssa.Global:
|
case *ssa.Global:
|
||||||
|
|||||||
+1
-1
@@ -130,7 +130,7 @@ func (c *Compiler) emitDefer(frame *Frame, instr *ssa.Defer) {
|
|||||||
// Put this struct in an alloca.
|
// Put this struct in an alloca.
|
||||||
alloca := c.builder.CreateAlloca(deferFrameType, "defer.alloca")
|
alloca := c.builder.CreateAlloca(deferFrameType, "defer.alloca")
|
||||||
c.builder.CreateStore(deferFrame, alloca)
|
c.builder.CreateStore(deferFrame, alloca)
|
||||||
if c.needsStackObjects() {
|
if c.NeedsStackObjects() {
|
||||||
c.trackPointer(alloca)
|
c.trackPointer(alloca)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -98,6 +98,10 @@ func (c *Compiler) LowerFuncValues() {
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
for _, funcValueWithSignatureConstant := range getUses(ptrtoint) {
|
for _, funcValueWithSignatureConstant := range getUses(ptrtoint) {
|
||||||
|
if !funcValueWithSignatureConstant.IsACallInst().IsNil() && funcValueWithSignatureConstant.CalledValue().Name() == "runtime.makeGoroutine" {
|
||||||
|
// makeGoroutine calls are handled seperately
|
||||||
|
continue
|
||||||
|
}
|
||||||
for _, funcValueWithSignatureGlobal := range getUses(funcValueWithSignatureConstant) {
|
for _, funcValueWithSignatureGlobal := range getUses(funcValueWithSignatureConstant) {
|
||||||
for _, use := range getUses(funcValueWithSignatureGlobal) {
|
for _, use := range getUses(funcValueWithSignatureGlobal) {
|
||||||
if ptrtoint.IsAConstantExpr().IsNil() || ptrtoint.Opcode() != llvm.PtrToInt {
|
if ptrtoint.IsAConstantExpr().IsNil() || ptrtoint.Opcode() != llvm.PtrToInt {
|
||||||
@@ -182,7 +186,11 @@ func (c *Compiler) LowerFuncValues() {
|
|||||||
panic("expected a inttoptr")
|
panic("expected a inttoptr")
|
||||||
}
|
}
|
||||||
for _, use := range getUses(inttoptr) {
|
for _, use := range getUses(inttoptr) {
|
||||||
c.addFuncLoweringSwitch(funcID, use, c.emitStartGoroutine, functions)
|
c.addFuncLoweringSwitch(funcID, use, func(funcPtr llvm.Value, params []llvm.Value) llvm.Value {
|
||||||
|
// The function lowering switch code passes in a parent handle value.
|
||||||
|
// Strip the parent handle off here because it is irrelevant to goroutine starts.
|
||||||
|
return c.emitStartGoroutine(funcPtr, params[:len(params)-1])
|
||||||
|
}, functions)
|
||||||
use.EraseFromParentAsInstruction()
|
use.EraseFromParentAsInstruction()
|
||||||
}
|
}
|
||||||
inttoptr.EraseFromParentAsInstruction()
|
inttoptr.EraseFromParentAsInstruction()
|
||||||
|
|||||||
+1
-1
@@ -34,7 +34,7 @@ const (
|
|||||||
func (c *Compiler) funcImplementation() funcValueImplementation {
|
func (c *Compiler) funcImplementation() funcValueImplementation {
|
||||||
// Always pick the switch implementation, as it allows the use of blocking
|
// Always pick the switch implementation, as it allows the use of blocking
|
||||||
// inside a function that is used as a func value.
|
// inside a function that is used as a func value.
|
||||||
switch c.selectScheduler() {
|
switch c.Scheduler() {
|
||||||
case "coroutines":
|
case "coroutines":
|
||||||
return funcValueSwitch
|
return funcValueSwitch
|
||||||
case "tasks":
|
case "tasks":
|
||||||
|
|||||||
-365
@@ -5,27 +5,11 @@ package compiler
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"go/token"
|
"go/token"
|
||||||
"math/big"
|
|
||||||
|
|
||||||
"golang.org/x/tools/go/ssa"
|
"golang.org/x/tools/go/ssa"
|
||||||
"tinygo.org/x/go-llvm"
|
"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
|
// trackExpr inserts pointer tracking intrinsics for the GC if the expression is
|
||||||
// one of the expressions that need this.
|
// one of the expressions that need this.
|
||||||
func (c *Compiler) trackExpr(frame *Frame, expr ssa.Value, value llvm.Value) {
|
func (c *Compiler) trackExpr(frame *Frame, expr ssa.Value, value llvm.Value) {
|
||||||
@@ -120,352 +104,3 @@ func typeHasPointers(t llvm.Type) bool {
|
|||||||
return false
|
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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -108,6 +108,7 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"github.com/tinygo-org/tinygo/compiler/llvmutil"
|
||||||
"tinygo.org/x/go-llvm"
|
"tinygo.org/x/go-llvm"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -126,7 +127,7 @@ type asyncFunc struct {
|
|||||||
// coroutine or the tasks implementation of goroutines, and whether goroutines
|
// coroutine or the tasks implementation of goroutines, and whether goroutines
|
||||||
// are necessary at all.
|
// are necessary at all.
|
||||||
func (c *Compiler) LowerGoroutines() error {
|
func (c *Compiler) LowerGoroutines() error {
|
||||||
switch c.selectScheduler() {
|
switch c.Scheduler() {
|
||||||
case "coroutines":
|
case "coroutines":
|
||||||
return c.lowerCoroutines()
|
return c.lowerCoroutines()
|
||||||
case "tasks":
|
case "tasks":
|
||||||
@@ -212,6 +213,10 @@ func (c *Compiler) lowerCoroutines() error {
|
|||||||
// still exported. Make sure it is optimized away.
|
// still exported. Make sure it is optimized away.
|
||||||
go_scheduler.SetLinkage(llvm.InternalLinkage)
|
go_scheduler.SetLinkage(llvm.InternalLinkage)
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
// Eliminate unnecessary fake coroutines.
|
||||||
|
// This is necessary to prevent infinite recursion in runtime.getFakeCoroutine.
|
||||||
|
c.eliminateFakeCoroutines()
|
||||||
}
|
}
|
||||||
|
|
||||||
// main.main was set to external linkage during IR construction. Set it to
|
// main.main was set to external linkage during IR construction. Set it to
|
||||||
@@ -312,7 +317,7 @@ func (c *Compiler) markAsyncFunctions() (needsScheduler bool, err error) {
|
|||||||
|
|
||||||
// Check whether a scheduler is needed.
|
// Check whether a scheduler is needed.
|
||||||
makeGoroutine := c.mod.NamedFunction("runtime.makeGoroutine")
|
makeGoroutine := c.mod.NamedFunction("runtime.makeGoroutine")
|
||||||
if strings.HasPrefix(c.Triple, "avr") {
|
if strings.HasPrefix(c.Triple(), "avr") {
|
||||||
needsScheduler = false
|
needsScheduler = false
|
||||||
getCoroutine := c.mod.NamedFunction("runtime.getCoroutine")
|
getCoroutine := c.mod.NamedFunction("runtime.getCoroutine")
|
||||||
for _, inst := range getUses(getCoroutine) {
|
for _, inst := range getUses(getCoroutine) {
|
||||||
@@ -534,7 +539,7 @@ func (c *Compiler) markAsyncFunctions() (needsScheduler bool, err error) {
|
|||||||
var retvalAlloca llvm.Value
|
var retvalAlloca llvm.Value
|
||||||
if callee.Type().ElementType().ReturnType().TypeKind() != llvm.VoidTypeKind {
|
if callee.Type().ElementType().ReturnType().TypeKind() != llvm.VoidTypeKind {
|
||||||
// allocate return value buffer
|
// allocate return value buffer
|
||||||
retvalAlloca = c.createInstructionAlloca(callee.Type().ElementType().ReturnType(), inst, "coro.retvalAlloca")
|
retvalAlloca = llvmutil.CreateInstructionAlloca(c.builder, c.mod, callee.Type().ElementType().ReturnType(), inst, "coro.retvalAlloca")
|
||||||
|
|
||||||
// call before function
|
// call before function
|
||||||
c.builder.SetInsertPointBefore(inst)
|
c.builder.SetInsertPointBefore(inst)
|
||||||
@@ -770,7 +775,7 @@ func (c *Compiler) markAsyncFunctions() (needsScheduler bool, err error) {
|
|||||||
size = c.builder.CreateZExt(size, c.uintptrType, "task.size.uintptr")
|
size = c.builder.CreateZExt(size, c.uintptrType, "task.size.uintptr")
|
||||||
}
|
}
|
||||||
data := c.createRuntimeCall("alloc", []llvm.Value{size}, "task.data")
|
data := c.createRuntimeCall("alloc", []llvm.Value{size}, "task.data")
|
||||||
if c.needsStackObjects() {
|
if c.NeedsStackObjects() {
|
||||||
c.trackPointer(data)
|
c.trackPointer(data)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -916,7 +921,7 @@ func (c *Compiler) lowerMakeGoroutineCalls(sched bool) error {
|
|||||||
params = append(params, realCall.Operand(i))
|
params = append(params, realCall.Operand(i))
|
||||||
}
|
}
|
||||||
c.builder.SetInsertPointBefore(realCall)
|
c.builder.SetInsertPointBefore(realCall)
|
||||||
if (!sched) || goroutine.InstructionParent().Parent() == c.mod.NamedFunction("runtime.getFakeCoroutine") {
|
if !sched {
|
||||||
params[len(params)-1] = llvm.Undef(c.i8ptrType)
|
params[len(params)-1] = llvm.Undef(c.i8ptrType)
|
||||||
} else {
|
} else {
|
||||||
params[len(params)-1] = c.createRuntimeCall("getFakeCoroutine", []llvm.Value{}, "") // parent coroutine handle (must not be nil)
|
params[len(params)-1] = c.createRuntimeCall("getFakeCoroutine", []llvm.Value{}, "") // parent coroutine handle (must not be nil)
|
||||||
@@ -933,3 +938,80 @@ func (c *Compiler) lowerMakeGoroutineCalls(sched bool) error {
|
|||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// internalArgumentValue finds the LLVM value inside the function which corresponds to the provided argument of the provided call.
|
||||||
|
func (c *Compiler) internalArgumentValue(call llvm.Value, arg llvm.Value) llvm.Value {
|
||||||
|
n := call.OperandsCount()
|
||||||
|
for i := 0; i < n; i++ {
|
||||||
|
if call.Operand(i) == arg {
|
||||||
|
return call.CalledValue().Param(i)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
panic("no corresponding argument")
|
||||||
|
}
|
||||||
|
|
||||||
|
// specialCoroFuncs are functions in the runtime which accept coroutines as arguments but act as a no-op if these are nil.
|
||||||
|
// Calls to these functions do not require a fake coroutine.
|
||||||
|
var specialCoroFuncs = map[string]bool{
|
||||||
|
"runtime.runqueuePushBack": true,
|
||||||
|
"runtime.activateTask": true,
|
||||||
|
}
|
||||||
|
|
||||||
|
// isCoroNecessary checks if a coroutine pointer value must be non-nil for the program to function.
|
||||||
|
// This returns true if replacing a fake coroutine value with nil will result in equivalent behavior.
|
||||||
|
func (c *Compiler) isCoroNecessary(coro llvm.Value, scanned map[llvm.Value]struct{}) (necessary bool) {
|
||||||
|
// avoid infinite recursion
|
||||||
|
if _, ok := scanned[coro]; ok {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
scanned[coro] = struct{}{}
|
||||||
|
|
||||||
|
for use := coro.FirstUse(); !use.IsNil(); use = use.NextUse() {
|
||||||
|
user := use.User()
|
||||||
|
switch {
|
||||||
|
case !user.IsACallInst().IsNil():
|
||||||
|
switch {
|
||||||
|
case !user.CalledValue().IsConstant():
|
||||||
|
// This is passed into an unknown function, so we do not know what is happening to it.
|
||||||
|
coroDebugPrintln("found unoptimizable dynamic call")
|
||||||
|
return true
|
||||||
|
case specialCoroFuncs[user.CalledValue().Name()]:
|
||||||
|
// Pushing nil to the runqueue is valid and acts as a no-op.
|
||||||
|
// This use does not require a non-nil coroutine.
|
||||||
|
case c.isCoroNecessary(c.internalArgumentValue(user, coro), scanned):
|
||||||
|
// The function we called depends on the coroutine value being non-nil.
|
||||||
|
coroDebugPrintln("call to function depending on non-nil coroutine")
|
||||||
|
return true
|
||||||
|
default:
|
||||||
|
// This call does not depend upon a non-nil coroutine.
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
if coroDebug {
|
||||||
|
fmt.Printf("unoptimizable usage of coroutine in %q: ", user.InstructionParent().Parent().Name())
|
||||||
|
user.Dump()
|
||||||
|
fmt.Println()
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Nothing we found needed this coroutine value.
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// eliminateFakeCoroutines replaces unnecessary calls to runtime.getFakeCoroutine.
|
||||||
|
// This is not considered an optimization, because it is necessary to avoid infinite recursion inside of runtime.getFakeCoroutine.
|
||||||
|
func (c *Compiler) eliminateFakeCoroutines() {
|
||||||
|
coroDebugPrintln("eliminating fake coroutines")
|
||||||
|
for _, v := range getUses(c.mod.NamedFunction("runtime.getFakeCoroutine")) {
|
||||||
|
if !c.isCoroNecessary(v, map[llvm.Value]struct{}{}) {
|
||||||
|
// This use of a fake coroutine is not necessary.
|
||||||
|
coroDebugPrintln("eliminating fake coroutine for", getUses(v)[0].CalledValue().Name())
|
||||||
|
v.ReplaceAllUsesWith(llvm.ConstNull(c.i8ptrType))
|
||||||
|
v.EraseFromParentAsInstruction()
|
||||||
|
} else {
|
||||||
|
// This use of a fake coroutine is necessary.
|
||||||
|
coroDebugPrintln("failed to eliminate fake coroutine for", getUses(v)[0].CalledValue().Name())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -7,10 +7,13 @@ import "tinygo.org/x/go-llvm"
|
|||||||
|
|
||||||
// emitStartGoroutine starts a new goroutine with the provided function pointer
|
// emitStartGoroutine starts a new goroutine with the provided function pointer
|
||||||
// and parameters.
|
// and parameters.
|
||||||
|
// In general, you should pass all regular parameters plus the context parameter.
|
||||||
|
// There is one exception: the task-based scheduler needs to have the function
|
||||||
|
// pointer passed in as a parameter too in addition to the context.
|
||||||
//
|
//
|
||||||
// Because a go statement doesn't return anything, return undef.
|
// Because a go statement doesn't return anything, return undef.
|
||||||
func (c *Compiler) emitStartGoroutine(funcPtr llvm.Value, params []llvm.Value) llvm.Value {
|
func (c *Compiler) emitStartGoroutine(funcPtr llvm.Value, params []llvm.Value) llvm.Value {
|
||||||
switch c.selectScheduler() {
|
switch c.Scheduler() {
|
||||||
case "tasks":
|
case "tasks":
|
||||||
paramBundle := c.emitPointerPack(params)
|
paramBundle := c.emitPointerPack(params)
|
||||||
paramBundle = c.builder.CreatePtrToInt(paramBundle, c.uintptrType, "")
|
paramBundle = c.builder.CreatePtrToInt(paramBundle, c.uintptrType, "")
|
||||||
@@ -24,7 +27,7 @@ func (c *Compiler) emitStartGoroutine(funcPtr llvm.Value, params []llvm.Value) l
|
|||||||
calleeValue := c.builder.CreatePtrToInt(funcPtr, c.uintptrType, "")
|
calleeValue := c.builder.CreatePtrToInt(funcPtr, c.uintptrType, "")
|
||||||
calleeValue = c.createRuntimeCall("makeGoroutine", []llvm.Value{calleeValue}, "")
|
calleeValue = c.createRuntimeCall("makeGoroutine", []llvm.Value{calleeValue}, "")
|
||||||
calleeValue = c.builder.CreateIntToPtr(calleeValue, funcPtr.Type(), "")
|
calleeValue = c.builder.CreateIntToPtr(calleeValue, funcPtr.Type(), "")
|
||||||
c.createCall(calleeValue, params, "")
|
c.createCall(calleeValue, append(params, llvm.ConstPointerNull(c.i8ptrType)), "")
|
||||||
default:
|
default:
|
||||||
panic("unreachable")
|
panic("unreachable")
|
||||||
}
|
}
|
||||||
@@ -74,8 +77,8 @@ func (c *Compiler) createGoroutineStartWrapper(fn llvm.Value) llvm.Value {
|
|||||||
|
|
||||||
// Create the list of params for the call.
|
// Create the list of params for the call.
|
||||||
paramTypes := fn.Type().ElementType().ParamTypes()
|
paramTypes := fn.Type().ElementType().ParamTypes()
|
||||||
params := c.emitPointerUnpack(wrapper.Param(0), paramTypes[:len(paramTypes)-2])
|
params := c.emitPointerUnpack(wrapper.Param(0), paramTypes[:len(paramTypes)-1])
|
||||||
params = append(params, llvm.Undef(c.i8ptrType), llvm.ConstPointerNull(c.i8ptrType))
|
params = append(params, llvm.Undef(c.i8ptrType))
|
||||||
|
|
||||||
// Create the call.
|
// Create the call.
|
||||||
c.builder.CreateCall(fn, params, "")
|
c.builder.CreateCall(fn, params, "")
|
||||||
|
|||||||
@@ -210,10 +210,6 @@ func getTypeCodeName(t types.Type) string {
|
|||||||
return "slice:" + getTypeCodeName(t.Elem())
|
return "slice:" + getTypeCodeName(t.Elem())
|
||||||
case *types.Struct:
|
case *types.Struct:
|
||||||
elems := make([]string, t.NumFields())
|
elems := make([]string, t.NumFields())
|
||||||
if t.NumFields() > 2 && t.Field(0).Name() == "C union" {
|
|
||||||
// TODO: report this as a normal error instead of panicking.
|
|
||||||
panic("cgo unions are not allowed in interfaces")
|
|
||||||
}
|
|
||||||
for i := 0; i < t.NumFields(); i++ {
|
for i := 0; i < t.NumFields(); i++ {
|
||||||
embedded := ""
|
embedded := ""
|
||||||
if t.Field(i).Embedded() {
|
if t.Field(i).Embedded() {
|
||||||
@@ -465,6 +461,9 @@ func (c *Compiler) getInterfaceInvokeWrapper(f *ir.Function) llvm.Value {
|
|||||||
paramTypes := append([]llvm.Type{c.i8ptrType}, fnType.ParamTypes()[len(expandedReceiverType):]...)
|
paramTypes := append([]llvm.Type{c.i8ptrType}, fnType.ParamTypes()[len(expandedReceiverType):]...)
|
||||||
wrapFnType := llvm.FunctionType(fnType.ReturnType(), paramTypes, false)
|
wrapFnType := llvm.FunctionType(fnType.ReturnType(), paramTypes, false)
|
||||||
wrapper = llvm.AddFunction(c.mod, wrapperName, wrapFnType)
|
wrapper = llvm.AddFunction(c.mod, wrapperName, wrapFnType)
|
||||||
|
if f.LLVMFn.LastParam().Name() == "parentHandle" {
|
||||||
|
wrapper.LastParam().SetName("parentHandle")
|
||||||
|
}
|
||||||
c.interfaceInvokeWrappers = append(c.interfaceInvokeWrappers, interfaceInvokeWrapper{
|
c.interfaceInvokeWrappers = append(c.interfaceInvokeWrappers, interfaceInvokeWrapper{
|
||||||
fn: f,
|
fn: f,
|
||||||
wrapper: wrapper,
|
wrapper: wrapper,
|
||||||
@@ -483,7 +482,7 @@ func (c *Compiler) createInterfaceInvokeWrapper(state interfaceInvokeWrapper) {
|
|||||||
wrapper.SetUnnamedAddr(true)
|
wrapper.SetUnnamedAddr(true)
|
||||||
|
|
||||||
// add debug info if needed
|
// add debug info if needed
|
||||||
if c.Debug {
|
if c.Debug() {
|
||||||
pos := c.ir.Program.Fset.Position(fn.Pos())
|
pos := c.ir.Program.Fset.Position(fn.Pos())
|
||||||
difunc := c.attachDebugInfoRaw(fn, wrapper, "$invoke", pos.Filename, pos.Line)
|
difunc := c.attachDebugInfoRaw(fn, wrapper, "$invoke", pos.Filename, pos.Line)
|
||||||
c.builder.SetCurrentDebugLocation(uint(pos.Line), uint(pos.Column), difunc, llvm.Metadata{})
|
c.builder.SetCurrentDebugLocation(uint(pos.Line), uint(pos.Column), difunc, llvm.Metadata{})
|
||||||
|
|||||||
+15
-91
@@ -1,8 +1,7 @@
|
|||||||
package compiler
|
package compiler
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"reflect"
|
"github.com/tinygo-org/tinygo/compiler/llvmutil"
|
||||||
|
|
||||||
"tinygo.org/x/go-llvm"
|
"tinygo.org/x/go-llvm"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -24,22 +23,6 @@ func getUses(value llvm.Value) []llvm.Value {
|
|||||||
return uses
|
return uses
|
||||||
}
|
}
|
||||||
|
|
||||||
// createEntryBlockAlloca creates a new alloca in the entry block, even though
|
|
||||||
// the IR builder is located elsewhere. It assumes that the insert point is
|
|
||||||
// at the end of the current block.
|
|
||||||
func (c *Compiler) createEntryBlockAlloca(t llvm.Type, name string) llvm.Value {
|
|
||||||
currentBlock := c.builder.GetInsertBlock()
|
|
||||||
entryBlock := currentBlock.Parent().EntryBasicBlock()
|
|
||||||
if entryBlock.FirstInstruction().IsNil() {
|
|
||||||
c.builder.SetInsertPointAtEnd(entryBlock)
|
|
||||||
} else {
|
|
||||||
c.builder.SetInsertPointBefore(entryBlock.FirstInstruction())
|
|
||||||
}
|
|
||||||
alloca := c.builder.CreateAlloca(t, name)
|
|
||||||
c.builder.SetInsertPointAtEnd(currentBlock)
|
|
||||||
return alloca
|
|
||||||
}
|
|
||||||
|
|
||||||
// createTemporaryAlloca creates a new alloca in the entry block and adds
|
// createTemporaryAlloca creates a new alloca in the entry block and adds
|
||||||
// lifetime start infromation in the IR signalling that the alloca won't be used
|
// lifetime start infromation in the IR signalling that the alloca won't be used
|
||||||
// before this point.
|
// before this point.
|
||||||
@@ -47,56 +30,26 @@ func (c *Compiler) createEntryBlockAlloca(t llvm.Type, name string) llvm.Value {
|
|||||||
// This is useful for creating temporary allocas for intrinsics. Don't forget to
|
// This is useful for creating temporary allocas for intrinsics. Don't forget to
|
||||||
// end the lifetime using emitLifetimeEnd after you're done with it.
|
// end the lifetime using emitLifetimeEnd after you're done with it.
|
||||||
func (c *Compiler) createTemporaryAlloca(t llvm.Type, name string) (alloca, bitcast, size llvm.Value) {
|
func (c *Compiler) createTemporaryAlloca(t llvm.Type, name string) (alloca, bitcast, size llvm.Value) {
|
||||||
alloca = c.createEntryBlockAlloca(t, name)
|
return llvmutil.CreateTemporaryAlloca(c.builder, c.mod, t, name)
|
||||||
bitcast = c.builder.CreateBitCast(alloca, c.i8ptrType, name+".bitcast")
|
|
||||||
size = llvm.ConstInt(c.ctx.Int64Type(), c.targetData.TypeAllocSize(t), false)
|
|
||||||
c.builder.CreateCall(c.getLifetimeStartFunc(), []llvm.Value{size, bitcast}, "")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// createInstructionAlloca creates an alloca in the entry block, and places lifetime control intrinsics around the instruction
|
|
||||||
func (c *Compiler) createInstructionAlloca(t llvm.Type, inst llvm.Value, name string) llvm.Value {
|
|
||||||
alloca := c.createEntryBlockAlloca(t, name)
|
|
||||||
c.builder.SetInsertPointBefore(inst)
|
|
||||||
bitcast := c.builder.CreateBitCast(alloca, c.i8ptrType, name+".bitcast")
|
|
||||||
size := llvm.ConstInt(c.ctx.Int64Type(), c.targetData.TypeAllocSize(t), false)
|
|
||||||
c.builder.CreateCall(c.getLifetimeStartFunc(), []llvm.Value{size, bitcast}, "")
|
|
||||||
if next := llvm.NextInstruction(inst); !next.IsNil() {
|
|
||||||
c.builder.SetInsertPointBefore(next)
|
|
||||||
} else {
|
|
||||||
c.builder.SetInsertPointAtEnd(inst.InstructionParent())
|
|
||||||
}
|
|
||||||
c.builder.CreateCall(c.getLifetimeEndFunc(), []llvm.Value{size, bitcast}, "")
|
|
||||||
return alloca
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// emitLifetimeEnd signals the end of an (alloca) lifetime by calling the
|
// emitLifetimeEnd signals the end of an (alloca) lifetime by calling the
|
||||||
// llvm.lifetime.end intrinsic. It is commonly used together with
|
// llvm.lifetime.end intrinsic. It is commonly used together with
|
||||||
// createTemporaryAlloca.
|
// createTemporaryAlloca.
|
||||||
func (c *Compiler) emitLifetimeEnd(ptr, size llvm.Value) {
|
func (c *Compiler) emitLifetimeEnd(ptr, size llvm.Value) {
|
||||||
c.builder.CreateCall(c.getLifetimeEndFunc(), []llvm.Value{size, ptr}, "")
|
llvmutil.EmitLifetimeEnd(c.builder, c.mod, ptr, size)
|
||||||
}
|
}
|
||||||
|
|
||||||
// getLifetimeStartFunc returns the llvm.lifetime.start intrinsic and creates it
|
// emitPointerPack packs the list of values into a single pointer value using
|
||||||
// first if it doesn't exist yet.
|
// bitcasts, or else allocates a value on the heap if it cannot be packed in the
|
||||||
func (c *Compiler) getLifetimeStartFunc() llvm.Value {
|
// pointer value directly. It returns the pointer with the packed data.
|
||||||
fn := c.mod.NamedFunction("llvm.lifetime.start.p0i8")
|
func (c *Compiler) emitPointerPack(values []llvm.Value) llvm.Value {
|
||||||
if fn.IsNil() {
|
return llvmutil.EmitPointerPack(c.builder, c.mod, c.Config, values)
|
||||||
fnType := llvm.FunctionType(c.ctx.VoidType(), []llvm.Type{c.ctx.Int64Type(), c.i8ptrType}, false)
|
|
||||||
fn = llvm.AddFunction(c.mod, "llvm.lifetime.start.p0i8", fnType)
|
|
||||||
}
|
|
||||||
return fn
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// getLifetimeEndFunc returns the llvm.lifetime.end intrinsic and creates it
|
// emitPointerUnpack extracts a list of values packed using emitPointerPack.
|
||||||
// first if it doesn't exist yet.
|
func (c *Compiler) emitPointerUnpack(ptr llvm.Value, valueTypes []llvm.Type) []llvm.Value {
|
||||||
func (c *Compiler) getLifetimeEndFunc() llvm.Value {
|
return llvmutil.EmitPointerUnpack(c.builder, c.mod, ptr, valueTypes)
|
||||||
fn := c.mod.NamedFunction("llvm.lifetime.end.p0i8")
|
|
||||||
if fn.IsNil() {
|
|
||||||
fnType := llvm.FunctionType(c.ctx.VoidType(), []llvm.Type{c.ctx.Int64Type(), c.i8ptrType}, false)
|
|
||||||
fn = llvm.AddFunction(c.mod, "llvm.lifetime.end.p0i8", fnType)
|
|
||||||
}
|
|
||||||
return fn
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// splitBasicBlock splits a LLVM basic block into two parts. All instructions
|
// splitBasicBlock splits a LLVM basic block into two parts. All instructions
|
||||||
@@ -175,43 +128,14 @@ func (c *Compiler) splitBasicBlock(afterInst llvm.Value, insertAfter llvm.BasicB
|
|||||||
// contents, and returns the global.
|
// contents, and returns the global.
|
||||||
// Note that it is left with the default linkage etc., you should set
|
// Note that it is left with the default linkage etc., you should set
|
||||||
// linkage/constant/etc properties yourself.
|
// linkage/constant/etc properties yourself.
|
||||||
func (c *Compiler) makeGlobalArray(bufItf interface{}, name string, elementType llvm.Type) llvm.Value {
|
func (c *Compiler) makeGlobalArray(buf []byte, name string, elementType llvm.Type) llvm.Value {
|
||||||
buf := reflect.ValueOf(bufItf)
|
globalType := llvm.ArrayType(elementType, len(buf))
|
||||||
globalType := llvm.ArrayType(elementType, buf.Len())
|
|
||||||
global := llvm.AddGlobal(c.mod, globalType, name)
|
global := llvm.AddGlobal(c.mod, globalType, name)
|
||||||
value := llvm.Undef(globalType)
|
value := llvm.Undef(globalType)
|
||||||
for i := 0; i < buf.Len(); i++ {
|
for i := 0; i < len(buf); i++ {
|
||||||
ch := buf.Index(i).Uint()
|
ch := uint64(buf[i])
|
||||||
value = llvm.ConstInsertValue(value, llvm.ConstInt(elementType, ch, false), []uint32{uint32(i)})
|
value = llvm.ConstInsertValue(value, llvm.ConstInt(elementType, ch, false), []uint32{uint32(i)})
|
||||||
}
|
}
|
||||||
global.SetInitializer(value)
|
global.SetInitializer(value)
|
||||||
return global
|
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
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -0,0 +1,96 @@
|
|||||||
|
// Package llvmutil contains utility functions used across multiple compiler
|
||||||
|
// packages. For example, they may be used by both the compiler pacakge and
|
||||||
|
// transformation packages.
|
||||||
|
//
|
||||||
|
// Normally, utility packages are avoided. However, in this case, the utility
|
||||||
|
// functions are non-trivial and hard to get right. Copying them to multiple
|
||||||
|
// places would be a big risk if only one of them is updated.
|
||||||
|
package llvmutil
|
||||||
|
|
||||||
|
import "tinygo.org/x/go-llvm"
|
||||||
|
|
||||||
|
// CreateEntryBlockAlloca creates a new alloca in the entry block, even though
|
||||||
|
// the IR builder is located elsewhere. It assumes that the insert point is
|
||||||
|
// at the end of the current block.
|
||||||
|
func CreateEntryBlockAlloca(builder llvm.Builder, t llvm.Type, name string) llvm.Value {
|
||||||
|
currentBlock := builder.GetInsertBlock()
|
||||||
|
entryBlock := currentBlock.Parent().EntryBasicBlock()
|
||||||
|
if entryBlock.FirstInstruction().IsNil() {
|
||||||
|
builder.SetInsertPointAtEnd(entryBlock)
|
||||||
|
} else {
|
||||||
|
builder.SetInsertPointBefore(entryBlock.FirstInstruction())
|
||||||
|
}
|
||||||
|
alloca := builder.CreateAlloca(t, name)
|
||||||
|
builder.SetInsertPointAtEnd(currentBlock)
|
||||||
|
return alloca
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateTemporaryAlloca creates a new alloca in the entry block and adds
|
||||||
|
// lifetime start infromation in the IR signalling that the alloca won't be used
|
||||||
|
// before this point.
|
||||||
|
//
|
||||||
|
// This is useful for creating temporary allocas for intrinsics. Don't forget to
|
||||||
|
// end the lifetime using emitLifetimeEnd after you're done with it.
|
||||||
|
func CreateTemporaryAlloca(builder llvm.Builder, mod llvm.Module, t llvm.Type, name string) (alloca, bitcast, size llvm.Value) {
|
||||||
|
ctx := t.Context()
|
||||||
|
targetData := llvm.NewTargetData(mod.DataLayout())
|
||||||
|
i8ptrType := llvm.PointerType(ctx.Int8Type(), 0)
|
||||||
|
alloca = CreateEntryBlockAlloca(builder, t, name)
|
||||||
|
bitcast = builder.CreateBitCast(alloca, i8ptrType, name+".bitcast")
|
||||||
|
size = llvm.ConstInt(ctx.Int64Type(), targetData.TypeAllocSize(t), false)
|
||||||
|
builder.CreateCall(getLifetimeStartFunc(mod), []llvm.Value{size, bitcast}, "")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateInstructionAlloca creates an alloca in the entry block, and places lifetime control intrinsics around the instruction
|
||||||
|
func CreateInstructionAlloca(builder llvm.Builder, mod llvm.Module, t llvm.Type, inst llvm.Value, name string) llvm.Value {
|
||||||
|
ctx := mod.Context()
|
||||||
|
targetData := llvm.NewTargetData(mod.DataLayout())
|
||||||
|
i8ptrType := llvm.PointerType(ctx.Int8Type(), 0)
|
||||||
|
|
||||||
|
alloca := CreateEntryBlockAlloca(builder, t, name)
|
||||||
|
builder.SetInsertPointBefore(inst)
|
||||||
|
bitcast := builder.CreateBitCast(alloca, i8ptrType, name+".bitcast")
|
||||||
|
size := llvm.ConstInt(ctx.Int64Type(), targetData.TypeAllocSize(t), false)
|
||||||
|
builder.CreateCall(getLifetimeStartFunc(mod), []llvm.Value{size, bitcast}, "")
|
||||||
|
if next := llvm.NextInstruction(inst); !next.IsNil() {
|
||||||
|
builder.SetInsertPointBefore(next)
|
||||||
|
} else {
|
||||||
|
builder.SetInsertPointAtEnd(inst.InstructionParent())
|
||||||
|
}
|
||||||
|
builder.CreateCall(getLifetimeEndFunc(mod), []llvm.Value{size, bitcast}, "")
|
||||||
|
return alloca
|
||||||
|
}
|
||||||
|
|
||||||
|
// EmitLifetimeEnd signals the end of an (alloca) lifetime by calling the
|
||||||
|
// llvm.lifetime.end intrinsic. It is commonly used together with
|
||||||
|
// createTemporaryAlloca.
|
||||||
|
func EmitLifetimeEnd(builder llvm.Builder, mod llvm.Module, ptr, size llvm.Value) {
|
||||||
|
builder.CreateCall(getLifetimeEndFunc(mod), []llvm.Value{size, ptr}, "")
|
||||||
|
}
|
||||||
|
|
||||||
|
// getLifetimeStartFunc returns the llvm.lifetime.start intrinsic and creates it
|
||||||
|
// first if it doesn't exist yet.
|
||||||
|
func getLifetimeStartFunc(mod llvm.Module) llvm.Value {
|
||||||
|
fn := mod.NamedFunction("llvm.lifetime.start.p0i8")
|
||||||
|
ctx := mod.Context()
|
||||||
|
i8ptrType := llvm.PointerType(ctx.Int8Type(), 0)
|
||||||
|
if fn.IsNil() {
|
||||||
|
fnType := llvm.FunctionType(ctx.VoidType(), []llvm.Type{ctx.Int64Type(), i8ptrType}, false)
|
||||||
|
fn = llvm.AddFunction(mod, "llvm.lifetime.start.p0i8", fnType)
|
||||||
|
}
|
||||||
|
return fn
|
||||||
|
}
|
||||||
|
|
||||||
|
// getLifetimeEndFunc returns the llvm.lifetime.end intrinsic and creates it
|
||||||
|
// first if it doesn't exist yet.
|
||||||
|
func getLifetimeEndFunc(mod llvm.Module) llvm.Value {
|
||||||
|
fn := mod.NamedFunction("llvm.lifetime.end.p0i8")
|
||||||
|
ctx := mod.Context()
|
||||||
|
i8ptrType := llvm.PointerType(ctx.Int8Type(), 0)
|
||||||
|
if fn.IsNil() {
|
||||||
|
fnType := llvm.FunctionType(ctx.VoidType(), []llvm.Type{ctx.Int64Type(), i8ptrType}, false)
|
||||||
|
fn = llvm.AddFunction(mod, "llvm.lifetime.end.p0i8", fnType)
|
||||||
|
}
|
||||||
|
return fn
|
||||||
|
}
|
||||||
@@ -0,0 +1,141 @@
|
|||||||
|
package llvmutil
|
||||||
|
|
||||||
|
// This file contains utility functions to pack and unpack sets of values. It
|
||||||
|
// can take in a list of values and tries to store it efficiently in the pointer
|
||||||
|
// itself if possible and legal.
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/tinygo-org/tinygo/compileopts"
|
||||||
|
"tinygo.org/x/go-llvm"
|
||||||
|
)
|
||||||
|
|
||||||
|
// EmitPointerPack packs the list of values into a single pointer value using
|
||||||
|
// bitcasts, or else allocates a value on the heap if it cannot be packed in the
|
||||||
|
// pointer value directly. It returns the pointer with the packed data.
|
||||||
|
func EmitPointerPack(builder llvm.Builder, mod llvm.Module, config *compileopts.Config, values []llvm.Value) llvm.Value {
|
||||||
|
ctx := mod.Context()
|
||||||
|
targetData := llvm.NewTargetData(mod.DataLayout())
|
||||||
|
i8ptrType := llvm.PointerType(mod.Context().Int8Type(), 0)
|
||||||
|
uintptrType := ctx.IntType(llvm.NewTargetData(mod.DataLayout()).PointerSize() * 8)
|
||||||
|
|
||||||
|
valueTypes := make([]llvm.Type, len(values))
|
||||||
|
for i, value := range values {
|
||||||
|
valueTypes[i] = value.Type()
|
||||||
|
}
|
||||||
|
packedType := ctx.StructType(valueTypes, false)
|
||||||
|
|
||||||
|
// Allocate memory for the packed data.
|
||||||
|
var packedAlloc, packedHeapAlloc llvm.Value
|
||||||
|
size := targetData.TypeAllocSize(packedType)
|
||||||
|
if size == 0 {
|
||||||
|
return llvm.ConstPointerNull(i8ptrType)
|
||||||
|
} else if len(values) == 1 && values[0].Type().TypeKind() == llvm.PointerTypeKind {
|
||||||
|
return builder.CreateBitCast(values[0], i8ptrType, "pack.ptr")
|
||||||
|
} else if size <= targetData.TypeAllocSize(i8ptrType) {
|
||||||
|
// Packed data fits in a pointer, so store it directly inside the
|
||||||
|
// pointer.
|
||||||
|
if len(values) == 1 && values[0].Type().TypeKind() == llvm.IntegerTypeKind {
|
||||||
|
// Try to keep this cast in SSA form.
|
||||||
|
return builder.CreateIntToPtr(values[0], i8ptrType, "pack.int")
|
||||||
|
}
|
||||||
|
// Because packedType is a struct and we have to cast it to a *i8, store
|
||||||
|
// it in an alloca first for bitcasting (store+bitcast+load).
|
||||||
|
packedAlloc, _, _ = CreateTemporaryAlloca(builder, mod, packedType, "")
|
||||||
|
} else {
|
||||||
|
// Packed data is bigger than a pointer, so allocate it on the heap.
|
||||||
|
sizeValue := llvm.ConstInt(uintptrType, size, false)
|
||||||
|
alloc := mod.NamedFunction("runtime.alloc")
|
||||||
|
packedHeapAlloc = builder.CreateCall(alloc, []llvm.Value{
|
||||||
|
sizeValue,
|
||||||
|
llvm.Undef(i8ptrType), // unused context parameter
|
||||||
|
llvm.ConstPointerNull(i8ptrType), // coroutine handle
|
||||||
|
}, "")
|
||||||
|
if config.NeedsStackObjects() {
|
||||||
|
trackPointer := mod.NamedFunction("runtime.trackPointer")
|
||||||
|
builder.CreateCall(trackPointer, []llvm.Value{
|
||||||
|
packedHeapAlloc,
|
||||||
|
llvm.Undef(i8ptrType), // unused context parameter
|
||||||
|
llvm.ConstPointerNull(i8ptrType), // coroutine handle
|
||||||
|
}, "")
|
||||||
|
}
|
||||||
|
packedAlloc = builder.CreateBitCast(packedHeapAlloc, llvm.PointerType(packedType, 0), "")
|
||||||
|
}
|
||||||
|
// Store all values in the alloca or heap pointer.
|
||||||
|
for i, value := range values {
|
||||||
|
indices := []llvm.Value{
|
||||||
|
llvm.ConstInt(ctx.Int32Type(), 0, false),
|
||||||
|
llvm.ConstInt(ctx.Int32Type(), uint64(i), false),
|
||||||
|
}
|
||||||
|
gep := builder.CreateInBoundsGEP(packedAlloc, indices, "")
|
||||||
|
builder.CreateStore(value, gep)
|
||||||
|
}
|
||||||
|
|
||||||
|
if packedHeapAlloc.IsNil() {
|
||||||
|
// Load value (as *i8) from the alloca.
|
||||||
|
packedAlloc = builder.CreateBitCast(packedAlloc, llvm.PointerType(i8ptrType, 0), "")
|
||||||
|
result := builder.CreateLoad(packedAlloc, "")
|
||||||
|
packedPtr := builder.CreateBitCast(packedAlloc, i8ptrType, "")
|
||||||
|
packedSize := llvm.ConstInt(ctx.Int64Type(), targetData.TypeAllocSize(packedAlloc.Type()), false)
|
||||||
|
EmitLifetimeEnd(builder, mod, packedPtr, packedSize)
|
||||||
|
return result
|
||||||
|
} else {
|
||||||
|
// Get the original heap allocation pointer, which already is an *i8.
|
||||||
|
return packedHeapAlloc
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// EmitPointerUnpack extracts a list of values packed using EmitPointerPack.
|
||||||
|
func EmitPointerUnpack(builder llvm.Builder, mod llvm.Module, ptr llvm.Value, valueTypes []llvm.Type) []llvm.Value {
|
||||||
|
ctx := mod.Context()
|
||||||
|
targetData := llvm.NewTargetData(mod.DataLayout())
|
||||||
|
i8ptrType := llvm.PointerType(mod.Context().Int8Type(), 0)
|
||||||
|
uintptrType := ctx.IntType(llvm.NewTargetData(mod.DataLayout()).PointerSize() * 8)
|
||||||
|
|
||||||
|
packedType := ctx.StructType(valueTypes, false)
|
||||||
|
|
||||||
|
// Get a correctly-typed pointer to the packed data.
|
||||||
|
var packedAlloc, packedRawAlloc llvm.Value
|
||||||
|
size := targetData.TypeAllocSize(packedType)
|
||||||
|
if size == 0 {
|
||||||
|
// No data to unpack.
|
||||||
|
} else if len(valueTypes) == 1 && valueTypes[0].TypeKind() == llvm.PointerTypeKind {
|
||||||
|
// A single pointer is always stored directly.
|
||||||
|
return []llvm.Value{builder.CreateBitCast(ptr, valueTypes[0], "unpack.ptr")}
|
||||||
|
} else if size <= targetData.TypeAllocSize(i8ptrType) {
|
||||||
|
// Packed data stored directly in pointer.
|
||||||
|
if len(valueTypes) == 1 && valueTypes[0].TypeKind() == llvm.IntegerTypeKind {
|
||||||
|
// Keep this cast in SSA form.
|
||||||
|
return []llvm.Value{builder.CreatePtrToInt(ptr, valueTypes[0], "unpack.int")}
|
||||||
|
}
|
||||||
|
// Fallback: load it using an alloca.
|
||||||
|
packedRawAlloc, _, _ = CreateTemporaryAlloca(builder, mod, llvm.PointerType(i8ptrType, 0), "unpack.raw.alloc")
|
||||||
|
packedRawValue := builder.CreateBitCast(ptr, llvm.PointerType(i8ptrType, 0), "unpack.raw.value")
|
||||||
|
builder.CreateStore(packedRawValue, packedRawAlloc)
|
||||||
|
packedAlloc = builder.CreateBitCast(packedRawAlloc, llvm.PointerType(packedType, 0), "unpack.alloc")
|
||||||
|
} else {
|
||||||
|
// Packed data stored on the heap. Bitcast the passed-in pointer to the
|
||||||
|
// correct pointer type.
|
||||||
|
packedAlloc = builder.CreateBitCast(ptr, llvm.PointerType(packedType, 0), "unpack.raw.ptr")
|
||||||
|
}
|
||||||
|
// Load each value from the packed data.
|
||||||
|
values := make([]llvm.Value, len(valueTypes))
|
||||||
|
for i, valueType := range valueTypes {
|
||||||
|
if targetData.TypeAllocSize(valueType) == 0 {
|
||||||
|
// This value has length zero, so there's nothing to load.
|
||||||
|
values[i] = llvm.ConstNull(valueType)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
indices := []llvm.Value{
|
||||||
|
llvm.ConstInt(ctx.Int32Type(), 0, false),
|
||||||
|
llvm.ConstInt(ctx.Int32Type(), uint64(i), false),
|
||||||
|
}
|
||||||
|
gep := builder.CreateInBoundsGEP(packedAlloc, indices, "")
|
||||||
|
values[i] = builder.CreateLoad(gep, "")
|
||||||
|
}
|
||||||
|
if !packedRawAlloc.IsNil() {
|
||||||
|
allocPtr := builder.CreateBitCast(packedRawAlloc, i8ptrType, "")
|
||||||
|
allocSize := llvm.ConstInt(ctx.Int64Type(), targetData.TypeAllocSize(uintptrType), false)
|
||||||
|
EmitLifetimeEnd(builder, mod, allocPtr, allocSize)
|
||||||
|
}
|
||||||
|
return values
|
||||||
|
}
|
||||||
+16
-27
@@ -19,13 +19,16 @@ func (c *Compiler) Optimize(optLevel, sizeLevel int, inlinerThreshold uint) erro
|
|||||||
}
|
}
|
||||||
builder.AddCoroutinePassesToExtensionPoints()
|
builder.AddCoroutinePassesToExtensionPoints()
|
||||||
|
|
||||||
if c.PanicStrategy == "trap" {
|
if c.PanicStrategy() == "trap" {
|
||||||
c.replacePanicsWithTrap() // -panic=trap
|
transform.ReplacePanicsWithTrap(c.mod) // -panic=trap
|
||||||
}
|
}
|
||||||
|
|
||||||
// run a check of all of our code
|
// run a check of all of our code
|
||||||
if c.VerifyIR {
|
if c.VerifyIR() {
|
||||||
c.checkModule()
|
err := c.checkModule()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Run function passes for each function.
|
// Run function passes for each function.
|
||||||
@@ -52,7 +55,7 @@ func (c *Compiler) Optimize(optLevel, sizeLevel int, inlinerThreshold uint) erro
|
|||||||
transform.OptimizeMaps(c.mod)
|
transform.OptimizeMaps(c.mod)
|
||||||
transform.OptimizeStringToBytes(c.mod)
|
transform.OptimizeStringToBytes(c.mod)
|
||||||
transform.OptimizeAllocs(c.mod)
|
transform.OptimizeAllocs(c.mod)
|
||||||
c.LowerInterfaces()
|
transform.LowerInterfaces(c.mod)
|
||||||
c.LowerFuncValues()
|
c.LowerFuncValues()
|
||||||
|
|
||||||
// After interfaces are lowered, there are many more opportunities for
|
// After interfaces are lowered, there are many more opportunities for
|
||||||
@@ -86,13 +89,18 @@ func (c *Compiler) Optimize(optLevel, sizeLevel int, inlinerThreshold uint) erro
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Must be run at any optimization level.
|
// Must be run at any optimization level.
|
||||||
c.LowerInterfaces()
|
transform.LowerInterfaces(c.mod)
|
||||||
c.LowerFuncValues()
|
c.LowerFuncValues()
|
||||||
err := c.LowerGoroutines()
|
err := c.LowerGoroutines()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if c.VerifyIR() {
|
||||||
|
if err := c.checkModule(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
if err := c.Verify(); err != nil {
|
if err := c.Verify(); err != nil {
|
||||||
return errors.New("optimizations caused a verification failure")
|
return errors.New("optimizations caused a verification failure")
|
||||||
}
|
}
|
||||||
@@ -129,8 +137,8 @@ func (c *Compiler) Optimize(optLevel, sizeLevel int, inlinerThreshold uint) erro
|
|||||||
builder.Populate(modPasses)
|
builder.Populate(modPasses)
|
||||||
modPasses.Run(c.mod)
|
modPasses.Run(c.mod)
|
||||||
|
|
||||||
hasGCPass := c.addGlobalsBitmap()
|
hasGCPass := transform.AddGlobalsBitmap(c.mod)
|
||||||
hasGCPass = c.makeGCStackSlots() || hasGCPass
|
hasGCPass = transform.MakeGCStackSlots(c.mod) || hasGCPass
|
||||||
if hasGCPass {
|
if hasGCPass {
|
||||||
if err := c.Verify(); err != nil {
|
if err := c.Verify(); err != nil {
|
||||||
return errors.New("GC pass caused a verification failure")
|
return errors.New("GC pass caused a verification failure")
|
||||||
@@ -139,22 +147,3 @@ func (c *Compiler) Optimize(optLevel, sizeLevel int, inlinerThreshold uint) erro
|
|||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Replace panic calls with calls to llvm.trap, to reduce code size. This is the
|
|
||||||
// -panic=trap intrinsic.
|
|
||||||
func (c *Compiler) replacePanicsWithTrap() {
|
|
||||||
trap := c.mod.NamedFunction("llvm.trap")
|
|
||||||
for _, name := range []string{"runtime._panic", "runtime.runtimePanic"} {
|
|
||||||
fn := c.mod.NamedFunction(name)
|
|
||||||
if fn.IsNil() {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
for _, use := range getUses(fn) {
|
|
||||||
if use.IsACallInst().IsNil() || use.CalledValue() != fn {
|
|
||||||
panic("expected use of a panic function to be a call")
|
|
||||||
}
|
|
||||||
c.builder.SetInsertPointBefore(use)
|
|
||||||
c.builder.CreateCall(trap, nil, "")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
+6
-29
@@ -63,12 +63,6 @@ func (s *StdSizes) Alignof(T types.Type) int64 {
|
|||||||
|
|
||||||
func (s *StdSizes) Offsetsof(fields []*types.Var) []int64 {
|
func (s *StdSizes) Offsetsof(fields []*types.Var) []int64 {
|
||||||
offsets := make([]int64, len(fields))
|
offsets := make([]int64, len(fields))
|
||||||
if len(fields) > 1 && fields[0].Name() == "C union" {
|
|
||||||
// This struct contains the magic "C union" field which indicates that
|
|
||||||
// this is actually a union from CGo.
|
|
||||||
// All fields in the union start at 0 so return that.
|
|
||||||
return offsets // all fields are still set to 0
|
|
||||||
}
|
|
||||||
var o int64
|
var o int64
|
||||||
for i, f := range fields {
|
for i, f := range fields {
|
||||||
a := s.Alignof(f.Type())
|
a := s.Alignof(f.Type())
|
||||||
@@ -143,29 +137,12 @@ func (s *StdSizes) Sizeof(T types.Type) int64 {
|
|||||||
maxAlign = al
|
maxAlign = al
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if fields[0].Name() == "C union" {
|
// Pick the size that fits this struct and add some alignment. Some
|
||||||
// Magic field that indicates this is a CGo union and not a struct.
|
// structs have some extra padding at the end which should also be taken
|
||||||
// The size is the biggest element, aligned to the element with the
|
// care of:
|
||||||
// biggest alignment. This is not necessarily the same, for example
|
// struct { int32 n; byte b }
|
||||||
// in the following union:
|
offsets := s.Offsetsof(fields)
|
||||||
// union { int32_t l; int16_t s[3] }
|
return align(offsets[n-1]+s.Sizeof(fields[n-1].Type()), maxAlign)
|
||||||
maxSize := int64(0)
|
|
||||||
for _, field := range fields[1:] {
|
|
||||||
si := s.Sizeof(field.Type())
|
|
||||||
if si > maxSize {
|
|
||||||
maxSize = si
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return align(maxSize, maxAlign)
|
|
||||||
} else {
|
|
||||||
// This is a regular struct.
|
|
||||||
// Pick the size that fits this struct and add some alignment. Some
|
|
||||||
// structs have some extra padding at the end which should also be
|
|
||||||
// taken care of:
|
|
||||||
// struct { int32 n; byte b }
|
|
||||||
offsets := s.Offsetsof(fields)
|
|
||||||
return align(offsets[n-1]+s.Sizeof(fields[n-1].Type()), maxAlign)
|
|
||||||
}
|
|
||||||
case *types.Interface:
|
case *types.Interface:
|
||||||
return s.PtrSize * 2
|
return s.PtrSize * 2
|
||||||
case *types.Pointer:
|
case *types.Pointer:
|
||||||
|
|||||||
+8
-8
@@ -16,8 +16,8 @@ func (c *Compiler) emitSyscall(frame *Frame, call *ssa.CallCommon) (llvm.Value,
|
|||||||
num := c.getValue(frame, call.Args[0])
|
num := c.getValue(frame, call.Args[0])
|
||||||
var syscallResult llvm.Value
|
var syscallResult llvm.Value
|
||||||
switch {
|
switch {
|
||||||
case c.GOARCH == "amd64":
|
case c.GOARCH() == "amd64":
|
||||||
if c.GOOS == "darwin" {
|
if c.GOOS() == "darwin" {
|
||||||
// Darwin adds this magic number to system call numbers:
|
// Darwin adds this magic number to system call numbers:
|
||||||
//
|
//
|
||||||
// > Syscall classes for 64-bit system call entry.
|
// > Syscall classes for 64-bit system call entry.
|
||||||
@@ -58,7 +58,7 @@ func (c *Compiler) emitSyscall(frame *Frame, call *ssa.CallCommon) (llvm.Value,
|
|||||||
fnType := llvm.FunctionType(c.uintptrType, argTypes, false)
|
fnType := llvm.FunctionType(c.uintptrType, argTypes, false)
|
||||||
target := llvm.InlineAsm(fnType, "syscall", constraints, true, false, llvm.InlineAsmDialectIntel)
|
target := llvm.InlineAsm(fnType, "syscall", constraints, true, false, llvm.InlineAsmDialectIntel)
|
||||||
syscallResult = c.builder.CreateCall(target, args, "")
|
syscallResult = c.builder.CreateCall(target, args, "")
|
||||||
case c.GOARCH == "386" && c.GOOS == "linux":
|
case c.GOARCH() == "386" && c.GOOS() == "linux":
|
||||||
// Sources:
|
// Sources:
|
||||||
// syscall(2) man page
|
// syscall(2) man page
|
||||||
// https://stackoverflow.com/a/2538212
|
// https://stackoverflow.com/a/2538212
|
||||||
@@ -84,7 +84,7 @@ func (c *Compiler) emitSyscall(frame *Frame, call *ssa.CallCommon) (llvm.Value,
|
|||||||
fnType := llvm.FunctionType(c.uintptrType, argTypes, false)
|
fnType := llvm.FunctionType(c.uintptrType, argTypes, false)
|
||||||
target := llvm.InlineAsm(fnType, "int 0x80", constraints, true, false, llvm.InlineAsmDialectIntel)
|
target := llvm.InlineAsm(fnType, "int 0x80", constraints, true, false, llvm.InlineAsmDialectIntel)
|
||||||
syscallResult = c.builder.CreateCall(target, args, "")
|
syscallResult = c.builder.CreateCall(target, args, "")
|
||||||
case c.GOARCH == "arm" && c.GOOS == "linux":
|
case c.GOARCH() == "arm" && c.GOOS() == "linux":
|
||||||
// Implement the EABI system call convention for Linux.
|
// Implement the EABI system call convention for Linux.
|
||||||
// Source: syscall(2) man page.
|
// Source: syscall(2) man page.
|
||||||
args := []llvm.Value{}
|
args := []llvm.Value{}
|
||||||
@@ -116,7 +116,7 @@ func (c *Compiler) emitSyscall(frame *Frame, call *ssa.CallCommon) (llvm.Value,
|
|||||||
fnType := llvm.FunctionType(c.uintptrType, argTypes, false)
|
fnType := llvm.FunctionType(c.uintptrType, argTypes, false)
|
||||||
target := llvm.InlineAsm(fnType, "svc #0", constraints, true, false, 0)
|
target := llvm.InlineAsm(fnType, "svc #0", constraints, true, false, 0)
|
||||||
syscallResult = c.builder.CreateCall(target, args, "")
|
syscallResult = c.builder.CreateCall(target, args, "")
|
||||||
case c.GOARCH == "arm64" && c.GOOS == "linux":
|
case c.GOARCH() == "arm64" && c.GOOS() == "linux":
|
||||||
// Source: syscall(2) man page.
|
// Source: syscall(2) man page.
|
||||||
args := []llvm.Value{}
|
args := []llvm.Value{}
|
||||||
argTypes := []llvm.Type{}
|
argTypes := []llvm.Type{}
|
||||||
@@ -149,9 +149,9 @@ func (c *Compiler) emitSyscall(frame *Frame, call *ssa.CallCommon) (llvm.Value,
|
|||||||
target := llvm.InlineAsm(fnType, "svc #0", constraints, true, false, 0)
|
target := llvm.InlineAsm(fnType, "svc #0", constraints, true, false, 0)
|
||||||
syscallResult = c.builder.CreateCall(target, args, "")
|
syscallResult = c.builder.CreateCall(target, args, "")
|
||||||
default:
|
default:
|
||||||
return llvm.Value{}, c.makeError(call.Pos(), "unknown GOOS/GOARCH for syscall: "+c.GOOS+"/"+c.GOARCH)
|
return llvm.Value{}, c.makeError(call.Pos(), "unknown GOOS/GOARCH for syscall: "+c.GOOS()+"/"+c.GOARCH())
|
||||||
}
|
}
|
||||||
switch c.GOOS {
|
switch c.GOOS() {
|
||||||
case "linux":
|
case "linux":
|
||||||
// Return values: r0, r1 uintptr, err Errno
|
// Return values: r0, r1 uintptr, err Errno
|
||||||
// Pseudocode:
|
// Pseudocode:
|
||||||
@@ -187,6 +187,6 @@ func (c *Compiler) emitSyscall(frame *Frame, call *ssa.CallCommon) (llvm.Value,
|
|||||||
retval = c.builder.CreateInsertValue(retval, errResult, 2, "")
|
retval = c.builder.CreateInsertValue(retval, errResult, 2, "")
|
||||||
return retval, nil
|
return retval, nil
|
||||||
default:
|
default:
|
||||||
return llvm.Value{}, c.makeError(call.Pos(), "unknown GOOS/GOARCH for syscall: "+c.GOOS+"/"+c.GOARCH)
|
return llvm.Value{}, c.makeError(call.Pos(), "unknown GOOS/GOARCH for syscall: "+c.GOOS()+"/"+c.GOARCH())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,120 +0,0 @@
|
|||||||
package compiler
|
|
||||||
|
|
||||||
// This file contains utility functions to pack and unpack sets of values. It
|
|
||||||
// can take in a list of values and tries to store it efficiently in the pointer
|
|
||||||
// itself if possible and legal.
|
|
||||||
|
|
||||||
import (
|
|
||||||
"tinygo.org/x/go-llvm"
|
|
||||||
)
|
|
||||||
|
|
||||||
// emitPointerPack packs the list of values into a single pointer value using
|
|
||||||
// bitcasts, or else allocates a value on the heap if it cannot be packed in the
|
|
||||||
// pointer value directly. It returns the pointer with the packed data.
|
|
||||||
func (c *Compiler) emitPointerPack(values []llvm.Value) llvm.Value {
|
|
||||||
valueTypes := make([]llvm.Type, len(values))
|
|
||||||
for i, value := range values {
|
|
||||||
valueTypes[i] = value.Type()
|
|
||||||
}
|
|
||||||
packedType := c.ctx.StructType(valueTypes, false)
|
|
||||||
|
|
||||||
// Allocate memory for the packed data.
|
|
||||||
var packedAlloc, packedHeapAlloc llvm.Value
|
|
||||||
size := c.targetData.TypeAllocSize(packedType)
|
|
||||||
if size == 0 {
|
|
||||||
return llvm.ConstPointerNull(c.i8ptrType)
|
|
||||||
} else if len(values) == 1 && values[0].Type().TypeKind() == llvm.PointerTypeKind {
|
|
||||||
return c.builder.CreateBitCast(values[0], c.i8ptrType, "pack.ptr")
|
|
||||||
} else if size <= c.targetData.TypeAllocSize(c.i8ptrType) {
|
|
||||||
// Packed data fits in a pointer, so store it directly inside the
|
|
||||||
// pointer.
|
|
||||||
if len(values) == 1 && values[0].Type().TypeKind() == llvm.IntegerTypeKind {
|
|
||||||
// Try to keep this cast in SSA form.
|
|
||||||
return c.builder.CreateIntToPtr(values[0], c.i8ptrType, "pack.int")
|
|
||||||
}
|
|
||||||
// Because packedType is a struct and we have to cast it to a *i8, store
|
|
||||||
// it in an alloca first for bitcasting (store+bitcast+load).
|
|
||||||
packedAlloc, _, _ = c.createTemporaryAlloca(packedType, "")
|
|
||||||
} else {
|
|
||||||
// Packed data is bigger than a pointer, so allocate it on the heap.
|
|
||||||
sizeValue := llvm.ConstInt(c.uintptrType, size, false)
|
|
||||||
packedHeapAlloc = c.createRuntimeCall("alloc", []llvm.Value{sizeValue}, "")
|
|
||||||
if c.needsStackObjects() {
|
|
||||||
c.trackPointer(packedHeapAlloc)
|
|
||||||
}
|
|
||||||
packedAlloc = c.builder.CreateBitCast(packedHeapAlloc, llvm.PointerType(packedType, 0), "")
|
|
||||||
}
|
|
||||||
// Store all values in the alloca or heap pointer.
|
|
||||||
for i, value := range values {
|
|
||||||
indices := []llvm.Value{
|
|
||||||
llvm.ConstInt(c.ctx.Int32Type(), 0, false),
|
|
||||||
llvm.ConstInt(c.ctx.Int32Type(), uint64(i), false),
|
|
||||||
}
|
|
||||||
gep := c.builder.CreateInBoundsGEP(packedAlloc, indices, "")
|
|
||||||
c.builder.CreateStore(value, gep)
|
|
||||||
}
|
|
||||||
|
|
||||||
if packedHeapAlloc.IsNil() {
|
|
||||||
// Load value (as *i8) from the alloca.
|
|
||||||
packedAlloc = c.builder.CreateBitCast(packedAlloc, llvm.PointerType(c.i8ptrType, 0), "")
|
|
||||||
result := c.builder.CreateLoad(packedAlloc, "")
|
|
||||||
packedPtr := c.builder.CreateBitCast(packedAlloc, c.i8ptrType, "")
|
|
||||||
packedSize := llvm.ConstInt(c.ctx.Int64Type(), c.targetData.TypeAllocSize(packedAlloc.Type()), false)
|
|
||||||
c.emitLifetimeEnd(packedPtr, packedSize)
|
|
||||||
return result
|
|
||||||
} else {
|
|
||||||
// Get the original heap allocation pointer, which already is an *i8.
|
|
||||||
return packedHeapAlloc
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// emitPointerUnpack extracts a list of values packed using emitPointerPack.
|
|
||||||
func (c *Compiler) emitPointerUnpack(ptr llvm.Value, valueTypes []llvm.Type) []llvm.Value {
|
|
||||||
packedType := c.ctx.StructType(valueTypes, false)
|
|
||||||
|
|
||||||
// Get a correctly-typed pointer to the packed data.
|
|
||||||
var packedAlloc, packedRawAlloc llvm.Value
|
|
||||||
size := c.targetData.TypeAllocSize(packedType)
|
|
||||||
if size == 0 {
|
|
||||||
// No data to unpack.
|
|
||||||
} else if len(valueTypes) == 1 && valueTypes[0].TypeKind() == llvm.PointerTypeKind {
|
|
||||||
// A single pointer is always stored directly.
|
|
||||||
return []llvm.Value{c.builder.CreateBitCast(ptr, valueTypes[0], "unpack.ptr")}
|
|
||||||
} else if size <= c.targetData.TypeAllocSize(c.i8ptrType) {
|
|
||||||
// Packed data stored directly in pointer.
|
|
||||||
if len(valueTypes) == 1 && valueTypes[0].TypeKind() == llvm.IntegerTypeKind {
|
|
||||||
// Keep this cast in SSA form.
|
|
||||||
return []llvm.Value{c.builder.CreatePtrToInt(ptr, valueTypes[0], "unpack.int")}
|
|
||||||
}
|
|
||||||
// Fallback: load it using an alloca.
|
|
||||||
packedRawAlloc, _, _ = c.createTemporaryAlloca(llvm.PointerType(c.i8ptrType, 0), "unpack.raw.alloc")
|
|
||||||
packedRawValue := c.builder.CreateBitCast(ptr, llvm.PointerType(c.i8ptrType, 0), "unpack.raw.value")
|
|
||||||
c.builder.CreateStore(packedRawValue, packedRawAlloc)
|
|
||||||
packedAlloc = c.builder.CreateBitCast(packedRawAlloc, llvm.PointerType(packedType, 0), "unpack.alloc")
|
|
||||||
} else {
|
|
||||||
// Packed data stored on the heap. Bitcast the passed-in pointer to the
|
|
||||||
// correct pointer type.
|
|
||||||
packedAlloc = c.builder.CreateBitCast(ptr, llvm.PointerType(packedType, 0), "unpack.raw.ptr")
|
|
||||||
}
|
|
||||||
// Load each value from the packed data.
|
|
||||||
values := make([]llvm.Value, len(valueTypes))
|
|
||||||
for i, valueType := range valueTypes {
|
|
||||||
if c.targetData.TypeAllocSize(valueType) == 0 {
|
|
||||||
// This value has length zero, so there's nothing to load.
|
|
||||||
values[i] = llvm.ConstNull(valueType)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
indices := []llvm.Value{
|
|
||||||
llvm.ConstInt(c.ctx.Int32Type(), 0, false),
|
|
||||||
llvm.ConstInt(c.ctx.Int32Type(), uint64(i), false),
|
|
||||||
}
|
|
||||||
gep := c.builder.CreateInBoundsGEP(packedAlloc, indices, "")
|
|
||||||
values[i] = c.builder.CreateLoad(gep, "")
|
|
||||||
}
|
|
||||||
if !packedRawAlloc.IsNil() {
|
|
||||||
allocPtr := c.builder.CreateBitCast(packedRawAlloc, c.i8ptrType, "")
|
|
||||||
allocSize := llvm.ConstInt(c.ctx.Int64Type(), c.targetData.TypeAllocSize(c.uintptrType), false)
|
|
||||||
c.emitLifetimeEnd(allocPtr, allocSize)
|
|
||||||
}
|
|
||||||
return values
|
|
||||||
}
|
|
||||||
@@ -5,9 +5,10 @@ go 1.11
|
|||||||
require (
|
require (
|
||||||
github.com/blakesmith/ar v0.0.0-20150311145944-8bd4349a67f2
|
github.com/blakesmith/ar v0.0.0-20150311145944-8bd4349a67f2
|
||||||
github.com/creack/goselect v0.1.0 // indirect
|
github.com/creack/goselect v0.1.0 // indirect
|
||||||
|
github.com/google/shlex v0.0.0-20181106134648-c34317bd91bf
|
||||||
github.com/marcinbor85/gohex v0.0.0-20180128172054-7a43cd876e46
|
github.com/marcinbor85/gohex v0.0.0-20180128172054-7a43cd876e46
|
||||||
go.bug.st/serial.v1 v0.0.0-20180827123349-5f7892a7bb45
|
go.bug.st/serial.v1 v0.0.0-20180827123349-5f7892a7bb45
|
||||||
golang.org/x/sys v0.0.0-20191010194322-b09406accb47 // indirect
|
golang.org/x/sys v0.0.0-20191010194322-b09406accb47 // indirect
|
||||||
golang.org/x/tools v0.0.0-20190227180812-8dcc6e70cdef
|
golang.org/x/tools v0.0.0-20190227180812-8dcc6e70cdef
|
||||||
tinygo.org/x/go-llvm v0.0.0-20190818154551-95bc4ffe1add
|
tinygo.org/x/go-llvm v0.0.0-20191124211856-b2db3df3f257
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -3,6 +3,8 @@ github.com/blakesmith/ar v0.0.0-20150311145944-8bd4349a67f2/go.mod h1:PkYb9DJNAw
|
|||||||
github.com/creack/goselect v0.1.0 h1:4QiXIhcpSQF50XGaBsFzesjwX/1qOY5bOveQPmN9CXY=
|
github.com/creack/goselect v0.1.0 h1:4QiXIhcpSQF50XGaBsFzesjwX/1qOY5bOveQPmN9CXY=
|
||||||
github.com/creack/goselect v0.1.0/go.mod h1:gHrIcH/9UZDn2qgeTUeW5K9eZsVYCH6/60J/FHysWyE=
|
github.com/creack/goselect v0.1.0/go.mod h1:gHrIcH/9UZDn2qgeTUeW5K9eZsVYCH6/60J/FHysWyE=
|
||||||
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||||
|
github.com/google/shlex v0.0.0-20181106134648-c34317bd91bf h1:7+FW5aGwISbqUtkfmIpZJGRgNFg2ioYPvFaUxdqpDsg=
|
||||||
|
github.com/google/shlex v0.0.0-20181106134648-c34317bd91bf/go.mod h1:RpwtwJQFrIEPstU94h88MWPXP2ektJZ8cZ0YntAmXiE=
|
||||||
github.com/marcinbor85/gohex v0.0.0-20180128172054-7a43cd876e46 h1:wXG2bA8fO7Vv7lLk2PihFMTqmbT173Tje39oKzQ50Mo=
|
github.com/marcinbor85/gohex v0.0.0-20180128172054-7a43cd876e46 h1:wXG2bA8fO7Vv7lLk2PihFMTqmbT173Tje39oKzQ50Mo=
|
||||||
github.com/marcinbor85/gohex v0.0.0-20180128172054-7a43cd876e46/go.mod h1:Pb6XcsXyropB9LNHhnqaknG/vEwYztLkQzVCHv8sQ3M=
|
github.com/marcinbor85/gohex v0.0.0-20180128172054-7a43cd876e46/go.mod h1:Pb6XcsXyropB9LNHhnqaknG/vEwYztLkQzVCHv8sQ3M=
|
||||||
go.bug.st/serial.v1 v0.0.0-20180827123349-5f7892a7bb45 h1:mACY1anK6HNCZtm/DK2Rf2ZPHggVqeB0+7rY9Gl6wyI=
|
go.bug.st/serial.v1 v0.0.0-20180827123349-5f7892a7bb45 h1:mACY1anK6HNCZtm/DK2Rf2ZPHggVqeB0+7rY9Gl6wyI=
|
||||||
@@ -20,3 +22,11 @@ tinygo.org/x/go-llvm v0.0.0-20190224120431-7707ae5d1261 h1:rJS2Hga39YAnm7DE4qrPm
|
|||||||
tinygo.org/x/go-llvm v0.0.0-20190224120431-7707ae5d1261/go.mod h1:fv1F0BSNpxMfCL0zF3M4OPFbgYHnhtB6ST0HvUtu/LE=
|
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 h1:dFjMH1sLhYADg8UQm7DB56B7e+TfvAmWmEZLhyv3r/w=
|
||||||
tinygo.org/x/go-llvm v0.0.0-20190818154551-95bc4ffe1add/go.mod h1:fv1F0BSNpxMfCL0zF3M4OPFbgYHnhtB6ST0HvUtu/LE=
|
tinygo.org/x/go-llvm v0.0.0-20190818154551-95bc4ffe1add/go.mod h1:fv1F0BSNpxMfCL0zF3M4OPFbgYHnhtB6ST0HvUtu/LE=
|
||||||
|
tinygo.org/x/go-llvm v0.0.0-20191103182207-90b6e4bdc0b9 h1:d6rAX39a3C0pKrY5HcojEGyN8w9ocU0v7X28lC/TRKU=
|
||||||
|
tinygo.org/x/go-llvm v0.0.0-20191103182207-90b6e4bdc0b9/go.mod h1:fv1F0BSNpxMfCL0zF3M4OPFbgYHnhtB6ST0HvUtu/LE=
|
||||||
|
tinygo.org/x/go-llvm v0.0.0-20191103200204-37e93e3f04e2 h1:Q5Hv3e5cLMGkiYwYgZL1Zrv6nb/EY+DJpRWrdO6ws6o=
|
||||||
|
tinygo.org/x/go-llvm v0.0.0-20191103200204-37e93e3f04e2/go.mod h1:fv1F0BSNpxMfCL0zF3M4OPFbgYHnhtB6ST0HvUtu/LE=
|
||||||
|
tinygo.org/x/go-llvm v0.0.0-20191113125529-bad6d01809e8 h1:9Bfvso+tTVQg16UzOA614NaYA4x8vsRBNtd3eBrXwp0=
|
||||||
|
tinygo.org/x/go-llvm v0.0.0-20191113125529-bad6d01809e8/go.mod h1:fv1F0BSNpxMfCL0zF3M4OPFbgYHnhtB6ST0HvUtu/LE=
|
||||||
|
tinygo.org/x/go-llvm v0.0.0-20191124211856-b2db3df3f257 h1:o8VDylrMN7gWemBMu8rEyuogKPhcLTdx5KrUAp9macc=
|
||||||
|
tinygo.org/x/go-llvm v0.0.0-20191124211856-b2db3df3f257/go.mod h1:fv1F0BSNpxMfCL0zF3M4OPFbgYHnhtB6ST0HvUtu/LE=
|
||||||
|
|||||||
+79
-2
@@ -3,15 +3,92 @@ package interp
|
|||||||
// This file provides useful types for errors encountered during IR evaluation.
|
// This file provides useful types for errors encountered during IR evaluation.
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"errors"
|
||||||
|
"go/scanner"
|
||||||
|
"go/token"
|
||||||
|
"path/filepath"
|
||||||
|
|
||||||
"tinygo.org/x/go-llvm"
|
"tinygo.org/x/go-llvm"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// errUnreachable is returned when an unreachable instruction is executed. This
|
||||||
|
// error should not be visible outside of the interp package.
|
||||||
|
var errUnreachable = errors.New("interp: unreachable executed")
|
||||||
|
|
||||||
|
// Unsupported is the specific error that is returned when an unsupported
|
||||||
|
// instruction is hit while trying to interpret all initializers.
|
||||||
type Unsupported struct {
|
type Unsupported struct {
|
||||||
Inst llvm.Value
|
ImportPath string
|
||||||
|
Inst llvm.Value
|
||||||
|
Pos token.Position
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e Unsupported) Error() string {
|
func (e Unsupported) Error() string {
|
||||||
// TODO: how to return the actual instruction string?
|
// TODO: how to return the actual instruction string?
|
||||||
// It looks like LLVM provides no function for that...
|
// It looks like LLVM provides no function for that...
|
||||||
return "interp: unsupported instruction"
|
return scanner.Error{
|
||||||
|
Pos: e.Pos,
|
||||||
|
Msg: "interp: unsupported instruction",
|
||||||
|
}.Error()
|
||||||
|
}
|
||||||
|
|
||||||
|
// unsupportedInstructionError returns a new "unsupported instruction" error for
|
||||||
|
// the given instruction. It includes source location information, when
|
||||||
|
// available.
|
||||||
|
func (e *evalPackage) unsupportedInstructionError(inst llvm.Value) *Unsupported {
|
||||||
|
return &Unsupported{
|
||||||
|
ImportPath: e.packagePath,
|
||||||
|
Inst: inst,
|
||||||
|
Pos: getPosition(inst),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Error encapsulates compile-time interpretation errors with an associated
|
||||||
|
// import path. The errors may not have a precise location attached.
|
||||||
|
type Error struct {
|
||||||
|
ImportPath string
|
||||||
|
Errs []scanner.Error
|
||||||
|
}
|
||||||
|
|
||||||
|
// Error returns the string of the first error in the list of errors.
|
||||||
|
func (e Error) Error() string {
|
||||||
|
return e.Errs[0].Error()
|
||||||
|
}
|
||||||
|
|
||||||
|
// errorAt returns an error value for the currently interpreted package at the
|
||||||
|
// location of the instruction. The location information may not be complete as
|
||||||
|
// it depends on debug information in the IR.
|
||||||
|
func (e *evalPackage) errorAt(inst llvm.Value, msg string) Error {
|
||||||
|
return Error{
|
||||||
|
ImportPath: e.packagePath,
|
||||||
|
Errs: []scanner.Error{errorAt(inst, msg)},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// errorAt returns an error value at the location of the instruction.
|
||||||
|
// The location information may not be complete as it depends on debug
|
||||||
|
// information in the IR.
|
||||||
|
func errorAt(inst llvm.Value, msg string) scanner.Error {
|
||||||
|
return scanner.Error{
|
||||||
|
Pos: getPosition(inst),
|
||||||
|
Msg: msg,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// getPosition returns the position information for the given instruction, as
|
||||||
|
// far as it is available.
|
||||||
|
func getPosition(inst llvm.Value) token.Position {
|
||||||
|
if inst.IsAInstruction().IsNil() {
|
||||||
|
return token.Position{}
|
||||||
|
}
|
||||||
|
loc := inst.InstructionDebugLoc()
|
||||||
|
if loc.IsNil() {
|
||||||
|
return token.Position{}
|
||||||
|
}
|
||||||
|
file := loc.LocationScope().ScopeFile()
|
||||||
|
return token.Position{
|
||||||
|
Filename: filepath.Join(file.FileDirectory(), file.FileFilename()),
|
||||||
|
Line: int(loc.LocationLine()),
|
||||||
|
Column: int(loc.LocationColumn()),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+63
-50
@@ -4,21 +4,17 @@ package interp
|
|||||||
// functions.
|
// functions.
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"tinygo.org/x/go-llvm"
|
"tinygo.org/x/go-llvm"
|
||||||
)
|
)
|
||||||
|
|
||||||
type frame struct {
|
type frame struct {
|
||||||
*Eval
|
*evalPackage
|
||||||
fn llvm.Value
|
fn llvm.Value
|
||||||
pkgName string
|
locals map[llvm.Value]Value
|
||||||
locals map[llvm.Value]Value
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var ErrUnreachable = errors.New("interp: unreachable executed")
|
|
||||||
|
|
||||||
// evalBasicBlock evaluates a single basic block, returning the return value (if
|
// evalBasicBlock evaluates a single basic block, returning the return value (if
|
||||||
// ending with a ret instruction), a list of outgoing basic blocks (if not
|
// ending with a ret instruction), a list of outgoing basic blocks (if not
|
||||||
// ending with a ret instruction), or an error on failure.
|
// ending with a ret instruction), or an error on failure.
|
||||||
@@ -79,13 +75,13 @@ func (fr *frame) evalBasicBlock(bb, incoming llvm.BasicBlock, indent string) (re
|
|||||||
fr.locals[inst] = &LocalValue{fr.Eval, fr.builder.CreateXor(lhs, rhs, "")}
|
fr.locals[inst] = &LocalValue{fr.Eval, fr.builder.CreateXor(lhs, rhs, "")}
|
||||||
|
|
||||||
default:
|
default:
|
||||||
return nil, nil, &Unsupported{inst}
|
return nil, nil, fr.unsupportedInstructionError(inst)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Memory operators
|
// Memory operators
|
||||||
case !inst.IsAAllocaInst().IsNil():
|
case !inst.IsAAllocaInst().IsNil():
|
||||||
allocType := inst.Type().ElementType()
|
allocType := inst.Type().ElementType()
|
||||||
alloca := llvm.AddGlobal(fr.Mod, allocType, fr.pkgName+"$alloca")
|
alloca := llvm.AddGlobal(fr.Mod, allocType, fr.packagePath+"$alloca")
|
||||||
alloca.SetInitializer(llvm.ConstNull(allocType))
|
alloca.SetInitializer(llvm.ConstNull(allocType))
|
||||||
alloca.SetLinkage(llvm.InternalLinkage)
|
alloca.SetLinkage(llvm.InternalLinkage)
|
||||||
fr.locals[inst] = &LocalValue{
|
fr.locals[inst] = &LocalValue{
|
||||||
@@ -101,7 +97,7 @@ func (fr *frame) evalBasicBlock(bb, incoming llvm.BasicBlock, indent string) (re
|
|||||||
value = operand.Load()
|
value = operand.Load()
|
||||||
}
|
}
|
||||||
if value.Type() != inst.Type() {
|
if value.Type() != inst.Type() {
|
||||||
panic("interp: load: type does not match")
|
return nil, nil, fr.errorAt(inst, "interp: load: type does not match")
|
||||||
}
|
}
|
||||||
fr.locals[inst] = fr.getValue(value)
|
fr.locals[inst] = fr.getValue(value)
|
||||||
case !inst.IsAStoreInst().IsNil():
|
case !inst.IsAStoreInst().IsNil():
|
||||||
@@ -125,15 +121,13 @@ func (fr *frame) evalBasicBlock(bb, incoming llvm.BasicBlock, indent string) (re
|
|||||||
// Not a constant operation.
|
// Not a constant operation.
|
||||||
// This should be detected by the scanner, but isn't at the
|
// This should be detected by the scanner, but isn't at the
|
||||||
// moment.
|
// moment.
|
||||||
panic("todo: non-const gep")
|
return nil, nil, fr.errorAt(inst, "todo: non-const gep")
|
||||||
}
|
}
|
||||||
indices[i] = uint32(operand.Value().ZExtValue())
|
indices[i] = uint32(operand.Value().ZExtValue())
|
||||||
}
|
}
|
||||||
result := value.GetElementPtr(indices)
|
result := value.GetElementPtr(indices)
|
||||||
if result.Type() != inst.Type() {
|
if result.Type() != inst.Type() {
|
||||||
println(" expected:", inst.Type().String())
|
return nil, nil, fr.errorAt(inst, "interp: gep: type does not match")
|
||||||
println(" actual: ", result.Type().String())
|
|
||||||
panic("interp: gep: type does not match")
|
|
||||||
}
|
}
|
||||||
fr.locals[inst] = result
|
fr.locals[inst] = result
|
||||||
|
|
||||||
@@ -188,7 +182,7 @@ func (fr *frame) evalBasicBlock(bb, incoming llvm.BasicBlock, indent string) (re
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
// It is not possible in Go to bitcast a map value to a pointer.
|
// It is not possible in Go to bitcast a map value to a pointer.
|
||||||
panic("unimplemented: bitcast of map")
|
return nil, nil, fr.errorAt(inst, "unimplemented: bitcast of map")
|
||||||
}
|
}
|
||||||
value := fr.getLocal(operand).(*LocalValue)
|
value := fr.getLocal(operand).(*LocalValue)
|
||||||
fr.locals[inst] = &LocalValue{fr.Eval, fr.builder.CreateBitCast(value.Value(), inst.Type(), "")}
|
fr.locals[inst] = &LocalValue{fr.Eval, fr.builder.CreateBitCast(value.Value(), inst.Type(), "")}
|
||||||
@@ -198,20 +192,28 @@ func (fr *frame) evalBasicBlock(bb, incoming llvm.BasicBlock, indent string) (re
|
|||||||
lhs := fr.getLocal(inst.Operand(0)).(*LocalValue).Underlying
|
lhs := fr.getLocal(inst.Operand(0)).(*LocalValue).Underlying
|
||||||
rhs := fr.getLocal(inst.Operand(1)).(*LocalValue).Underlying
|
rhs := fr.getLocal(inst.Operand(1)).(*LocalValue).Underlying
|
||||||
predicate := inst.IntPredicate()
|
predicate := inst.IntPredicate()
|
||||||
if predicate == llvm.IntEQ && lhs.Type().TypeKind() == llvm.PointerTypeKind {
|
if predicate == llvm.IntEQ {
|
||||||
// Unfortunately, the const propagation in the IR builder
|
var lhsZero, rhsZero bool
|
||||||
// doesn't handle pointer compares of inttoptr values. So we
|
var ok1, ok2 bool
|
||||||
// implement it manually here.
|
if lhs.Type().TypeKind() == llvm.PointerTypeKind {
|
||||||
lhsNil, ok1 := isPointerNil(lhs)
|
// Unfortunately, the const propagation in the IR builder
|
||||||
rhsNil, ok2 := isPointerNil(rhs)
|
// doesn't handle pointer compares of inttoptr values. So we
|
||||||
|
// implement it manually here.
|
||||||
|
lhsZero, ok1 = isPointerNil(lhs)
|
||||||
|
rhsZero, ok2 = isPointerNil(rhs)
|
||||||
|
}
|
||||||
|
if lhs.Type().TypeKind() == llvm.IntegerTypeKind {
|
||||||
|
lhsZero, ok1 = isZero(lhs)
|
||||||
|
rhsZero, ok2 = isZero(rhs)
|
||||||
|
}
|
||||||
if ok1 && ok2 {
|
if ok1 && ok2 {
|
||||||
if lhsNil && rhsNil {
|
if lhsZero && rhsZero {
|
||||||
// Both are nil, so this icmp is always evaluated to true.
|
// Both are zero, so this icmp is always evaluated to true.
|
||||||
fr.locals[inst] = &LocalValue{fr.Eval, llvm.ConstInt(fr.Mod.Context().Int1Type(), 1, false)}
|
fr.locals[inst] = &LocalValue{fr.Eval, llvm.ConstInt(fr.Mod.Context().Int1Type(), 1, false)}
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if lhsNil != rhsNil {
|
if lhsZero != rhsZero {
|
||||||
// Only one of them is nil, so this comparison must return false.
|
// Only one of them is zero, so this comparison must return false.
|
||||||
fr.locals[inst] = &LocalValue{fr.Eval, llvm.ConstInt(fr.Mod.Context().Int1Type(), 0, false)}
|
fr.locals[inst] = &LocalValue{fr.Eval, llvm.ConstInt(fr.Mod.Context().Int1Type(), 0, false)}
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
@@ -247,12 +249,12 @@ func (fr *frame) evalBasicBlock(bb, incoming llvm.BasicBlock, indent string) (re
|
|||||||
if size != typeSize {
|
if size != typeSize {
|
||||||
// allocate an array
|
// allocate an array
|
||||||
if size%typeSize != 0 {
|
if size%typeSize != 0 {
|
||||||
return nil, nil, &Unsupported{inst}
|
return nil, nil, fr.unsupportedInstructionError(inst)
|
||||||
}
|
}
|
||||||
elementCount = int(size / typeSize)
|
elementCount = int(size / typeSize)
|
||||||
allocType = llvm.ArrayType(allocType, elementCount)
|
allocType = llvm.ArrayType(allocType, elementCount)
|
||||||
}
|
}
|
||||||
alloc := llvm.AddGlobal(fr.Mod, allocType, fr.pkgName+"$alloc")
|
alloc := llvm.AddGlobal(fr.Mod, allocType, fr.packagePath+"$alloc")
|
||||||
alloc.SetInitializer(llvm.ConstNull(allocType))
|
alloc.SetInitializer(llvm.ConstNull(allocType))
|
||||||
alloc.SetLinkage(llvm.InternalLinkage)
|
alloc.SetLinkage(llvm.InternalLinkage)
|
||||||
result := &LocalValue{
|
result := &LocalValue{
|
||||||
@@ -270,13 +272,16 @@ func (fr *frame) evalBasicBlock(bb, incoming llvm.BasicBlock, indent string) (re
|
|||||||
valueSize := inst.Operand(1).ZExtValue()
|
valueSize := inst.Operand(1).ZExtValue()
|
||||||
fr.locals[inst] = &MapValue{
|
fr.locals[inst] = &MapValue{
|
||||||
Eval: fr.Eval,
|
Eval: fr.Eval,
|
||||||
PkgName: fr.pkgName,
|
PkgName: fr.packagePath,
|
||||||
KeySize: int(keySize),
|
KeySize: int(keySize),
|
||||||
ValueSize: int(valueSize),
|
ValueSize: int(valueSize),
|
||||||
}
|
}
|
||||||
case callee.Name() == "runtime.hashmapStringSet":
|
case callee.Name() == "runtime.hashmapStringSet":
|
||||||
// set a string key in the map
|
// set a string key in the map
|
||||||
m := fr.getLocal(inst.Operand(0)).(*MapValue)
|
m, ok := fr.getLocal(inst.Operand(0)).(*MapValue)
|
||||||
|
if !ok {
|
||||||
|
return nil, nil, fr.errorAt(inst, "could not update map with string key")
|
||||||
|
}
|
||||||
// "key" is a Go string value, which in the TinyGo calling convention is split up
|
// "key" is a Go string value, which in the TinyGo calling convention is split up
|
||||||
// into separate pointer and length parameters.
|
// into separate pointer and length parameters.
|
||||||
keyBuf := fr.getLocal(inst.Operand(1)).(*LocalValue)
|
keyBuf := fr.getLocal(inst.Operand(1)).(*LocalValue)
|
||||||
@@ -285,7 +290,10 @@ func (fr *frame) evalBasicBlock(bb, incoming llvm.BasicBlock, indent string) (re
|
|||||||
m.PutString(keyBuf, keyLen, valPtr)
|
m.PutString(keyBuf, keyLen, valPtr)
|
||||||
case callee.Name() == "runtime.hashmapBinarySet":
|
case callee.Name() == "runtime.hashmapBinarySet":
|
||||||
// set a binary (int etc.) key in the map
|
// set a binary (int etc.) key in the map
|
||||||
m := fr.getLocal(inst.Operand(0)).(*MapValue)
|
m, ok := fr.getLocal(inst.Operand(0)).(*MapValue)
|
||||||
|
if !ok {
|
||||||
|
return nil, nil, fr.errorAt(inst, "could not update map")
|
||||||
|
}
|
||||||
keyBuf := fr.getLocal(inst.Operand(1)).(*LocalValue)
|
keyBuf := fr.getLocal(inst.Operand(1)).(*LocalValue)
|
||||||
valPtr := fr.getLocal(inst.Operand(2)).(*LocalValue)
|
valPtr := fr.getLocal(inst.Operand(2)).(*LocalValue)
|
||||||
m.PutBinary(keyBuf, valPtr)
|
m.PutBinary(keyBuf, valPtr)
|
||||||
@@ -304,7 +312,7 @@ func (fr *frame) evalBasicBlock(bb, incoming llvm.BasicBlock, indent string) (re
|
|||||||
}
|
}
|
||||||
globalType := llvm.ArrayType(fr.Mod.Context().Int8Type(), len(result))
|
globalType := llvm.ArrayType(fr.Mod.Context().Int8Type(), len(result))
|
||||||
globalValue := llvm.ConstArray(fr.Mod.Context().Int8Type(), vals)
|
globalValue := llvm.ConstArray(fr.Mod.Context().Int8Type(), vals)
|
||||||
global := llvm.AddGlobal(fr.Mod, globalType, fr.pkgName+"$stringconcat")
|
global := llvm.AddGlobal(fr.Mod, globalType, fr.packagePath+"$stringconcat")
|
||||||
global.SetInitializer(globalValue)
|
global.SetInitializer(globalValue)
|
||||||
global.SetLinkage(llvm.InternalLinkage)
|
global.SetLinkage(llvm.InternalLinkage)
|
||||||
global.SetGlobalConstant(true)
|
global.SetGlobalConstant(true)
|
||||||
@@ -336,20 +344,20 @@ func (fr *frame) evalBasicBlock(bb, incoming llvm.BasicBlock, indent string) (re
|
|||||||
srcArray = srcArray.GetElementPtr([]uint32{0, 0}).(*LocalValue)
|
srcArray = srcArray.GetElementPtr([]uint32{0, 0}).(*LocalValue)
|
||||||
}
|
}
|
||||||
if fr.Eval.TargetData.TypeAllocSize(dstArray.Type().ElementType()) != elementSize {
|
if fr.Eval.TargetData.TypeAllocSize(dstArray.Type().ElementType()) != elementSize {
|
||||||
return nil, nil, errors.New("interp: slice dst element size does not match pointer type")
|
return nil, nil, fr.errorAt(inst, "interp: slice dst element size does not match pointer type")
|
||||||
}
|
}
|
||||||
if fr.Eval.TargetData.TypeAllocSize(srcArray.Type().ElementType()) != elementSize {
|
if fr.Eval.TargetData.TypeAllocSize(srcArray.Type().ElementType()) != elementSize {
|
||||||
return nil, nil, errors.New("interp: slice src element size does not match pointer type")
|
return nil, nil, fr.errorAt(inst, "interp: slice src element size does not match pointer type")
|
||||||
}
|
}
|
||||||
if dstArray.Type() != srcArray.Type() {
|
if dstArray.Type() != srcArray.Type() {
|
||||||
return nil, nil, errors.New("interp: slice element types don't match")
|
return nil, nil, fr.errorAt(inst, "interp: slice element types don't match")
|
||||||
}
|
}
|
||||||
length := dstLen.Value().SExtValue()
|
length := dstLen.Value().SExtValue()
|
||||||
if srcLength := srcLen.Value().SExtValue(); srcLength < length {
|
if srcLength := srcLen.Value().SExtValue(); srcLength < length {
|
||||||
length = srcLength
|
length = srcLength
|
||||||
}
|
}
|
||||||
if length < 0 {
|
if length < 0 {
|
||||||
return nil, nil, errors.New("interp: trying to copy a slice with negative length?")
|
return nil, nil, fr.errorAt(inst, "interp: trying to copy a slice with negative length?")
|
||||||
}
|
}
|
||||||
for i := int64(0); i < length; i++ {
|
for i := int64(0); i < length; i++ {
|
||||||
// *dst = *src
|
// *dst = *src
|
||||||
@@ -370,7 +378,7 @@ func (fr *frame) evalBasicBlock(bb, incoming llvm.BasicBlock, indent string) (re
|
|||||||
}
|
}
|
||||||
globalType := llvm.ArrayType(fr.Mod.Context().Int8Type(), len(result))
|
globalType := llvm.ArrayType(fr.Mod.Context().Int8Type(), len(result))
|
||||||
globalValue := llvm.ConstArray(fr.Mod.Context().Int8Type(), vals)
|
globalValue := llvm.ConstArray(fr.Mod.Context().Int8Type(), vals)
|
||||||
global := llvm.AddGlobal(fr.Mod, globalType, fr.pkgName+"$bytes")
|
global := llvm.AddGlobal(fr.Mod, globalType, fr.packagePath+"$bytes")
|
||||||
global.SetInitializer(globalValue)
|
global.SetInitializer(globalValue)
|
||||||
global.SetLinkage(llvm.InternalLinkage)
|
global.SetLinkage(llvm.InternalLinkage)
|
||||||
global.SetGlobalConstant(true)
|
global.SetGlobalConstant(true)
|
||||||
@@ -387,16 +395,16 @@ func (fr *frame) evalBasicBlock(bb, incoming llvm.BasicBlock, indent string) (re
|
|||||||
typecode := fr.getLocal(inst.Operand(0)).(*LocalValue).Underlying
|
typecode := fr.getLocal(inst.Operand(0)).(*LocalValue).Underlying
|
||||||
interfaceMethodSet := fr.getLocal(inst.Operand(1)).(*LocalValue).Underlying
|
interfaceMethodSet := fr.getLocal(inst.Operand(1)).(*LocalValue).Underlying
|
||||||
if typecode.IsAConstantExpr().IsNil() || typecode.Opcode() != llvm.PtrToInt {
|
if typecode.IsAConstantExpr().IsNil() || typecode.Opcode() != llvm.PtrToInt {
|
||||||
panic("interp: expected typecode to be a ptrtoint")
|
return nil, nil, fr.errorAt(inst, "interp: expected typecode to be a ptrtoint")
|
||||||
}
|
}
|
||||||
typecode = typecode.Operand(0)
|
typecode = typecode.Operand(0)
|
||||||
if interfaceMethodSet.IsAConstantExpr().IsNil() || interfaceMethodSet.Opcode() != llvm.GetElementPtr {
|
if interfaceMethodSet.IsAConstantExpr().IsNil() || interfaceMethodSet.Opcode() != llvm.GetElementPtr {
|
||||||
panic("interp: expected method set in runtime.interfaceImplements to be a constant gep")
|
return nil, nil, fr.errorAt(inst, "interp: expected method set in runtime.interfaceImplements to be a constant gep")
|
||||||
}
|
}
|
||||||
interfaceMethodSet = interfaceMethodSet.Operand(0).Initializer()
|
interfaceMethodSet = interfaceMethodSet.Operand(0).Initializer()
|
||||||
methodSet := llvm.ConstExtractValue(typecode.Initializer(), []uint32{1})
|
methodSet := llvm.ConstExtractValue(typecode.Initializer(), []uint32{1})
|
||||||
if methodSet.IsAConstantExpr().IsNil() || methodSet.Opcode() != llvm.GetElementPtr {
|
if methodSet.IsAConstantExpr().IsNil() || methodSet.Opcode() != llvm.GetElementPtr {
|
||||||
panic("interp: expected method set to be a constant gep")
|
return nil, nil, fr.errorAt(inst, "interp: expected method set to be a constant gep")
|
||||||
}
|
}
|
||||||
methodSet = methodSet.Operand(0).Initializer()
|
methodSet = methodSet.Operand(0).Initializer()
|
||||||
|
|
||||||
@@ -466,7 +474,10 @@ func (fr *frame) evalBasicBlock(bb, incoming llvm.BasicBlock, indent string) (re
|
|||||||
params = append(params, local)
|
params = append(params, local)
|
||||||
}
|
}
|
||||||
var ret Value
|
var ret Value
|
||||||
scanResult := fr.Eval.hasSideEffects(callee)
|
scanResult, err := fr.hasSideEffects(callee)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
if scanResult.severity == sideEffectLimited || dirtyParams && scanResult.severity != sideEffectAll {
|
if scanResult.severity == sideEffectLimited || dirtyParams && scanResult.severity != sideEffectAll {
|
||||||
// Side effect is bounded. This means the operation invokes
|
// Side effect is bounded. This means the operation invokes
|
||||||
// side effects (like calling an external function) but it
|
// side effects (like calling an external function) but it
|
||||||
@@ -489,7 +500,7 @@ func (fr *frame) evalBasicBlock(bb, incoming llvm.BasicBlock, indent string) (re
|
|||||||
// compile time.
|
// compile time.
|
||||||
// * Unbounded: cannot call at runtime so we'll try to
|
// * Unbounded: cannot call at runtime so we'll try to
|
||||||
// interpret anyway and hope for the best.
|
// interpret anyway and hope for the best.
|
||||||
ret, err = fr.function(callee, params, fr.pkgName, indent+" ")
|
ret, err = fr.function(callee, params, indent+" ")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
@@ -499,7 +510,7 @@ func (fr *frame) evalBasicBlock(bb, incoming llvm.BasicBlock, indent string) (re
|
|||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
// function pointers, etc.
|
// function pointers, etc.
|
||||||
return nil, nil, &Unsupported{inst}
|
return nil, nil, fr.unsupportedInstructionError(inst)
|
||||||
}
|
}
|
||||||
case !inst.IsAExtractValueInst().IsNil():
|
case !inst.IsAExtractValueInst().IsNil():
|
||||||
agg := fr.getLocal(inst.Operand(0)).(*LocalValue) // must be constant
|
agg := fr.getLocal(inst.Operand(0)).(*LocalValue) // must be constant
|
||||||
@@ -509,7 +520,7 @@ func (fr *frame) evalBasicBlock(bb, incoming llvm.BasicBlock, indent string) (re
|
|||||||
fr.locals[inst] = fr.getValue(newValue)
|
fr.locals[inst] = fr.getValue(newValue)
|
||||||
} else {
|
} else {
|
||||||
if len(indices) != 1 {
|
if len(indices) != 1 {
|
||||||
return nil, nil, errors.New("cannot handle extractvalue with not exactly 1 index")
|
return nil, nil, fr.errorAt(inst, "interp: cannot handle extractvalue with not exactly 1 index")
|
||||||
}
|
}
|
||||||
fr.locals[inst] = &LocalValue{fr.Eval, fr.builder.CreateExtractValue(agg.Underlying, int(indices[0]), inst.Name())}
|
fr.locals[inst] = &LocalValue{fr.Eval, fr.builder.CreateExtractValue(agg.Underlying, int(indices[0]), inst.Name())}
|
||||||
}
|
}
|
||||||
@@ -522,7 +533,7 @@ func (fr *frame) evalBasicBlock(bb, incoming llvm.BasicBlock, indent string) (re
|
|||||||
fr.locals[inst] = &LocalValue{fr.Eval, newValue}
|
fr.locals[inst] = &LocalValue{fr.Eval, newValue}
|
||||||
} else {
|
} else {
|
||||||
if len(indices) != 1 {
|
if len(indices) != 1 {
|
||||||
return nil, nil, errors.New("cannot handle insertvalue with not exactly 1 index")
|
return nil, nil, fr.errorAt(inst, "interp: cannot handle insertvalue with not exactly 1 index")
|
||||||
}
|
}
|
||||||
fr.locals[inst] = &LocalValue{fr.Eval, fr.builder.CreateInsertValue(agg.Underlying, val.Value(), int(indices[0]), inst.Name())}
|
fr.locals[inst] = &LocalValue{fr.Eval, fr.builder.CreateInsertValue(agg.Underlying, val.Value(), int(indices[0]), inst.Name())}
|
||||||
}
|
}
|
||||||
@@ -535,17 +546,17 @@ func (fr *frame) evalBasicBlock(bb, incoming llvm.BasicBlock, indent string) (re
|
|||||||
// conditional branch (if/then/else)
|
// conditional branch (if/then/else)
|
||||||
cond := fr.getLocal(inst.Operand(0)).Value()
|
cond := fr.getLocal(inst.Operand(0)).Value()
|
||||||
if cond.Type() != fr.Mod.Context().Int1Type() {
|
if cond.Type() != fr.Mod.Context().Int1Type() {
|
||||||
panic("expected an i1 in a branch instruction")
|
return nil, nil, fr.errorAt(inst, "expected an i1 in a branch instruction")
|
||||||
}
|
}
|
||||||
thenBB := inst.Operand(1)
|
thenBB := inst.Operand(1)
|
||||||
elseBB := inst.Operand(2)
|
elseBB := inst.Operand(2)
|
||||||
if !cond.IsAInstruction().IsNil() {
|
if !cond.IsAInstruction().IsNil() {
|
||||||
return nil, nil, errors.New("interp: branch on a non-constant")
|
return nil, nil, fr.errorAt(inst, "interp: branch on a non-constant")
|
||||||
}
|
}
|
||||||
if !cond.IsAConstantExpr().IsNil() {
|
if !cond.IsAConstantExpr().IsNil() {
|
||||||
// This may happen when the instruction builder could not
|
// This may happen when the instruction builder could not
|
||||||
// const-fold some instructions.
|
// const-fold some instructions.
|
||||||
return nil, nil, errors.New("interp: branch on a non-const-propagated constant expression")
|
return nil, nil, fr.errorAt(inst, "interp: branch on a non-const-propagated constant expression")
|
||||||
}
|
}
|
||||||
switch cond {
|
switch cond {
|
||||||
case llvm.ConstInt(fr.Mod.Context().Int1Type(), 0, false): // false
|
case llvm.ConstInt(fr.Mod.Context().Int1Type(), 0, false): // false
|
||||||
@@ -553,7 +564,7 @@ func (fr *frame) evalBasicBlock(bb, incoming llvm.BasicBlock, indent string) (re
|
|||||||
case llvm.ConstInt(fr.Mod.Context().Int1Type(), 1, false): // true
|
case llvm.ConstInt(fr.Mod.Context().Int1Type(), 1, false): // true
|
||||||
return nil, []llvm.Value{elseBB}, nil // else
|
return nil, []llvm.Value{elseBB}, nil // else
|
||||||
default:
|
default:
|
||||||
panic("branch was not true or false")
|
return nil, nil, fr.errorAt(inst, "branch was not true or false")
|
||||||
}
|
}
|
||||||
case !inst.IsABranchInst().IsNil() && inst.OperandsCount() == 1:
|
case !inst.IsABranchInst().IsNil() && inst.OperandsCount() == 1:
|
||||||
// unconditional branch (goto)
|
// unconditional branch (goto)
|
||||||
@@ -561,10 +572,11 @@ func (fr *frame) evalBasicBlock(bb, incoming llvm.BasicBlock, indent string) (re
|
|||||||
case !inst.IsAUnreachableInst().IsNil():
|
case !inst.IsAUnreachableInst().IsNil():
|
||||||
// Unreachable was reached (e.g. after a call to panic()).
|
// Unreachable was reached (e.g. after a call to panic()).
|
||||||
// Report this as an error, as it is not supposed to happen.
|
// Report this as an error, as it is not supposed to happen.
|
||||||
return nil, nil, ErrUnreachable
|
// This is a sentinel error value.
|
||||||
|
return nil, nil, errUnreachable
|
||||||
|
|
||||||
default:
|
default:
|
||||||
return nil, nil, &Unsupported{inst}
|
return nil, nil, fr.unsupportedInstructionError(inst)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -578,6 +590,7 @@ func (fr *frame) getLocal(v llvm.Value) Value {
|
|||||||
} else if value := fr.getValue(v); value != nil {
|
} else if value := fr.getValue(v); value != nil {
|
||||||
return value
|
return value
|
||||||
} else {
|
} else {
|
||||||
|
// This should not happen under normal circumstances.
|
||||||
panic("cannot find value")
|
panic("cannot find value")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+23
-14
@@ -7,7 +7,6 @@ package interp
|
|||||||
// methods.
|
// methods.
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"tinygo.org/x/go-llvm"
|
"tinygo.org/x/go-llvm"
|
||||||
@@ -22,6 +21,14 @@ type Eval struct {
|
|||||||
sideEffectFuncs map[llvm.Value]*sideEffectResult // cache of side effect scan results
|
sideEffectFuncs map[llvm.Value]*sideEffectResult // cache of side effect scan results
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// evalPackage encapsulates the Eval type for just a single package. The Eval
|
||||||
|
// type keeps state across the whole program, the evalPackage type keeps extra
|
||||||
|
// state for the currently interpreted package.
|
||||||
|
type evalPackage struct {
|
||||||
|
*Eval
|
||||||
|
packagePath string
|
||||||
|
}
|
||||||
|
|
||||||
// Run evaluates the function with the given name and then eliminates all
|
// Run evaluates the function with the given name and then eliminates all
|
||||||
// callers.
|
// callers.
|
||||||
func Run(mod llvm.Module, debug bool) error {
|
func Run(mod llvm.Module, debug bool) error {
|
||||||
@@ -56,7 +63,7 @@ func Run(mod llvm.Module, debug bool) error {
|
|||||||
break // ret void
|
break // ret void
|
||||||
}
|
}
|
||||||
if inst.IsACallInst().IsNil() || inst.CalledValue().IsAFunction().IsNil() {
|
if inst.IsACallInst().IsNil() || inst.CalledValue().IsAFunction().IsNil() {
|
||||||
return errors.New("expected all instructions in " + name + " to be direct calls")
|
return errorAt(inst, "interp: expected all instructions in "+name+" to be direct calls")
|
||||||
}
|
}
|
||||||
initCalls = append(initCalls, inst)
|
initCalls = append(initCalls, inst)
|
||||||
}
|
}
|
||||||
@@ -66,13 +73,17 @@ func Run(mod llvm.Module, debug bool) error {
|
|||||||
for _, call := range initCalls {
|
for _, call := range initCalls {
|
||||||
initName := call.CalledValue().Name()
|
initName := call.CalledValue().Name()
|
||||||
if !strings.HasSuffix(initName, ".init") {
|
if !strings.HasSuffix(initName, ".init") {
|
||||||
return errors.New("expected all instructions in " + name + " to be *.init() calls")
|
return errorAt(call, "interp: expected all instructions in "+name+" to be *.init() calls")
|
||||||
}
|
}
|
||||||
pkgName := initName[:len(initName)-5]
|
pkgName := initName[:len(initName)-5]
|
||||||
fn := call.CalledValue()
|
fn := call.CalledValue()
|
||||||
call.EraseFromParentAsInstruction()
|
call.EraseFromParentAsInstruction()
|
||||||
_, err := e.Function(fn, []Value{&LocalValue{e, undefPtr}, &LocalValue{e, undefPtr}}, pkgName)
|
evalPkg := evalPackage{
|
||||||
if err == ErrUnreachable {
|
Eval: e,
|
||||||
|
packagePath: pkgName,
|
||||||
|
}
|
||||||
|
_, err := evalPkg.function(fn, []Value{&LocalValue{e, undefPtr}, &LocalValue{e, undefPtr}}, "")
|
||||||
|
if err == errUnreachable {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -83,16 +94,14 @@ func Run(mod llvm.Module, debug bool) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e *Eval) Function(fn llvm.Value, params []Value, pkgName string) (Value, error) {
|
// function interprets the given function. The params are the function params
|
||||||
return e.function(fn, params, pkgName, "")
|
// and the indent is the string indentation to use when dumping all interpreted
|
||||||
}
|
// instructions.
|
||||||
|
func (e *evalPackage) function(fn llvm.Value, params []Value, indent string) (Value, error) {
|
||||||
func (e *Eval) function(fn llvm.Value, params []Value, pkgName, indent string) (Value, error) {
|
|
||||||
fr := frame{
|
fr := frame{
|
||||||
Eval: e,
|
evalPackage: e,
|
||||||
fn: fn,
|
fn: fn,
|
||||||
pkgName: pkgName,
|
locals: make(map[llvm.Value]Value),
|
||||||
locals: make(map[llvm.Value]Value),
|
|
||||||
}
|
}
|
||||||
for i, param := range fn.Params() {
|
for i, param := range fn.Params() {
|
||||||
fr.locals[param] = params[i]
|
fr.locals[param] = params[i]
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ func TestInterp(t *testing.T) {
|
|||||||
for _, name := range []string{
|
for _, name := range []string{
|
||||||
"basic",
|
"basic",
|
||||||
"slice-copy",
|
"slice-copy",
|
||||||
|
"consteval",
|
||||||
} {
|
} {
|
||||||
name := name // make tc local to this closure
|
name := name // make tc local to this closure
|
||||||
t.Run(name, func(t *testing.T) {
|
t.Run(name, func(t *testing.T) {
|
||||||
|
|||||||
+42
-16
@@ -6,6 +6,21 @@ import (
|
|||||||
|
|
||||||
type sideEffectSeverity int
|
type sideEffectSeverity int
|
||||||
|
|
||||||
|
func (severity sideEffectSeverity) String() string {
|
||||||
|
switch severity {
|
||||||
|
case sideEffectInProgress:
|
||||||
|
return "in progress"
|
||||||
|
case sideEffectNone:
|
||||||
|
return "none"
|
||||||
|
case sideEffectLimited:
|
||||||
|
return "limited"
|
||||||
|
case sideEffectAll:
|
||||||
|
return "all"
|
||||||
|
default:
|
||||||
|
return "unknown"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const (
|
const (
|
||||||
sideEffectInProgress sideEffectSeverity = iota // computing side effects is in progress (for recursive functions)
|
sideEffectInProgress sideEffectSeverity = iota // computing side effects is in progress (for recursive functions)
|
||||||
sideEffectNone // no side effects at all (pure)
|
sideEffectNone // no side effects at all (pure)
|
||||||
@@ -23,30 +38,33 @@ type sideEffectResult struct {
|
|||||||
// hasSideEffects scans this function and all descendants, recursively. It
|
// hasSideEffects scans this function and all descendants, recursively. It
|
||||||
// returns whether this function has side effects and if it does, which globals
|
// returns whether this function has side effects and if it does, which globals
|
||||||
// it mentions anywhere in this function or any called functions.
|
// it mentions anywhere in this function or any called functions.
|
||||||
func (e *Eval) hasSideEffects(fn llvm.Value) *sideEffectResult {
|
func (e *evalPackage) hasSideEffects(fn llvm.Value) (*sideEffectResult, error) {
|
||||||
switch fn.Name() {
|
switch fn.Name() {
|
||||||
case "runtime.alloc":
|
case "runtime.alloc":
|
||||||
// Cannot be scanned but can be interpreted.
|
// Cannot be scanned but can be interpreted.
|
||||||
return &sideEffectResult{severity: sideEffectNone}
|
return &sideEffectResult{severity: sideEffectNone}, nil
|
||||||
case "runtime.nanotime":
|
case "runtime.nanotime":
|
||||||
// Fixed value at compile time.
|
// Fixed value at compile time.
|
||||||
return &sideEffectResult{severity: sideEffectNone}
|
return &sideEffectResult{severity: sideEffectNone}, nil
|
||||||
case "runtime._panic":
|
case "runtime._panic":
|
||||||
return &sideEffectResult{severity: sideEffectLimited}
|
return &sideEffectResult{severity: sideEffectLimited}, nil
|
||||||
case "runtime.interfaceImplements":
|
case "runtime.interfaceImplements":
|
||||||
return &sideEffectResult{severity: sideEffectNone}
|
return &sideEffectResult{severity: sideEffectNone}, nil
|
||||||
case "runtime.sliceCopy":
|
case "runtime.sliceCopy":
|
||||||
return &sideEffectResult{severity: sideEffectNone}
|
return &sideEffectResult{severity: sideEffectNone}, nil
|
||||||
case "runtime.trackPointer":
|
case "runtime.trackPointer":
|
||||||
return &sideEffectResult{severity: sideEffectNone}
|
return &sideEffectResult{severity: sideEffectNone}, nil
|
||||||
case "llvm.dbg.value":
|
case "llvm.dbg.value":
|
||||||
return &sideEffectResult{severity: sideEffectNone}
|
return &sideEffectResult{severity: sideEffectNone}, nil
|
||||||
|
}
|
||||||
|
if fn.IsDeclaration() {
|
||||||
|
return &sideEffectResult{severity: sideEffectLimited}, nil
|
||||||
}
|
}
|
||||||
if e.sideEffectFuncs == nil {
|
if e.sideEffectFuncs == nil {
|
||||||
e.sideEffectFuncs = make(map[llvm.Value]*sideEffectResult)
|
e.sideEffectFuncs = make(map[llvm.Value]*sideEffectResult)
|
||||||
}
|
}
|
||||||
if se, ok := e.sideEffectFuncs[fn]; ok {
|
if se, ok := e.sideEffectFuncs[fn]; ok {
|
||||||
return se
|
return se, nil
|
||||||
}
|
}
|
||||||
result := &sideEffectResult{
|
result := &sideEffectResult{
|
||||||
severity: sideEffectInProgress,
|
severity: sideEffectInProgress,
|
||||||
@@ -57,6 +75,7 @@ func (e *Eval) hasSideEffects(fn llvm.Value) *sideEffectResult {
|
|||||||
for bb := fn.EntryBasicBlock(); !bb.IsNil(); bb = llvm.NextBasicBlock(bb) {
|
for bb := fn.EntryBasicBlock(); !bb.IsNil(); bb = llvm.NextBasicBlock(bb) {
|
||||||
for inst := bb.FirstInstruction(); !inst.IsNil(); inst = llvm.NextInstruction(inst) {
|
for inst := bb.FirstInstruction(); !inst.IsNil(); inst = llvm.NextInstruction(inst) {
|
||||||
if inst.IsAInstruction().IsNil() {
|
if inst.IsAInstruction().IsNil() {
|
||||||
|
// Should not happen in valid IR.
|
||||||
panic("not an instruction")
|
panic("not an instruction")
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -73,7 +92,7 @@ func (e *Eval) hasSideEffects(fn llvm.Value) *sideEffectResult {
|
|||||||
switch inst.InstructionOpcode() {
|
switch inst.InstructionOpcode() {
|
||||||
case llvm.IndirectBr, llvm.Invoke:
|
case llvm.IndirectBr, llvm.Invoke:
|
||||||
// Not emitted by the compiler.
|
// Not emitted by the compiler.
|
||||||
panic("unknown instructions")
|
return nil, e.errorAt(inst, "unknown instructions")
|
||||||
case llvm.Call:
|
case llvm.Call:
|
||||||
child := inst.CalledValue()
|
child := inst.CalledValue()
|
||||||
if !child.IsAInlineAsm().IsNil() {
|
if !child.IsAInlineAsm().IsNil() {
|
||||||
@@ -99,7 +118,10 @@ func (e *Eval) hasSideEffects(fn llvm.Value) *sideEffectResult {
|
|||||||
}
|
}
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
childSideEffects := e.hasSideEffects(child)
|
childSideEffects, err := e.hasSideEffects(child)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
switch childSideEffects.severity {
|
switch childSideEffects.severity {
|
||||||
case sideEffectInProgress, sideEffectNone:
|
case sideEffectInProgress, sideEffectNone:
|
||||||
// no side effects or recursive function - continue scanning
|
// no side effects or recursive function - continue scanning
|
||||||
@@ -141,7 +163,7 @@ func (e *Eval) hasSideEffects(fn llvm.Value) *sideEffectResult {
|
|||||||
// No side effect was reported for this function.
|
// No side effect was reported for this function.
|
||||||
result.severity = sideEffectNone
|
result.severity = sideEffectNone
|
||||||
}
|
}
|
||||||
return result
|
return result, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// hasLocalSideEffects checks whether the given instruction flows into a branch
|
// hasLocalSideEffects checks whether the given instruction flows into a branch
|
||||||
@@ -156,6 +178,7 @@ func (e *Eval) hasLocalSideEffects(dirtyLocals map[llvm.Value]struct{}, inst llv
|
|||||||
for use := inst.FirstUse(); !use.IsNil(); use = use.NextUse() {
|
for use := inst.FirstUse(); !use.IsNil(); use = use.NextUse() {
|
||||||
user := use.User()
|
user := use.User()
|
||||||
if user.IsAInstruction().IsNil() {
|
if user.IsAInstruction().IsNil() {
|
||||||
|
// Should not happen in valid IR.
|
||||||
panic("user not an instruction")
|
panic("user not an instruction")
|
||||||
}
|
}
|
||||||
switch user.InstructionOpcode() {
|
switch user.InstructionOpcode() {
|
||||||
@@ -175,10 +198,13 @@ func (e *Eval) hasLocalSideEffects(dirtyLocals map[llvm.Value]struct{}, inst llv
|
|||||||
// Already handled in (*Eval).hasSideEffects.
|
// Already handled in (*Eval).hasSideEffects.
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
// But a store might also store to an alloca, in which case all uses
|
// This store might affect all kinds of values. While it is
|
||||||
// of the alloca (possibly indirect through a GEP, bitcast, etc.)
|
// certainly possible to traverse through all of them, the easiest
|
||||||
// must be marked dirty.
|
// option right now is to just assume the worst and say that this
|
||||||
panic("todo: store")
|
// function has side effects.
|
||||||
|
// TODO: traverse through all stores and mark all relevant allocas /
|
||||||
|
// globals dirty.
|
||||||
|
return true
|
||||||
default:
|
default:
|
||||||
// All instructions that take 0 or more operands (1 or more if it
|
// All instructions that take 0 or more operands (1 or more if it
|
||||||
// was a use) and produce a result.
|
// was a use) and produce a result.
|
||||||
|
|||||||
@@ -0,0 +1,93 @@
|
|||||||
|
package interp
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"sort"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"tinygo.org/x/go-llvm"
|
||||||
|
)
|
||||||
|
|
||||||
|
var scanTestTable = []struct {
|
||||||
|
name string
|
||||||
|
severity sideEffectSeverity
|
||||||
|
mentionsGlobals []string
|
||||||
|
}{
|
||||||
|
{"returnsConst", sideEffectNone, nil},
|
||||||
|
{"returnsArg", sideEffectNone, nil},
|
||||||
|
{"externalCallOnly", sideEffectNone, nil},
|
||||||
|
{"externalCallAndReturn", sideEffectLimited, nil},
|
||||||
|
{"externalCallBranch", sideEffectLimited, nil},
|
||||||
|
{"readCleanGlobal", sideEffectNone, []string{"cleanGlobalInt"}},
|
||||||
|
{"readDirtyGlobal", sideEffectLimited, []string{"dirtyGlobalInt"}},
|
||||||
|
{"callFunctionPointer", sideEffectAll, []string{"functionPointer"}},
|
||||||
|
{"getDirtyPointer", sideEffectLimited, nil},
|
||||||
|
{"storeToPointer", sideEffectLimited, nil},
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestScan(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
// Read the input IR.
|
||||||
|
path := "testdata/scan.ll"
|
||||||
|
ctx := llvm.NewContext()
|
||||||
|
buf, err := llvm.NewMemoryBufferFromFile(path)
|
||||||
|
os.Stat(path) // make sure this file is tracked by `go test` caching
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("could not read file %s: %v", path, err)
|
||||||
|
}
|
||||||
|
mod, err := ctx.ParseIR(buf)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("could not load module:\n%v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check all to-be-tested functions.
|
||||||
|
for _, tc := range scanTestTable {
|
||||||
|
// Create an eval object, for testing.
|
||||||
|
e := &Eval{
|
||||||
|
Mod: mod,
|
||||||
|
TargetData: llvm.NewTargetData(mod.DataLayout()),
|
||||||
|
dirtyGlobals: map[llvm.Value]struct{}{},
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mark some globals dirty, for testing.
|
||||||
|
e.markDirty(mod.NamedGlobal("dirtyGlobalInt"))
|
||||||
|
|
||||||
|
// Scan for side effects.
|
||||||
|
fn := mod.NamedFunction(tc.name)
|
||||||
|
if fn.IsNil() {
|
||||||
|
t.Errorf("scan test: could not find tested function %s in the IR", tc.name)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
evalPkg := &evalPackage{e, "testdata"}
|
||||||
|
result, err := evalPkg.hasSideEffects(fn)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("scan test: failed to scan %s for side effects: %v", fn.Name(), err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check whether the result is what we expect.
|
||||||
|
if result.severity != tc.severity {
|
||||||
|
t.Errorf("scan test: function %s should have severity %s but it has %s", tc.name, tc.severity, result.severity)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check whether the mentioned globals match with what we'd expect.
|
||||||
|
mentionsGlobalNames := make([]string, 0, len(result.mentionsGlobals))
|
||||||
|
for global := range result.mentionsGlobals {
|
||||||
|
mentionsGlobalNames = append(mentionsGlobalNames, global.Name())
|
||||||
|
}
|
||||||
|
sort.Strings(mentionsGlobalNames)
|
||||||
|
globalsMismatch := false
|
||||||
|
if len(result.mentionsGlobals) != len(tc.mentionsGlobals) {
|
||||||
|
globalsMismatch = true
|
||||||
|
} else {
|
||||||
|
for i, globalName := range mentionsGlobalNames {
|
||||||
|
if tc.mentionsGlobals[i] != globalName {
|
||||||
|
globalsMismatch = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if globalsMismatch {
|
||||||
|
t.Errorf("scan test: expected %s to mention globals %v, but it mentions globals %v", tc.name, tc.mentionsGlobals, mentionsGlobalNames)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Vendored
+18
@@ -2,6 +2,8 @@ target datalayout = "e-m:e-i64:64-f80:128-n8:16:32:64-S128"
|
|||||||
target triple = "x86_64--linux"
|
target triple = "x86_64--linux"
|
||||||
|
|
||||||
@main.v1 = internal global i64 0
|
@main.v1 = internal global i64 0
|
||||||
|
@main.nonConst1 = global [4 x i64] zeroinitializer
|
||||||
|
@main.nonConst2 = global i64 0
|
||||||
|
|
||||||
declare void @runtime.printint64(i64) unnamed_addr
|
declare void @runtime.printint64(i64) unnamed_addr
|
||||||
|
|
||||||
@@ -31,6 +33,20 @@ define internal void @main.init() unnamed_addr {
|
|||||||
entry:
|
entry:
|
||||||
store i64 3, i64* @main.v1
|
store i64 3, i64* @main.v1
|
||||||
call void @"main.init#1"()
|
call void @"main.init#1"()
|
||||||
|
|
||||||
|
; test the following pattern:
|
||||||
|
; func someValue() int // extern function
|
||||||
|
; var nonConst1 = [4]int{someValue(), 0, 0, 0}
|
||||||
|
%value1 = call i64 @someValue()
|
||||||
|
%gep1 = getelementptr [4 x i64], [4 x i64]* @main.nonConst1, i32 0, i32 0
|
||||||
|
store i64 %value1, i64* %gep1
|
||||||
|
|
||||||
|
; Test that the global really is marked dirty:
|
||||||
|
; var nonConst2 = nonConst1[0]
|
||||||
|
%gep2 = getelementptr [4 x i64], [4 x i64]* @main.nonConst1, i32 0, i32 0
|
||||||
|
%value2 = load i64, i64* %gep2
|
||||||
|
store i64 %value2, i64* @main.nonConst2
|
||||||
|
|
||||||
ret void
|
ret void
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -40,3 +56,5 @@ entry:
|
|||||||
call void @runtime.printnl()
|
call void @runtime.printnl()
|
||||||
ret void
|
ret void
|
||||||
}
|
}
|
||||||
|
|
||||||
|
declare i64 @someValue()
|
||||||
|
|||||||
Vendored
+9
@@ -1,6 +1,9 @@
|
|||||||
target datalayout = "e-m:e-i64:64-f80:128-n8:16:32:64-S128"
|
target datalayout = "e-m:e-i64:64-f80:128-n8:16:32:64-S128"
|
||||||
target triple = "x86_64--linux"
|
target triple = "x86_64--linux"
|
||||||
|
|
||||||
|
@main.nonConst1 = local_unnamed_addr global [4 x i64] zeroinitializer
|
||||||
|
@main.nonConst2 = local_unnamed_addr global i64 0
|
||||||
|
|
||||||
declare void @runtime.printint64(i64) unnamed_addr
|
declare void @runtime.printint64(i64) unnamed_addr
|
||||||
|
|
||||||
declare void @runtime.printnl() unnamed_addr
|
declare void @runtime.printnl() unnamed_addr
|
||||||
@@ -9,6 +12,10 @@ define void @runtime.initAll() unnamed_addr {
|
|||||||
entry:
|
entry:
|
||||||
call void @runtime.printint64(i64 5)
|
call void @runtime.printint64(i64 5)
|
||||||
call void @runtime.printnl()
|
call void @runtime.printnl()
|
||||||
|
%value1 = call i64 @someValue()
|
||||||
|
store i64 %value1, i64* getelementptr inbounds ([4 x i64], [4 x i64]* @main.nonConst1, i32 0, i32 0)
|
||||||
|
%value2 = load i64, i64* getelementptr inbounds ([4 x i64], [4 x i64]* @main.nonConst1, i32 0, i32 0)
|
||||||
|
store i64 %value2, i64* @main.nonConst2
|
||||||
ret void
|
ret void
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -18,3 +25,5 @@ entry:
|
|||||||
call void @runtime.printnl()
|
call void @runtime.printnl()
|
||||||
ret void
|
ret void
|
||||||
}
|
}
|
||||||
|
|
||||||
|
declare i64 @someValue() local_unnamed_addr
|
||||||
|
|||||||
Vendored
+42
@@ -0,0 +1,42 @@
|
|||||||
|
target datalayout = "e-m:e-i64:64-f80:128-n8:16:32:64-S128"
|
||||||
|
target triple = "x86_64--linux"
|
||||||
|
|
||||||
|
@intToPtrResult = global i8 0
|
||||||
|
@ptrToIntResult = global i8 0
|
||||||
|
|
||||||
|
define void @runtime.initAll() {
|
||||||
|
call void @main.init()
|
||||||
|
ret void
|
||||||
|
}
|
||||||
|
|
||||||
|
define internal void @main.init() {
|
||||||
|
call void @testIntToPtr()
|
||||||
|
call void @testPtrToInt()
|
||||||
|
ret void
|
||||||
|
}
|
||||||
|
|
||||||
|
define internal void @testIntToPtr() {
|
||||||
|
%nil = icmp eq i8* inttoptr (i64 1024 to i8*), null
|
||||||
|
br i1 %nil, label %a, label %b
|
||||||
|
a:
|
||||||
|
; should not be reached
|
||||||
|
store i8 1, i8* @intToPtrResult
|
||||||
|
ret void
|
||||||
|
b:
|
||||||
|
; should be reached
|
||||||
|
store i8 2, i8* @intToPtrResult
|
||||||
|
ret void
|
||||||
|
}
|
||||||
|
|
||||||
|
define internal void @testPtrToInt() {
|
||||||
|
%zero = icmp eq i64 ptrtoint (i8* @ptrToIntResult to i64), 0
|
||||||
|
br i1 %zero, label %a, label %b
|
||||||
|
a:
|
||||||
|
; should not be reached
|
||||||
|
store i8 1, i8* @ptrToIntResult
|
||||||
|
ret void
|
||||||
|
b:
|
||||||
|
; should be reached
|
||||||
|
store i8 2, i8* @ptrToIntResult
|
||||||
|
ret void
|
||||||
|
}
|
||||||
Vendored
+9
@@ -0,0 +1,9 @@
|
|||||||
|
target datalayout = "e-m:e-i64:64-f80:128-n8:16:32:64-S128"
|
||||||
|
target triple = "x86_64--linux"
|
||||||
|
|
||||||
|
@intToPtrResult = local_unnamed_addr global i8 2
|
||||||
|
@ptrToIntResult = local_unnamed_addr global i8 2
|
||||||
|
|
||||||
|
define void @runtime.initAll() local_unnamed_addr {
|
||||||
|
ret void
|
||||||
|
}
|
||||||
Vendored
+61
@@ -0,0 +1,61 @@
|
|||||||
|
target datalayout = "e-m:e-i64:64-f80:128-n8:16:32:64-S128"
|
||||||
|
target triple = "x86_64--linux"
|
||||||
|
|
||||||
|
define i64 @returnsConst() {
|
||||||
|
ret i64 0
|
||||||
|
}
|
||||||
|
|
||||||
|
define i64 @returnsArg(i64 %arg) {
|
||||||
|
ret i64 %arg
|
||||||
|
}
|
||||||
|
|
||||||
|
declare i64 @externalCall()
|
||||||
|
|
||||||
|
define i64 @externalCallOnly() {
|
||||||
|
%result = call i64 @externalCall()
|
||||||
|
ret i64 0
|
||||||
|
}
|
||||||
|
|
||||||
|
define i64 @externalCallAndReturn() {
|
||||||
|
%result = call i64 @externalCall()
|
||||||
|
ret i64 %result
|
||||||
|
}
|
||||||
|
|
||||||
|
define i64 @externalCallBranch() {
|
||||||
|
%result = call i64 @externalCall()
|
||||||
|
%zero = icmp eq i64 %result, 0
|
||||||
|
br i1 %zero, label %if.then, label %if.done
|
||||||
|
|
||||||
|
if.then:
|
||||||
|
ret i64 2
|
||||||
|
|
||||||
|
if.done:
|
||||||
|
ret i64 4
|
||||||
|
}
|
||||||
|
|
||||||
|
@cleanGlobalInt = global i64 5
|
||||||
|
define i64 @readCleanGlobal() {
|
||||||
|
%global = load i64, i64* @cleanGlobalInt
|
||||||
|
ret i64 %global
|
||||||
|
}
|
||||||
|
|
||||||
|
@dirtyGlobalInt = global i64 5
|
||||||
|
define i64 @readDirtyGlobal() {
|
||||||
|
%global = load i64, i64* @dirtyGlobalInt
|
||||||
|
ret i64 %global
|
||||||
|
}
|
||||||
|
|
||||||
|
declare i64* @getDirtyPointer()
|
||||||
|
|
||||||
|
define void @storeToPointer() {
|
||||||
|
%ptr = call i64* @getDirtyPointer()
|
||||||
|
store i64 3, i64* %ptr
|
||||||
|
ret void
|
||||||
|
}
|
||||||
|
|
||||||
|
@functionPointer = global i64()* null
|
||||||
|
define i64 @callFunctionPointer() {
|
||||||
|
%fp = load i64()*, i64()** @functionPointer
|
||||||
|
%result = call i64 %fp()
|
||||||
|
ret i64 %result
|
||||||
|
}
|
||||||
+42
-3
@@ -60,9 +60,9 @@ func isPointerNil(v llvm.Value) (result bool, ok bool) {
|
|||||||
case llvm.IntToPtr:
|
case llvm.IntToPtr:
|
||||||
// Whether a constant inttoptr is nil is easy to
|
// Whether a constant inttoptr is nil is easy to
|
||||||
// determine.
|
// determine.
|
||||||
operand := v.Operand(0)
|
result, ok = isZero(v.Operand(0))
|
||||||
if operand.IsConstant() {
|
if ok {
|
||||||
return operand.ZExtValue() == 0, true
|
return
|
||||||
}
|
}
|
||||||
case llvm.BitCast, llvm.GetElementPtr:
|
case llvm.BitCast, llvm.GetElementPtr:
|
||||||
// These const instructions are just a kind of wrappers for the
|
// These const instructions are just a kind of wrappers for the
|
||||||
@@ -74,5 +74,44 @@ func isPointerNil(v llvm.Value) (result bool, ok bool) {
|
|||||||
// A constant pointer null is always null, of course.
|
// A constant pointer null is always null, of course.
|
||||||
return true, true
|
return true, true
|
||||||
}
|
}
|
||||||
|
if !v.IsAGlobalValue().IsNil() {
|
||||||
|
// A global value is never null.
|
||||||
|
return false, true
|
||||||
|
}
|
||||||
return false, false // not valid
|
return false, false // not valid
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// isZero returns whether the value in v is the integer zero, and whether that
|
||||||
|
// can be known right now.
|
||||||
|
func isZero(v llvm.Value) (result bool, ok bool) {
|
||||||
|
if !v.IsAConstantExpr().IsNil() {
|
||||||
|
switch v.Opcode() {
|
||||||
|
case llvm.PtrToInt:
|
||||||
|
return isPointerNil(v.Operand(0))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !v.IsAConstantInt().IsNil() {
|
||||||
|
val := v.ZExtValue()
|
||||||
|
return val == 0, true
|
||||||
|
}
|
||||||
|
return false, false // not valid
|
||||||
|
}
|
||||||
|
|
||||||
|
// unwrap returns the underlying value, with GEPs removed. This can be useful to
|
||||||
|
// get the underlying global of a GEP pointer.
|
||||||
|
func unwrap(value llvm.Value) llvm.Value {
|
||||||
|
for {
|
||||||
|
if !value.IsAConstantExpr().IsNil() {
|
||||||
|
switch value.Opcode() {
|
||||||
|
case llvm.GetElementPtr:
|
||||||
|
value = value.Operand(0)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
} else if !value.IsAGetElementPtrInst().IsNil() {
|
||||||
|
value = value.Operand(0)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
return value
|
||||||
|
}
|
||||||
|
|||||||
+9
-3
@@ -36,7 +36,7 @@ func (v *LocalValue) Type() llvm.Type {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (v *LocalValue) IsConstant() bool {
|
func (v *LocalValue) IsConstant() bool {
|
||||||
if _, ok := v.Eval.dirtyGlobals[v.Underlying]; ok {
|
if _, ok := v.Eval.dirtyGlobals[unwrap(v.Underlying)]; ok {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
return v.Underlying.IsConstant()
|
return v.Underlying.IsConstant()
|
||||||
@@ -75,6 +75,11 @@ func (v *LocalValue) Store(value llvm.Value) {
|
|||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
if !value.IsConstant() {
|
||||||
|
v.MarkDirty()
|
||||||
|
v.Eval.builder.CreateStore(value, v.Underlying)
|
||||||
|
return
|
||||||
|
}
|
||||||
switch v.Underlying.Opcode() {
|
switch v.Underlying.Opcode() {
|
||||||
case llvm.GetElementPtr:
|
case llvm.GetElementPtr:
|
||||||
indices := v.getConstGEPIndices()
|
indices := v.getConstGEPIndices()
|
||||||
@@ -150,13 +155,14 @@ func (v *LocalValue) getConstGEPIndices() []uint32 {
|
|||||||
// MarkDirty marks this global as dirty, meaning that every load from and store
|
// MarkDirty marks this global as dirty, meaning that every load from and store
|
||||||
// to this global (from now on) must be performed at runtime.
|
// to this global (from now on) must be performed at runtime.
|
||||||
func (v *LocalValue) MarkDirty() {
|
func (v *LocalValue) MarkDirty() {
|
||||||
if v.Underlying.IsAGlobalVariable().IsNil() {
|
underlying := unwrap(v.Underlying)
|
||||||
|
if underlying.IsAGlobalVariable().IsNil() {
|
||||||
panic("trying to mark a non-global as dirty")
|
panic("trying to mark a non-global as dirty")
|
||||||
}
|
}
|
||||||
if !v.IsConstant() {
|
if !v.IsConstant() {
|
||||||
return // already dirty
|
return // already dirty
|
||||||
}
|
}
|
||||||
v.Eval.dirtyGlobals[v.Underlying] = struct{}{}
|
v.Eval.dirtyGlobals[underlying] = struct{}{}
|
||||||
}
|
}
|
||||||
|
|
||||||
// MapValue implements a Go map which is created at compile time and stored as a
|
// MapValue implements a Go map which is created at compile time and stored as a
|
||||||
|
|||||||
@@ -4,9 +4,9 @@ import (
|
|||||||
"errors"
|
"errors"
|
||||||
"flag"
|
"flag"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"go/scanner"
|
||||||
"go/types"
|
"go/types"
|
||||||
"io"
|
"io"
|
||||||
"io/ioutil"
|
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"os/signal"
|
"os/signal"
|
||||||
@@ -17,7 +17,8 @@ import (
|
|||||||
"syscall"
|
"syscall"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/tinygo-org/tinygo/compiler"
|
"github.com/tinygo-org/tinygo/builder"
|
||||||
|
"github.com/tinygo-org/tinygo/compileopts"
|
||||||
"github.com/tinygo-org/tinygo/goenv"
|
"github.com/tinygo-org/tinygo/goenv"
|
||||||
"github.com/tinygo-org/tinygo/interp"
|
"github.com/tinygo-org/tinygo/interp"
|
||||||
"github.com/tinygo-org/tinygo/loader"
|
"github.com/tinygo-org/tinygo/loader"
|
||||||
@@ -37,310 +38,50 @@ func (e *commandError) Error() string {
|
|||||||
return e.Msg + " " + e.File + ": " + e.Err.Error()
|
return e.Msg + " " + e.File + ": " + e.Err.Error()
|
||||||
}
|
}
|
||||||
|
|
||||||
// multiError is a list of multiple errors (actually: diagnostics) returned
|
// moveFile renames the file from src to dst. If renaming doesn't work (for
|
||||||
// during LLVM IR generation.
|
// example, the rename crosses a filesystem boundary), the file is copied and
|
||||||
type multiError struct {
|
// the old file is removed.
|
||||||
Errs []error
|
func moveFile(src, dst string) error {
|
||||||
}
|
err := os.Rename(src, dst)
|
||||||
|
if err == nil {
|
||||||
func (e *multiError) Error() string {
|
// Success!
|
||||||
return e.Errs[0].Error()
|
return nil
|
||||||
}
|
|
||||||
|
|
||||||
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.
|
|
||||||
func Compile(pkgName, outpath string, spec *TargetSpec, config *BuildConfig, action func(string) error) error {
|
|
||||||
if config.gc == "" && spec.GC != "" {
|
|
||||||
config.gc = spec.GC
|
|
||||||
}
|
}
|
||||||
|
// Failed to move, probably a different filesystem.
|
||||||
root := goenv.Get("TINYGOROOT")
|
// Do a copy + remove.
|
||||||
|
inf, err := os.Open(src)
|
||||||
// Merge and adjust CFlags.
|
|
||||||
cflags := append([]string{}, config.cFlags...)
|
|
||||||
for _, flag := range spec.CFlags {
|
|
||||||
cflags = append(cflags, strings.Replace(flag, "{root}", root, -1))
|
|
||||||
}
|
|
||||||
|
|
||||||
// Merge and adjust LDFlags.
|
|
||||||
ldflags := append([]string{}, config.ldFlags...)
|
|
||||||
for _, flag := range spec.LDFlags {
|
|
||||||
ldflags = append(ldflags, strings.Replace(flag, "{root}", root, -1))
|
|
||||||
}
|
|
||||||
|
|
||||||
goroot := goenv.Get("GOROOT")
|
|
||||||
if goroot == "" {
|
|
||||||
return errors.New("cannot locate $GOROOT, please set it manually")
|
|
||||||
}
|
|
||||||
tags := spec.BuildTags
|
|
||||||
major, minor, err := getGorootVersion(goroot)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("could not read version from GOROOT (%v): %v", goroot, err)
|
return err
|
||||||
}
|
}
|
||||||
if major != 1 || (minor != 11 && minor != 12 && minor != 13) {
|
defer inf.Close()
|
||||||
return fmt.Errorf("requires go version 1.11, 1.12, or 1.13, got go%d.%d", major, minor)
|
outpath := dst + ".tmp"
|
||||||
}
|
outf, err := os.Create(outpath)
|
||||||
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,
|
|
||||||
Features: spec.Features,
|
|
||||||
GOOS: spec.GOOS,
|
|
||||||
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: goenv.Get("GOPATH"),
|
|
||||||
BuildTags: tags,
|
|
||||||
TestConfig: config.testConfig,
|
|
||||||
}
|
|
||||||
c, err := compiler.NewCompiler(pkgName, compilerConfig)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Compile Go code to IR.
|
_, err = io.Copy(outf, inf)
|
||||||
errs := c.Compile(pkgName)
|
if err != nil {
|
||||||
if len(errs) != 0 {
|
os.Remove(outpath)
|
||||||
if len(errs) == 1 {
|
return err
|
||||||
return errs[0]
|
|
||||||
}
|
|
||||||
return &multiError{errs}
|
|
||||||
}
|
|
||||||
if config.printIR {
|
|
||||||
fmt.Println("; Generated LLVM IR:")
|
|
||||||
fmt.Println(c.IR())
|
|
||||||
}
|
|
||||||
if err := c.Verify(); err != nil {
|
|
||||||
return errors.New("verification error after IR construction")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
err = interp.Run(c.Module(), config.dumpSSA)
|
err = outf.Close()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if err := c.Verify(); err != nil {
|
|
||||||
return errors.New("verification error after interpreting runtime.initAll")
|
|
||||||
}
|
|
||||||
|
|
||||||
if spec.GOOS != "darwin" {
|
return os.Rename(dst+".tmp", dst)
|
||||||
c.ApplyFunctionSections() // -ffunction-sections
|
|
||||||
}
|
|
||||||
|
|
||||||
// Browsers cannot handle external functions that have type i64 because it
|
|
||||||
// cannot be represented exactly in JavaScript (JS only has doubles). To
|
|
||||||
// keep functions interoperable, pass int64 types as pointers to
|
|
||||||
// stack-allocated values.
|
|
||||||
// Use -wasm-abi=generic to disable this behaviour.
|
|
||||||
if config.wasmAbi == "js" && strings.HasPrefix(spec.Triple, "wasm") {
|
|
||||||
err := c.ExternalInt64AsPtr()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Optimization levels here are roughly the same as Clang, but probably not
|
|
||||||
// exactly.
|
|
||||||
switch config.opt {
|
|
||||||
case "none:", "0":
|
|
||||||
err = c.Optimize(0, 0, 0) // -O0
|
|
||||||
case "1":
|
|
||||||
err = c.Optimize(1, 0, 0) // -O1
|
|
||||||
case "2":
|
|
||||||
err = c.Optimize(2, 0, 225) // -O2
|
|
||||||
case "s":
|
|
||||||
err = c.Optimize(2, 1, 225) // -Os
|
|
||||||
case "z":
|
|
||||||
err = c.Optimize(2, 2, 5) // -Oz, default
|
|
||||||
default:
|
|
||||||
err = errors.New("unknown optimization level: -opt=" + config.opt)
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if err := c.Verify(); err != nil {
|
|
||||||
return errors.New("verification failure after LLVM optimization passes")
|
|
||||||
}
|
|
||||||
|
|
||||||
// On the AVR, pointers can point either to flash or to RAM, but we don't
|
|
||||||
// know. As a temporary fix, load all global variables in RAM.
|
|
||||||
// In the future, there should be a compiler pass that determines which
|
|
||||||
// pointers are flash and which are in RAM so that pointers can have a
|
|
||||||
// correct address space parameter (address space 1 is for flash).
|
|
||||||
if strings.HasPrefix(spec.Triple, "avr") {
|
|
||||||
c.NonConstGlobals()
|
|
||||||
if err := c.Verify(); err != nil {
|
|
||||||
return errors.New("verification error after making all globals non-constant on AVR")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Generate output.
|
|
||||||
outext := filepath.Ext(outpath)
|
|
||||||
switch outext {
|
|
||||||
case ".o":
|
|
||||||
return c.EmitObject(outpath)
|
|
||||||
case ".bc":
|
|
||||||
return c.EmitBitcode(outpath)
|
|
||||||
case ".ll":
|
|
||||||
return c.EmitText(outpath)
|
|
||||||
default:
|
|
||||||
// Act as a compiler driver.
|
|
||||||
|
|
||||||
// Create a temporary directory for intermediary files.
|
|
||||||
dir, err := ioutil.TempDir("", "tinygo")
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer os.RemoveAll(dir)
|
|
||||||
|
|
||||||
// Write the object file.
|
|
||||||
objfile := filepath.Join(dir, "main.o")
|
|
||||||
err = c.EmitObject(objfile)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Load builtins library from the cache, possibly compiling it on the
|
|
||||||
// fly.
|
|
||||||
var librt string
|
|
||||||
if spec.RTLib == "compiler-rt" {
|
|
||||||
librt, err = loadBuiltins(spec.Triple)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Prepare link command.
|
|
||||||
executable := filepath.Join(dir, "main")
|
|
||||||
tmppath := executable // final file
|
|
||||||
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 {
|
|
||||||
abspath := filepath.Join(root, path)
|
|
||||||
outpath := filepath.Join(dir, "extra-"+strconv.Itoa(i)+"-"+filepath.Base(path)+".o")
|
|
||||||
cmdNames := []string{spec.Compiler}
|
|
||||||
if names, ok := commands[spec.Compiler]; ok {
|
|
||||||
cmdNames = names
|
|
||||||
}
|
|
||||||
err := execCommand(cmdNames, append(cflags, "-c", "-o", outpath, abspath)...)
|
|
||||||
if err != nil {
|
|
||||||
return &commandError{"failed to build", path, err}
|
|
||||||
}
|
|
||||||
ldflags = append(ldflags, outpath)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Compile C files in packages.
|
|
||||||
for i, pkg := range c.Packages() {
|
|
||||||
for _, file := range pkg.CFiles {
|
|
||||||
path := filepath.Join(pkg.Package.Dir, file)
|
|
||||||
outpath := filepath.Join(dir, "pkg"+strconv.Itoa(i)+"-"+file+".o")
|
|
||||||
cmdNames := []string{spec.Compiler}
|
|
||||||
if names, ok := commands[spec.Compiler]; ok {
|
|
||||||
cmdNames = names
|
|
||||||
}
|
|
||||||
err := execCommand(cmdNames, append(cflags, "-c", "-o", outpath, path)...)
|
|
||||||
if err != nil {
|
|
||||||
return &commandError{"failed to build", path, err}
|
|
||||||
}
|
|
||||||
ldflags = append(ldflags, outpath)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Link the object files together.
|
|
||||||
err = Link(spec.Linker, ldflags...)
|
|
||||||
if err != nil {
|
|
||||||
return &commandError{"failed to link", executable, err}
|
|
||||||
}
|
|
||||||
|
|
||||||
if config.printSizes == "short" || config.printSizes == "full" {
|
|
||||||
sizes, err := Sizes(executable)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if config.printSizes == "short" {
|
|
||||||
fmt.Printf(" code data bss | flash ram\n")
|
|
||||||
fmt.Printf("%7d %7d %7d | %7d %7d\n", sizes.Code, sizes.Data, sizes.BSS, sizes.Code+sizes.Data, sizes.Data+sizes.BSS)
|
|
||||||
} else {
|
|
||||||
fmt.Printf(" code rodata data bss | flash ram | package\n")
|
|
||||||
for _, name := range sizes.SortedPackageNames() {
|
|
||||||
pkgSize := sizes.Packages[name]
|
|
||||||
fmt.Printf("%7d %7d %7d %7d | %7d %7d | %s\n", pkgSize.Code, pkgSize.ROData, pkgSize.Data, pkgSize.BSS, pkgSize.Flash(), pkgSize.RAM(), name)
|
|
||||||
}
|
|
||||||
fmt.Printf("%7d %7d %7d %7d | %7d %7d | (sum)\n", sizes.Sum.Code, sizes.Sum.ROData, sizes.Sum.Data, sizes.Sum.BSS, sizes.Sum.Flash(), sizes.Sum.RAM())
|
|
||||||
fmt.Printf("%7d - %7d %7d | %7d %7d | (all)\n", sizes.Code, sizes.Data, sizes.BSS, sizes.Code+sizes.Data, sizes.Data+sizes.BSS)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get an Intel .hex file or .bin file from the .elf file.
|
|
||||||
if outext == ".hex" || outext == ".bin" || outext == ".gba" {
|
|
||||||
tmppath = filepath.Join(dir, "main"+outext)
|
|
||||||
err := Objcopy(executable, tmppath)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
} else if outext == ".uf2" {
|
|
||||||
// Get UF2 from the .elf file.
|
|
||||||
tmppath = filepath.Join(dir, "main"+outext)
|
|
||||||
err := ConvertELFFileToUF2File(executable, tmppath)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return action(tmppath)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func Build(pkgName, outpath, target string, config *BuildConfig) error {
|
// Build compiles and links the given package and writes it to outpath.
|
||||||
spec, err := LoadTarget(target)
|
func Build(pkgName, outpath string, options *compileopts.Options) error {
|
||||||
|
config, err := builder.NewConfig(options)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
return Compile(pkgName, outpath, spec, config, func(tmppath string) error {
|
return builder.Build(pkgName, outpath, config, func(tmppath string) error {
|
||||||
if err := os.Rename(tmppath, outpath); err != nil {
|
if err := os.Rename(tmppath, outpath); err != nil {
|
||||||
// Moving failed. Do a file copy.
|
// Moving failed. Do a file copy.
|
||||||
inf, err := os.Open(tmppath)
|
inf, err := os.Open(tmppath)
|
||||||
@@ -368,15 +109,21 @@ func Build(pkgName, outpath, target string, config *BuildConfig) error {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func Test(pkgName, target string, config *BuildConfig) error {
|
// Test runs the tests in the given package.
|
||||||
spec, err := LoadTarget(target)
|
func Test(pkgName string, options *compileopts.Options) error {
|
||||||
|
config, err := builder.NewConfig(options)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
spec.BuildTags = append(spec.BuildTags, "test")
|
// Add test build tag. This is incorrect: `go test` only looks at the
|
||||||
config.testConfig.CompileTestBinary = true
|
// _test.go file suffix but does not add the test build tag in the process.
|
||||||
return Compile(pkgName, ".elf", spec, config, func(tmppath string) error {
|
// However, it's a simple fix right now.
|
||||||
|
// For details: https://github.com/golang/go/issues/21360
|
||||||
|
config.Target.BuildTags = append(config.Target.BuildTags, "test")
|
||||||
|
|
||||||
|
options.TestConfig.CompileTestBinary = true
|
||||||
|
return builder.Build(pkgName, ".elf", config, func(tmppath string) error {
|
||||||
cmd := exec.Command(tmppath)
|
cmd := exec.Command(tmppath)
|
||||||
cmd.Stdout = os.Stdout
|
cmd.Stdout = os.Stdout
|
||||||
cmd.Stderr = os.Stderr
|
cmd.Stderr = os.Stderr
|
||||||
@@ -395,8 +142,9 @@ func Test(pkgName, target string, config *BuildConfig) error {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func Flash(pkgName, target, port string, config *BuildConfig) error {
|
// Flash builds and flashes the built binary to the given serial port.
|
||||||
spec, err := LoadTarget(target)
|
func Flash(pkgName, port string, options *compileopts.Options) error {
|
||||||
|
config, err := builder.NewConfig(options)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -404,36 +152,37 @@ func Flash(pkgName, target, port string, config *BuildConfig) error {
|
|||||||
// determine the type of file to compile
|
// determine the type of file to compile
|
||||||
var fileExt string
|
var fileExt string
|
||||||
|
|
||||||
switch spec.FlashMethod {
|
flashMethod, _ := config.Programmer()
|
||||||
|
switch flashMethod {
|
||||||
case "command", "":
|
case "command", "":
|
||||||
switch {
|
switch {
|
||||||
case strings.Contains(spec.FlashCommand, "{hex}"):
|
case strings.Contains(config.Target.FlashCommand, "{hex}"):
|
||||||
fileExt = ".hex"
|
fileExt = ".hex"
|
||||||
case strings.Contains(spec.FlashCommand, "{elf}"):
|
case strings.Contains(config.Target.FlashCommand, "{elf}"):
|
||||||
fileExt = ".elf"
|
fileExt = ".elf"
|
||||||
case strings.Contains(spec.FlashCommand, "{bin}"):
|
case strings.Contains(config.Target.FlashCommand, "{bin}"):
|
||||||
fileExt = ".bin"
|
fileExt = ".bin"
|
||||||
case strings.Contains(spec.FlashCommand, "{uf2}"):
|
case strings.Contains(config.Target.FlashCommand, "{uf2}"):
|
||||||
fileExt = ".uf2"
|
fileExt = ".uf2"
|
||||||
default:
|
default:
|
||||||
return errors.New("invalid target file - did you forget the {hex} token in the 'flash-command' section?")
|
return errors.New("invalid target file - did you forget the {hex} token in the 'flash-command' section?")
|
||||||
}
|
}
|
||||||
case "msd":
|
case "msd":
|
||||||
if spec.FlashFilename == "" {
|
if config.Target.FlashFilename == "" {
|
||||||
return errors.New("invalid target file: flash-method was set to \"msd\" but no msd-firmware-name was set")
|
return errors.New("invalid target file: flash-method was set to \"msd\" but no msd-firmware-name was set")
|
||||||
}
|
}
|
||||||
fileExt = filepath.Ext(spec.FlashFilename)
|
fileExt = filepath.Ext(config.Target.FlashFilename)
|
||||||
case "openocd":
|
case "openocd":
|
||||||
fileExt = ".hex"
|
fileExt = ".hex"
|
||||||
case "native":
|
case "native":
|
||||||
return errors.New("unknown flash method \"native\" - did you miss a -target flag?")
|
return errors.New("unknown flash method \"native\" - did you miss a -target flag?")
|
||||||
default:
|
default:
|
||||||
return errors.New("unknown flash method: " + spec.FlashMethod)
|
return errors.New("unknown flash method: " + flashMethod)
|
||||||
}
|
}
|
||||||
|
|
||||||
return Compile(pkgName, fileExt, spec, config, func(tmppath string) error {
|
return builder.Build(pkgName, fileExt, config, func(tmppath string) error {
|
||||||
// do we need port reset to put MCU into bootloader mode?
|
// do we need port reset to put MCU into bootloader mode?
|
||||||
if spec.PortReset == "true" {
|
if config.Target.PortReset == "true" {
|
||||||
err := touchSerialPortAt1200bps(port)
|
err := touchSerialPortAt1200bps(port)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return &commandError{"failed to reset port", tmppath, err}
|
return &commandError{"failed to reset port", tmppath, err}
|
||||||
@@ -443,10 +192,10 @@ func Flash(pkgName, target, port string, config *BuildConfig) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// this flashing method copies the binary data to a Mass Storage Device (msd)
|
// this flashing method copies the binary data to a Mass Storage Device (msd)
|
||||||
switch spec.FlashMethod {
|
switch flashMethod {
|
||||||
case "", "command":
|
case "", "command":
|
||||||
// Create the command.
|
// Create the command.
|
||||||
flashCmd := spec.FlashCommand
|
flashCmd := config.Target.FlashCommand
|
||||||
fileToken := "{" + fileExt[1:] + "}"
|
fileToken := "{" + fileExt[1:] + "}"
|
||||||
flashCmd = strings.Replace(flashCmd, fileToken, tmppath, -1)
|
flashCmd = strings.Replace(flashCmd, fileToken, tmppath, -1)
|
||||||
flashCmd = strings.Replace(flashCmd, "{port}", port, -1)
|
flashCmd = strings.Replace(flashCmd, "{port}", port, -1)
|
||||||
@@ -464,13 +213,13 @@ func Flash(pkgName, target, port string, config *BuildConfig) error {
|
|||||||
case "msd":
|
case "msd":
|
||||||
switch fileExt {
|
switch fileExt {
|
||||||
case ".uf2":
|
case ".uf2":
|
||||||
err := flashUF2UsingMSD(spec.FlashVolume, tmppath)
|
err := flashUF2UsingMSD(config.Target.FlashVolume, tmppath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return &commandError{"failed to flash", tmppath, err}
|
return &commandError{"failed to flash", tmppath, err}
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
case ".hex":
|
case ".hex":
|
||||||
err := flashHexUsingMSD(spec.FlashVolume, tmppath)
|
err := flashHexUsingMSD(config.Target.FlashVolume, tmppath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return &commandError{"failed to flash", tmppath, err}
|
return &commandError{"failed to flash", tmppath, err}
|
||||||
}
|
}
|
||||||
@@ -479,7 +228,7 @@ func Flash(pkgName, target, port string, config *BuildConfig) error {
|
|||||||
return errors.New("mass storage device flashing currently only supports uf2 and hex")
|
return errors.New("mass storage device flashing currently only supports uf2 and hex")
|
||||||
}
|
}
|
||||||
case "openocd":
|
case "openocd":
|
||||||
args, err := spec.OpenOCDConfiguration()
|
args, err := config.OpenOCDConfiguration()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -493,34 +242,36 @@ func Flash(pkgName, target, port string, config *BuildConfig) error {
|
|||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
default:
|
default:
|
||||||
return fmt.Errorf("unknown flash method: %s", spec.FlashMethod)
|
return fmt.Errorf("unknown flash method: %s", flashMethod)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// Flash a program on a microcontroller and drop into a GDB shell.
|
// FlashGDB compiles and flashes a program to a microcontroller (just like
|
||||||
|
// Flash) but instead of resetting the target, it will drop into a GDB shell.
|
||||||
|
// You can then set breakpoints, run the GDB `continue` command to start, hit
|
||||||
|
// Ctrl+C to break the running program, etc.
|
||||||
//
|
//
|
||||||
// Note: this command is expected to execute just before exiting, as it
|
// Note: this command is expected to execute just before exiting, as it
|
||||||
// modifies global state.
|
// modifies global state.
|
||||||
func FlashGDB(pkgName, target, port string, ocdOutput bool, config *BuildConfig) error {
|
func FlashGDB(pkgName, port string, ocdOutput bool, options *compileopts.Options) error {
|
||||||
spec, err := LoadTarget(target)
|
config, err := builder.NewConfig(options)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
if config.Target.GDB == "" {
|
||||||
if spec.GDB == "" {
|
|
||||||
return errors.New("gdb not configured in the target specification")
|
return errors.New("gdb not configured in the target specification")
|
||||||
}
|
}
|
||||||
|
|
||||||
return Compile(pkgName, "", spec, config, func(tmppath string) error {
|
return builder.Build(pkgName, "", config, func(tmppath string) error {
|
||||||
// Find a good way to run GDB.
|
// Find a good way to run GDB.
|
||||||
gdbInterface := spec.FlashMethod
|
gdbInterface, openocdInterface := config.Programmer()
|
||||||
switch gdbInterface {
|
switch gdbInterface {
|
||||||
case "msd", "command", "":
|
case "msd", "command", "":
|
||||||
if gdbInterface == "" {
|
if gdbInterface == "" {
|
||||||
gdbInterface = "command"
|
gdbInterface = "command"
|
||||||
}
|
}
|
||||||
if spec.OpenOCDInterface != "" && spec.OpenOCDTarget != "" {
|
if openocdInterface != "" && config.Target.OpenOCDTarget != "" {
|
||||||
gdbInterface = "openocd"
|
gdbInterface = "openocd"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -530,12 +281,11 @@ func FlashGDB(pkgName, target, port string, ocdOutput bool, config *BuildConfig)
|
|||||||
switch gdbInterface {
|
switch gdbInterface {
|
||||||
case "native":
|
case "native":
|
||||||
// Run GDB directly.
|
// Run GDB directly.
|
||||||
gdbCommands = append(gdbCommands, "run")
|
|
||||||
case "openocd":
|
case "openocd":
|
||||||
gdbCommands = append(gdbCommands, "target remote :3333", "monitor halt", "load", "monitor reset", "c")
|
gdbCommands = append(gdbCommands, "target remote :3333", "monitor halt", "load", "monitor reset halt")
|
||||||
|
|
||||||
// We need a separate debugging daemon for on-chip debugging.
|
// We need a separate debugging daemon for on-chip debugging.
|
||||||
args, err := spec.OpenOCDConfiguration()
|
args, err := config.OpenOCDConfiguration()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -544,7 +294,7 @@ func FlashGDB(pkgName, target, port string, ocdOutput bool, config *BuildConfig)
|
|||||||
// Make it clear which output is from the daemon.
|
// Make it clear which output is from the daemon.
|
||||||
w := &ColorWriter{
|
w := &ColorWriter{
|
||||||
Out: os.Stderr,
|
Out: os.Stderr,
|
||||||
Prefix: spec.OCDDaemon[0] + ": ",
|
Prefix: "openocd: ",
|
||||||
Color: TermColorYellow,
|
Color: TermColorYellow,
|
||||||
}
|
}
|
||||||
daemon.Stdout = w
|
daemon.Stdout = w
|
||||||
@@ -581,7 +331,7 @@ func FlashGDB(pkgName, target, port string, ocdOutput bool, config *BuildConfig)
|
|||||||
for _, cmd := range gdbCommands {
|
for _, cmd := range gdbCommands {
|
||||||
params = append(params, "-ex", cmd)
|
params = append(params, "-ex", cmd)
|
||||||
}
|
}
|
||||||
cmd := exec.Command(spec.GDB, params...)
|
cmd := exec.Command(config.Target.GDB, params...)
|
||||||
cmd.Stdin = os.Stdin
|
cmd.Stdin = os.Stdin
|
||||||
cmd.Stdout = os.Stdout
|
cmd.Stdout = os.Stdout
|
||||||
cmd.Stderr = os.Stderr
|
cmd.Stderr = os.Stderr
|
||||||
@@ -593,15 +343,18 @@ func FlashGDB(pkgName, target, port string, ocdOutput bool, config *BuildConfig)
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// Compile and run the given program, directly or in an emulator.
|
// Run compiles and runs the given program. Depending on the target provided in
|
||||||
func Run(pkgName, target string, config *BuildConfig) error {
|
// the options, it will run the program directly on the host or will run it in
|
||||||
spec, err := LoadTarget(target)
|
// an emulator. For example, -target=wasm will cause the binary to be run inside
|
||||||
|
// of a WebAssembly VM.
|
||||||
|
func Run(pkgName string, options *compileopts.Options) error {
|
||||||
|
config, err := builder.NewConfig(options)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
return Compile(pkgName, ".elf", spec, config, func(tmppath string) error {
|
return builder.Build(pkgName, ".elf", config, func(tmppath string) error {
|
||||||
if len(spec.Emulator) == 0 {
|
if len(config.Target.Emulator) == 0 {
|
||||||
// Run directly.
|
// Run directly.
|
||||||
cmd := exec.Command(tmppath)
|
cmd := exec.Command(tmppath)
|
||||||
cmd.Stdout = os.Stdout
|
cmd.Stdout = os.Stdout
|
||||||
@@ -617,8 +370,8 @@ func Run(pkgName, target string, config *BuildConfig) error {
|
|||||||
return nil
|
return nil
|
||||||
} else {
|
} else {
|
||||||
// Run in an emulator.
|
// Run in an emulator.
|
||||||
args := append(spec.Emulator[1:], tmppath)
|
args := append(config.Target.Emulator[1:], tmppath)
|
||||||
cmd := exec.Command(spec.Emulator[0], args...)
|
cmd := exec.Command(config.Target.Emulator[0], args...)
|
||||||
cmd.Stdout = os.Stdout
|
cmd.Stdout = os.Stdout
|
||||||
cmd.Stderr = os.Stderr
|
cmd.Stderr = os.Stderr
|
||||||
err := cmd.Run()
|
err := cmd.Run()
|
||||||
@@ -728,17 +481,27 @@ func handleCompilerError(err error) {
|
|||||||
switch err := err.(type) {
|
switch err := err.(type) {
|
||||||
case *interp.Unsupported:
|
case *interp.Unsupported:
|
||||||
// hit an unknown/unsupported instruction
|
// hit an unknown/unsupported instruction
|
||||||
fmt.Fprintln(os.Stderr, "unsupported instruction during init evaluation:")
|
fmt.Fprintln(os.Stderr, "#", err.ImportPath)
|
||||||
|
msg := "unsupported instruction during init evaluation:"
|
||||||
|
if err.Pos.String() != "" {
|
||||||
|
msg = err.Pos.String() + " " + msg
|
||||||
|
}
|
||||||
|
fmt.Fprintln(os.Stderr, msg)
|
||||||
err.Inst.Dump()
|
err.Inst.Dump()
|
||||||
fmt.Fprintln(os.Stderr)
|
fmt.Fprintln(os.Stderr)
|
||||||
case types.Error:
|
case types.Error, scanner.Error:
|
||||||
fmt.Fprintln(os.Stderr, err)
|
fmt.Fprintln(os.Stderr, err)
|
||||||
|
case interp.Error:
|
||||||
|
fmt.Fprintln(os.Stderr, "#", err.ImportPath)
|
||||||
|
for _, err := range err.Errs {
|
||||||
|
fmt.Fprintln(os.Stderr, err)
|
||||||
|
}
|
||||||
case loader.Errors:
|
case loader.Errors:
|
||||||
fmt.Fprintln(os.Stderr, "#", err.Pkg.ImportPath)
|
fmt.Fprintln(os.Stderr, "#", err.Pkg.ImportPath)
|
||||||
for _, err := range err.Errs {
|
for _, err := range err.Errs {
|
||||||
fmt.Fprintln(os.Stderr, err)
|
fmt.Fprintln(os.Stderr, err)
|
||||||
}
|
}
|
||||||
case *multiError:
|
case *builder.MultiError:
|
||||||
for _, err := range err.Errs {
|
for _, err := range err.Errs {
|
||||||
fmt.Fprintln(os.Stderr, err)
|
fmt.Fprintln(os.Stderr, err)
|
||||||
}
|
}
|
||||||
@@ -764,6 +527,7 @@ func main() {
|
|||||||
nodebug := flag.Bool("no-debug", false, "disable DWARF debug symbol generation")
|
nodebug := flag.Bool("no-debug", false, "disable DWARF debug symbol generation")
|
||||||
ocdOutput := flag.Bool("ocd-output", false, "print OCD daemon output during debug")
|
ocdOutput := flag.Bool("ocd-output", false, "print OCD daemon output during debug")
|
||||||
port := flag.String("port", "/dev/ttyACM0", "flash port")
|
port := flag.String("port", "/dev/ttyACM0", "flash port")
|
||||||
|
programmer := flag.String("programmer", "", "which hardware programmer to use")
|
||||||
cFlags := flag.String("cflags", "", "additional cflags for compiler")
|
cFlags := flag.String("cflags", "", "additional cflags for compiler")
|
||||||
ldFlags := flag.String("ldflags", "", "additional ldflags for linker")
|
ldFlags := flag.String("ldflags", "", "additional ldflags for linker")
|
||||||
wasmAbi := flag.String("wasm-abi", "js", "WebAssembly ABI conventions: js (no i64 params) or generic")
|
wasmAbi := flag.String("wasm-abi", "js", "WebAssembly ABI conventions: js (no i64 params) or generic")
|
||||||
@@ -777,26 +541,28 @@ func main() {
|
|||||||
command := os.Args[1]
|
command := os.Args[1]
|
||||||
|
|
||||||
flag.CommandLine.Parse(os.Args[2:])
|
flag.CommandLine.Parse(os.Args[2:])
|
||||||
config := &BuildConfig{
|
options := &compileopts.Options{
|
||||||
opt: *opt,
|
Target: *target,
|
||||||
gc: *gc,
|
Opt: *opt,
|
||||||
panicStrategy: *panicStrategy,
|
GC: *gc,
|
||||||
scheduler: *scheduler,
|
PanicStrategy: *panicStrategy,
|
||||||
printIR: *printIR,
|
Scheduler: *scheduler,
|
||||||
dumpSSA: *dumpSSA,
|
PrintIR: *printIR,
|
||||||
verifyIR: *verifyIR,
|
DumpSSA: *dumpSSA,
|
||||||
debug: !*nodebug,
|
VerifyIR: *verifyIR,
|
||||||
printSizes: *printSize,
|
Debug: !*nodebug,
|
||||||
tags: *tags,
|
PrintSizes: *printSize,
|
||||||
wasmAbi: *wasmAbi,
|
Tags: *tags,
|
||||||
|
WasmAbi: *wasmAbi,
|
||||||
|
Programmer: *programmer,
|
||||||
}
|
}
|
||||||
|
|
||||||
if *cFlags != "" {
|
if *cFlags != "" {
|
||||||
config.cFlags = strings.Split(*cFlags, " ")
|
options.CFlags = strings.Split(*cFlags, " ")
|
||||||
}
|
}
|
||||||
|
|
||||||
if *ldFlags != "" {
|
if *ldFlags != "" {
|
||||||
config.ldFlags = strings.Split(*ldFlags, " ")
|
options.LDFlags = strings.Split(*ldFlags, " ")
|
||||||
}
|
}
|
||||||
|
|
||||||
if *panicStrategy != "print" && *panicStrategy != "trap" {
|
if *panicStrategy != "print" && *panicStrategy != "trap" {
|
||||||
@@ -806,7 +572,7 @@ func main() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var err error
|
var err error
|
||||||
if config.heapSize, err = parseSize(*heapSize); err != nil {
|
if options.HeapSize, err = parseSize(*heapSize); err != nil {
|
||||||
fmt.Fprintln(os.Stderr, "Could not read heap size:", *heapSize)
|
fmt.Fprintln(os.Stderr, "Could not read heap size:", *heapSize)
|
||||||
usage()
|
usage()
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
@@ -829,11 +595,10 @@ func main() {
|
|||||||
usage()
|
usage()
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
target := *target
|
if options.Target == "" && filepath.Ext(*outpath) == ".wasm" {
|
||||||
if target == "" && filepath.Ext(*outpath) == ".wasm" {
|
options.Target = "wasm"
|
||||||
target = "wasm"
|
|
||||||
}
|
}
|
||||||
err := Build(pkgName, *outpath, target, config)
|
err := Build(pkgName, *outpath, options)
|
||||||
handleCompilerError(err)
|
handleCompilerError(err)
|
||||||
case "build-builtins":
|
case "build-builtins":
|
||||||
// Note: this command is only meant to be used while making a release!
|
// Note: this command is only meant to be used while making a release!
|
||||||
@@ -845,7 +610,7 @@ func main() {
|
|||||||
if *target == "" {
|
if *target == "" {
|
||||||
fmt.Fprintln(os.Stderr, "No target (-target).")
|
fmt.Fprintln(os.Stderr, "No target (-target).")
|
||||||
}
|
}
|
||||||
err := compileBuiltins(*target, func(path string) error {
|
err := builder.CompileBuiltins(*target, func(path string) error {
|
||||||
return moveFile(path, *outpath)
|
return moveFile(path, *outpath)
|
||||||
})
|
})
|
||||||
handleCompilerError(err)
|
handleCompilerError(err)
|
||||||
@@ -856,15 +621,15 @@ func main() {
|
|||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
if command == "flash" {
|
if command == "flash" {
|
||||||
err := Flash(flag.Arg(0), *target, *port, config)
|
err := Flash(flag.Arg(0), *port, options)
|
||||||
handleCompilerError(err)
|
handleCompilerError(err)
|
||||||
} else {
|
} else {
|
||||||
if !config.debug {
|
if !options.Debug {
|
||||||
fmt.Fprintln(os.Stderr, "Debug disabled while running gdb?")
|
fmt.Fprintln(os.Stderr, "Debug disabled while running gdb?")
|
||||||
usage()
|
usage()
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
err := FlashGDB(flag.Arg(0), *target, *port, *ocdOutput, config)
|
err := FlashGDB(flag.Arg(0), *port, *ocdOutput, options)
|
||||||
handleCompilerError(err)
|
handleCompilerError(err)
|
||||||
}
|
}
|
||||||
case "run":
|
case "run":
|
||||||
@@ -873,7 +638,7 @@ func main() {
|
|||||||
usage()
|
usage()
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
err := Run(flag.Arg(0), *target, config)
|
err := Run(flag.Arg(0), options)
|
||||||
handleCompilerError(err)
|
handleCompilerError(err)
|
||||||
case "test":
|
case "test":
|
||||||
pkgName := "."
|
pkgName := "."
|
||||||
@@ -884,8 +649,33 @@ func main() {
|
|||||||
usage()
|
usage()
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
err := Test(pkgName, *target, config)
|
err := Test(pkgName, options)
|
||||||
handleCompilerError(err)
|
handleCompilerError(err)
|
||||||
|
case "info":
|
||||||
|
if flag.NArg() == 1 {
|
||||||
|
options.Target = flag.Arg(0)
|
||||||
|
} else if flag.NArg() > 1 {
|
||||||
|
fmt.Fprintln(os.Stderr, "only one target name is accepted")
|
||||||
|
usage()
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
config, err := builder.NewConfig(options)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Fprintln(os.Stderr, err)
|
||||||
|
usage()
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
config.GoMinorVersion = 0 // this avoids creating the list of Go1.x build tags.
|
||||||
|
if err != nil {
|
||||||
|
fmt.Fprintln(os.Stderr, err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
fmt.Printf("LLVM triple: %s\n", config.Triple())
|
||||||
|
fmt.Printf("GOOS: %s\n", config.GOOS())
|
||||||
|
fmt.Printf("GOARCH: %s\n", config.GOARCH())
|
||||||
|
fmt.Printf("build tags: %s\n", strings.Join(config.BuildTags(), " "))
|
||||||
|
fmt.Printf("garbage collector: %s\n", config.GC())
|
||||||
|
fmt.Printf("scheduler: %s\n", config.Scheduler())
|
||||||
case "clean":
|
case "clean":
|
||||||
// remove cache directory
|
// remove cache directory
|
||||||
err := os.RemoveAll(goenv.Get("GOCACHE"))
|
err := os.RemoveAll(goenv.Get("GOCACHE"))
|
||||||
@@ -897,7 +687,7 @@ func main() {
|
|||||||
usage()
|
usage()
|
||||||
case "version":
|
case "version":
|
||||||
goversion := "<unknown>"
|
goversion := "<unknown>"
|
||||||
if s, err := getGorootVersionString(goenv.Get("GOROOT")); err == nil {
|
if s, err := builder.GorootVersionString(goenv.Get("GOROOT")); err == nil {
|
||||||
goversion = s
|
goversion = s
|
||||||
}
|
}
|
||||||
fmt.Printf("tinygo version %s %s/%s (using go version %s)\n", version, runtime.GOOS, runtime.GOARCH, goversion)
|
fmt.Printf("tinygo version %s %s/%s (using go version %s)\n", version, runtime.GOOS, runtime.GOARCH, goversion)
|
||||||
|
|||||||
+65
-59
@@ -14,6 +14,7 @@ import (
|
|||||||
"sort"
|
"sort"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/tinygo-org/tinygo/compileopts"
|
||||||
"github.com/tinygo-org/tinygo/loader"
|
"github.com/tinygo-org/tinygo/loader"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -38,67 +39,59 @@ func TestCompiler(t *testing.T) {
|
|||||||
|
|
||||||
sort.Strings(matches)
|
sort.Strings(matches)
|
||||||
|
|
||||||
// Create a temporary directory for test output files.
|
|
||||||
tmpdir, err := ioutil.TempDir("", "tinygo-test")
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal("could not create temporary directory:", err)
|
|
||||||
}
|
|
||||||
defer os.RemoveAll(tmpdir)
|
|
||||||
|
|
||||||
if runtime.GOOS != "windows" {
|
if runtime.GOOS != "windows" {
|
||||||
t.Log("running tests on host...")
|
t.Run("Host", func(t *testing.T) {
|
||||||
for _, path := range matches {
|
runPlatTests("", matches, t)
|
||||||
t.Run(path, func(t *testing.T) {
|
})
|
||||||
runTest(path, tmpdir, "", t)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if testing.Short() {
|
if testing.Short() {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
t.Log("running tests for emulated cortex-m3...")
|
t.Run("EmulatedCortexM3", func(t *testing.T) {
|
||||||
for _, path := range matches {
|
runPlatTests("qemu", matches, t)
|
||||||
t.Run(path, func(t *testing.T) {
|
})
|
||||||
runTest(path, tmpdir, "qemu", t)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
if runtime.GOOS == "linux" {
|
if runtime.GOOS == "linux" {
|
||||||
t.Log("running tests for linux/arm...")
|
t.Run("ARMLinux", func(t *testing.T) {
|
||||||
for _, path := range matches {
|
runPlatTests("arm--linux-gnueabihf", matches, t)
|
||||||
if path == filepath.Join("testdata", "cgo")+string(filepath.Separator) {
|
})
|
||||||
continue // TODO: improve CGo
|
t.Run("ARM64Linux", func(t *testing.T) {
|
||||||
}
|
runPlatTests("aarch64--linux-gnu", matches, t)
|
||||||
t.Run(path, func(t *testing.T) {
|
})
|
||||||
runTest(path, tmpdir, "arm--linux-gnueabihf", t)
|
t.Run("WebAssembly", func(t *testing.T) {
|
||||||
})
|
runPlatTests("wasm", matches, t)
|
||||||
}
|
})
|
||||||
|
|
||||||
t.Log("running tests for linux/arm64...")
|
|
||||||
for _, path := range matches {
|
|
||||||
if path == filepath.Join("testdata", "cgo")+string(filepath.Separator) {
|
|
||||||
continue // TODO: improve CGo
|
|
||||||
}
|
|
||||||
t.Run(path, func(t *testing.T) {
|
|
||||||
runTest(path, tmpdir, "aarch64--linux-gnu", t)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
t.Log("running tests for WebAssembly...")
|
|
||||||
for _, path := range matches {
|
|
||||||
if path == filepath.Join("testdata", "gc.go") {
|
|
||||||
continue // known to fail
|
|
||||||
}
|
|
||||||
t.Run(path, func(t *testing.T) {
|
|
||||||
runTest(path, tmpdir, "wasm", t)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func runTest(path, tmpdir string, target string, t *testing.T) {
|
func runPlatTests(target string, matches []string, t *testing.T) {
|
||||||
|
for _, path := range matches {
|
||||||
|
switch {
|
||||||
|
case target == "wasm":
|
||||||
|
// testdata/gc.go is known not to work on WebAssembly
|
||||||
|
if path == filepath.Join("testdata", "gc.go") {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
case target == "":
|
||||||
|
// run all tests on host
|
||||||
|
case target == "qemu":
|
||||||
|
// all tests are supported
|
||||||
|
default:
|
||||||
|
// cross-compilation of cgo is not yet supported
|
||||||
|
if path == filepath.Join("testdata", "cgo")+string(filepath.Separator) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Run(filepath.Base(path), func(t *testing.T) {
|
||||||
|
runTest(path, target, t)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func runTest(path, target string, t *testing.T) {
|
||||||
// Get the expected output for this test.
|
// Get the expected output for this test.
|
||||||
txtpath := path[:len(path)-3] + ".txt"
|
txtpath := path[:len(path)-3] + ".txt"
|
||||||
if path[len(path)-1] == os.PathSeparator {
|
if path[len(path)-1] == os.PathSeparator {
|
||||||
@@ -113,18 +106,31 @@ func runTest(path, tmpdir string, target string, t *testing.T) {
|
|||||||
t.Fatal("could not read expected output file:", err)
|
t.Fatal("could not read expected output file:", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Create a temporary directory for test output files.
|
||||||
|
tmpdir, err := ioutil.TempDir("", "tinygo-test")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal("could not create temporary directory:", err)
|
||||||
|
}
|
||||||
|
defer func() {
|
||||||
|
rerr := os.RemoveAll(tmpdir)
|
||||||
|
if rerr != nil {
|
||||||
|
t.Errorf("failed to remove temporary directory %q: %s", tmpdir, rerr.Error())
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
// Build the test binary.
|
// Build the test binary.
|
||||||
config := &BuildConfig{
|
config := &compileopts.Options{
|
||||||
opt: "z",
|
Target: target,
|
||||||
printIR: false,
|
Opt: "z",
|
||||||
dumpSSA: false,
|
PrintIR: false,
|
||||||
verifyIR: true,
|
DumpSSA: false,
|
||||||
debug: false,
|
VerifyIR: true,
|
||||||
printSizes: "",
|
Debug: false,
|
||||||
wasmAbi: "js",
|
PrintSizes: "",
|
||||||
|
WasmAbi: "js",
|
||||||
}
|
}
|
||||||
binary := filepath.Join(tmpdir, "test")
|
binary := filepath.Join(tmpdir, "test")
|
||||||
err = Build("./"+path, binary, target, config)
|
err = Build("./"+path, binary, config)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if errLoader, ok := err.(loader.Errors); ok {
|
if errLoader, ok := err.(loader.Errors); ok {
|
||||||
for _, err := range errLoader.Errs {
|
for _, err := range errLoader.Errs {
|
||||||
@@ -142,7 +148,7 @@ func runTest(path, tmpdir string, target string, t *testing.T) {
|
|||||||
if target == "" {
|
if target == "" {
|
||||||
cmd = exec.Command(binary)
|
cmd = exec.Command(binary)
|
||||||
} else {
|
} else {
|
||||||
spec, err := LoadTarget(target)
|
spec, err := compileopts.LoadTarget(target)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal("failed to load target spec:", err)
|
t.Fatal("failed to load target spec:", err)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -30,6 +30,7 @@
|
|||||||
package arm
|
package arm
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"errors"
|
||||||
"runtime/volatile"
|
"runtime/volatile"
|
||||||
"unsafe"
|
"unsafe"
|
||||||
)
|
)
|
||||||
@@ -72,6 +73,7 @@ func SVCall4(num uintptr, a1, a2, a3, a4 interface{}) uintptr
|
|||||||
|
|
||||||
const (
|
const (
|
||||||
SCS_BASE = 0xE000E000
|
SCS_BASE = 0xE000E000
|
||||||
|
SYST_BASE = SCS_BASE + 0x0010
|
||||||
NVIC_BASE = SCS_BASE + 0x0100
|
NVIC_BASE = SCS_BASE + 0x0100
|
||||||
SCB_BASE = SCS_BASE + 0x0D00
|
SCB_BASE = SCS_BASE + 0x0D00
|
||||||
)
|
)
|
||||||
@@ -119,6 +121,53 @@ type NVIC_Type struct {
|
|||||||
|
|
||||||
var NVIC = (*NVIC_Type)(unsafe.Pointer(uintptr(NVIC_BASE)))
|
var NVIC = (*NVIC_Type)(unsafe.Pointer(uintptr(NVIC_BASE)))
|
||||||
|
|
||||||
|
// System Timer (SYST)
|
||||||
|
//
|
||||||
|
// Source: https://static.docs.arm.com/ddi0403/e/DDI0403E_d_armv7m_arm.pdf B3.3
|
||||||
|
type SYST_Type struct {
|
||||||
|
SYST_CSR volatile.Register32
|
||||||
|
SYST_RVR volatile.Register32
|
||||||
|
SYST_CVR volatile.Register32
|
||||||
|
SYST_CALIB volatile.Register32
|
||||||
|
}
|
||||||
|
|
||||||
|
var SYST = (*SYST_Type)(unsafe.Pointer(uintptr(SYST_BASE)))
|
||||||
|
|
||||||
|
// Bitfields for SYST: System Timer
|
||||||
|
const (
|
||||||
|
// SYST.SYST_CSR: SysTick Control and Status Register
|
||||||
|
SYST_CSR_ENABLE_Pos = 0x0 // Position of ENABLE field.
|
||||||
|
SYST_CSR_ENABLE_Msk = 0x1 // Bit mask of ENABLE field.
|
||||||
|
SYST_CSR_ENABLE = 0x1 // Bit ENABLE.
|
||||||
|
SYST_CSR_TICKINT_Pos = 0x1 // Position of TICKINT field.
|
||||||
|
SYST_CSR_TICKINT_Msk = 0x2 // Bit mask of TICKINT field.
|
||||||
|
SYST_CSR_TICKINT = 0x2 // Bit TICKINT.
|
||||||
|
SYST_CSR_CLKSOURCE_Pos = 0x2 // Position of CLKSOURCE field.
|
||||||
|
SYST_CSR_CLKSOURCE_Msk = 0x4 // Bit mask of CLKSOURCE field.
|
||||||
|
SYST_CSR_CLKSOURCE = 0x4 // Bit CLKSOURCE.
|
||||||
|
SYST_CSR_COUNTFLAG_Pos = 0x10 // Position of COUNTFLAG field.
|
||||||
|
SYST_CSR_COUNTFLAG_Msk = 0x10000 // Bit mask of COUNTFLAG field.
|
||||||
|
SYST_CSR_COUNTFLAG = 0x10000 // Bit COUNTFLAG.
|
||||||
|
|
||||||
|
// SYST.SYST_RVR: SysTick Reload Value Register
|
||||||
|
SYST_RVR_RELOAD_Pos = 0x0 // Position of RELOAD field.
|
||||||
|
SYST_RVR_RELOAD_Msk = 0xffffff // Bit mask of RELOAD field.
|
||||||
|
|
||||||
|
// SYST.SYST_CVR: SysTick Current Value Register
|
||||||
|
SYST_CVR_CURRENT_Pos = 0x0 // Position of CURRENT field.
|
||||||
|
SYST_CVR_CURRENT_Msk = 0xffffff // Bit mask of CURRENT field.
|
||||||
|
|
||||||
|
// SYST.SYST_CALIB: SysTick Calibration Value Register
|
||||||
|
SYST_CALIB_TENMS_Pos = 0x0 // Position of TENMS field.
|
||||||
|
SYST_CALIB_TENMS_Msk = 0xffffff // Bit mask of TENMS field.
|
||||||
|
SYST_CALIB_SKEW_Pos = 0x1e // Position of SKEW field.
|
||||||
|
SYST_CALIB_SKEW_Msk = 0x40000000 // Bit mask of SKEW field.
|
||||||
|
SYST_CALIB_SKEW = 0x40000000 // Bit SKEW.
|
||||||
|
SYST_CALIB_NOREF_Pos = 0x1f // Position of NOREF field.
|
||||||
|
SYST_CALIB_NOREF_Msk = 0x80000000 // Bit mask of NOREF field.
|
||||||
|
SYST_CALIB_NOREF = 0x80000000 // Bit NOREF.
|
||||||
|
)
|
||||||
|
|
||||||
// Enable the given interrupt number.
|
// Enable the given interrupt number.
|
||||||
func EnableIRQ(irq uint32) {
|
func EnableIRQ(irq uint32) {
|
||||||
NVIC.ISER[irq>>5].Set(1 << (irq & 0x1F))
|
NVIC.ISER[irq>>5].Set(1 << (irq & 0x1F))
|
||||||
@@ -169,3 +218,28 @@ func SystemReset() {
|
|||||||
Asm("wfi")
|
Asm("wfi")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Set up the system timer to generate periodic tick events.
|
||||||
|
// This will cause SysTick_Handler to fire once per tick.
|
||||||
|
// The cyclecount parameter is a counter value which can range from 0 to
|
||||||
|
// 0xffffff. A value of 0 disables the timer.
|
||||||
|
func SetupSystemTimer(cyclecount uint32) error {
|
||||||
|
// turn it off
|
||||||
|
SYST.SYST_CSR.ClearBits(SYST_CSR_TICKINT | SYST_CSR_ENABLE)
|
||||||
|
if cyclecount == 0 {
|
||||||
|
// leave the system timer turned off.
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if cyclecount&SYST_RVR_RELOAD_Msk != cyclecount {
|
||||||
|
// The cycle refresh register is only 24 bits wide. The user-specified value will overflow.
|
||||||
|
return errors.New("requested cycle count is too large, overflows 24 bit counter")
|
||||||
|
}
|
||||||
|
|
||||||
|
// set refresh count
|
||||||
|
SYST.SYST_RVR.Set(cyclecount)
|
||||||
|
// set current counter value
|
||||||
|
SYST.SYST_CVR.Set(cyclecount)
|
||||||
|
// enable clock, enable SysTick interrupt when clock reaches 0, run it off of the processor clock
|
||||||
|
SYST.SYST_CSR.SetBits(SYST_CSR_TICKINT | SYST_CSR_ENABLE | SYST_CSR_CLKSOURCE)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|||||||
@@ -19,3 +19,12 @@ HardFault_Handler:
|
|||||||
|
|
||||||
// Continue handling this error in Go.
|
// Continue handling this error in Go.
|
||||||
bl handleHardFault
|
bl handleHardFault
|
||||||
|
|
||||||
|
// This is a convenience function for semihosting support.
|
||||||
|
// At some point, this should be replaced by inline assembly.
|
||||||
|
.section .text.SemihostingCall
|
||||||
|
.global SemihostingCall
|
||||||
|
.type SemihostingCall, %function
|
||||||
|
SemihostingCall:
|
||||||
|
bkpt 0xab
|
||||||
|
bx lr
|
||||||
|
|||||||
@@ -0,0 +1,12 @@
|
|||||||
|
# TinyGo ARM SysTick example
|
||||||
|
|
||||||
|
This example uses the ARM System Timer to blink an LED. The timer fires
|
||||||
|
an interrupt 10 times per second. The interrupt handler toggles the LED on
|
||||||
|
and off.
|
||||||
|
|
||||||
|
Many ARM-based chips have this timer feature. If you run the example and the
|
||||||
|
LED blinks, then you have one.
|
||||||
|
|
||||||
|
The System Timer runs from a cycle counter. The more cycles, the slower the
|
||||||
|
LED will blink. This counter is 24 bits wide, which places an upper bound on
|
||||||
|
the number of cycles, and the slowness of the blinking.
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"device/arm"
|
||||||
|
"machine"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
machine.LED.Configure(machine.PinConfig{Mode: machine.PinOutput})
|
||||||
|
|
||||||
|
// timer fires 10 times per second
|
||||||
|
arm.SetupSystemTimer(machine.CPU_FREQUENCY / 10)
|
||||||
|
|
||||||
|
for {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var led_state bool
|
||||||
|
|
||||||
|
//go:export SysTick_Handler
|
||||||
|
func timer_isr() {
|
||||||
|
if led_state {
|
||||||
|
machine.LED.Low()
|
||||||
|
} else {
|
||||||
|
machine.LED.High()
|
||||||
|
}
|
||||||
|
led_state = !led_state
|
||||||
|
}
|
||||||
@@ -48,18 +48,6 @@ const (
|
|||||||
LED = D13
|
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
|
// UART0 aka USBCDC pins
|
||||||
const (
|
const (
|
||||||
USBCDC_DM_PIN Pin = PA24
|
USBCDC_DM_PIN Pin = PA24
|
||||||
@@ -131,6 +119,28 @@ var (
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// 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
|
||||||
|
NINA_TX Pin = PA22
|
||||||
|
NINA_RX Pin = PA23
|
||||||
|
)
|
||||||
|
|
||||||
|
// SPI1 is connected to the NINA-W102 chip on the Arduino Nano 33.
|
||||||
|
var (
|
||||||
|
SPI1 = SPI{
|
||||||
|
Bus: sam.SERCOM2_SPI,
|
||||||
|
SERCOM: 2,
|
||||||
|
}
|
||||||
|
NINA_SPI = SPI1
|
||||||
|
)
|
||||||
|
|
||||||
// I2S pins
|
// I2S pins
|
||||||
const (
|
const (
|
||||||
I2S_SCK_PIN Pin = PA10
|
I2S_SCK_PIN Pin = PA10
|
||||||
|
|||||||
@@ -0,0 +1,95 @@
|
|||||||
|
// +build sam,atsamd51,feather_m4
|
||||||
|
|
||||||
|
package machine
|
||||||
|
|
||||||
|
import "device/sam"
|
||||||
|
|
||||||
|
// used to reset into bootloader
|
||||||
|
const RESET_MAGIC_VALUE = 0xf01669ef
|
||||||
|
|
||||||
|
// GPIO Pins
|
||||||
|
const (
|
||||||
|
D0 = PB17 // UART0 RX/PWM available
|
||||||
|
D1 = PB16 // UART0 TX/PWM available
|
||||||
|
D4 = PA14 // PWM available
|
||||||
|
D5 = PA16 // PWM available
|
||||||
|
D6 = PA18 // PWM available
|
||||||
|
D8 = PB03 // built-in neopixel
|
||||||
|
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 = PA22 // PWM available
|
||||||
|
D13 = PA23 // PWM available
|
||||||
|
D21 = PA13 // PWM available
|
||||||
|
D22 = PA12 // PWM available
|
||||||
|
D23 = PB22 // PWM available
|
||||||
|
D24 = PB23 // PWM available
|
||||||
|
D25 = PA17 // PWM available
|
||||||
|
)
|
||||||
|
|
||||||
|
// Analog pins
|
||||||
|
const (
|
||||||
|
A0 = PA02 // ADC/AIN[0]
|
||||||
|
A1 = PA05 // 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
|
||||||
|
)
|
||||||
|
|
||||||
|
// UART2 pins
|
||||||
|
const (
|
||||||
|
UART2_TX_PIN = A4
|
||||||
|
UART2_RX_PIN = A5
|
||||||
|
)
|
||||||
|
|
||||||
|
// I2C pins
|
||||||
|
const (
|
||||||
|
SDA_PIN = D22 // SDA: SERCOM2/PAD[0]
|
||||||
|
SCL_PIN = D21 // SCL: SERCOM2/PAD[1]
|
||||||
|
)
|
||||||
|
|
||||||
|
// I2C on the Feather M4.
|
||||||
|
var (
|
||||||
|
I2C0 = I2C{Bus: sam.SERCOM2_I2CM,
|
||||||
|
SDA: SDA_PIN,
|
||||||
|
SCL: SCL_PIN,
|
||||||
|
PinMode: PinSERCOM}
|
||||||
|
)
|
||||||
|
|
||||||
|
// SPI pins
|
||||||
|
const (
|
||||||
|
SPI0_SCK_PIN = D25 // SCK: SERCOM1/PAD[1]
|
||||||
|
SPI0_MOSI_PIN = D24 // MOSI: SERCOM1/PAD[3]
|
||||||
|
SPI0_MISO_PIN = D23 // MISO: SERCOM1/PAD[2]
|
||||||
|
)
|
||||||
|
|
||||||
|
// SPI on the Feather M4.
|
||||||
|
var (
|
||||||
|
SPI0 = SPI{Bus: sam.SERCOM1_SPIM,
|
||||||
|
SCK: SPI0_SCK_PIN,
|
||||||
|
MOSI: SPI0_MOSI_PIN,
|
||||||
|
MISO: SPI0_MISO_PIN,
|
||||||
|
DOpad: spiTXPad3SCK1,
|
||||||
|
DIpad: sercomRXPad2,
|
||||||
|
MISOPinMode: PinSERCOM,
|
||||||
|
MOSIPinMode: PinSERCOM,
|
||||||
|
SCKPinMode: PinSERCOM,
|
||||||
|
}
|
||||||
|
)
|
||||||
@@ -6,23 +6,23 @@ const (
|
|||||||
D0 = P16
|
D0 = P16
|
||||||
D1 = P17
|
D1 = P17
|
||||||
D2 = P18
|
D2 = P18
|
||||||
D3 = P19 // Green LED/PWM
|
D3 = P19 // Green LED/PWM (PWM1_PWM1)
|
||||||
D4 = P20 // PWM
|
D4 = P20 // PWM (PWM1_PWM0)
|
||||||
D5 = P21 // Blue LED/PWM
|
D5 = P21 // Blue LED/PWM (PWM1_PWM2)
|
||||||
D6 = P22 // Red LED/PWM
|
D6 = P22 // Red LED/PWM (PWM1_PWM3)
|
||||||
D7 = P16
|
D7 = P16
|
||||||
D8 = NoPin // PWM?
|
D8 = NoPin // PWM?
|
||||||
D9 = P01
|
D9 = P01
|
||||||
D10 = P02
|
D10 = P02 // SPI1_CS0
|
||||||
D11 = P03
|
D11 = P03 // SPI1_DQ0
|
||||||
D12 = P04
|
D12 = P04 // SPI1_DQ1
|
||||||
D13 = P05
|
D13 = P05 // SPI1_SCK
|
||||||
D14 = NoPin // not connected
|
D14 = NoPin // not connected
|
||||||
D15 = P09 // does not seem to work?
|
D15 = P09 // does not seem to work?
|
||||||
D16 = P10 // PWM
|
D16 = P10 // PWM (PWM2_PWM0)
|
||||||
D17 = P11 // PWM
|
D17 = P11 // PWM (PWM2_PWM1)
|
||||||
D18 = P12 // SDA/PWM
|
D18 = P12 // SDA (I2C0_SDA)/PWM (PWM2_PWM2)
|
||||||
D19 = P13 // SDL/PWM
|
D19 = P13 // SDL (I2C0_SCL)/PWM (PWM2_PWM3)
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@@ -40,3 +40,14 @@ const (
|
|||||||
UART_TX_PIN = NoPin
|
UART_TX_PIN = NoPin
|
||||||
UART_RX_PIN = NoPin
|
UART_RX_PIN = NoPin
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// SPI pins
|
||||||
|
const (
|
||||||
|
SPI0_SCK_PIN = NoPin
|
||||||
|
SPI0_MOSI_PIN = NoPin
|
||||||
|
SPI0_MISO_PIN = NoPin
|
||||||
|
|
||||||
|
SPI1_SCK_PIN = D13
|
||||||
|
SPI1_MOSI_PIN = D11
|
||||||
|
SPI1_MISO_PIN = D12
|
||||||
|
)
|
||||||
|
|||||||
@@ -0,0 +1,12 @@
|
|||||||
|
// +build fe310,hifive1b
|
||||||
|
|
||||||
|
package machine
|
||||||
|
|
||||||
|
import "device/sifive"
|
||||||
|
|
||||||
|
// SPI on the HiFive1.
|
||||||
|
var (
|
||||||
|
SPI1 = SPI{
|
||||||
|
Bus: sifive.QSPI1,
|
||||||
|
}
|
||||||
|
)
|
||||||
@@ -28,7 +28,7 @@ const (
|
|||||||
// Analog pins
|
// Analog pins
|
||||||
const (
|
const (
|
||||||
A0 = PA02 // ADC/AIN[0]
|
A0 = PA02 // ADC/AIN[0]
|
||||||
A1 = PB05 // ADC/AIN[2]
|
A1 = PA05 // ADC/AIN[2]
|
||||||
A2 = PB08 // ADC/AIN[3]
|
A2 = PB08 // ADC/AIN[3]
|
||||||
A3 = PB09 // ADC/AIN[4]
|
A3 = PB09 // ADC/AIN[4]
|
||||||
A4 = PA04 // ADC/AIN[5]
|
A4 = PA04 // ADC/AIN[5]
|
||||||
@@ -51,10 +51,20 @@ const (
|
|||||||
UART_RX_PIN = D0
|
UART_RX_PIN = D0
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// UART1 var is on SERCOM3, defined in atsamd51.go
|
||||||
|
|
||||||
|
// UART2 pins
|
||||||
|
const (
|
||||||
|
UART2_TX_PIN = A4
|
||||||
|
UART2_RX_PIN = D2
|
||||||
|
)
|
||||||
|
|
||||||
|
// UART2 var is on SERCOM0, defined in atsamd51.go
|
||||||
|
|
||||||
// I2C pins
|
// I2C pins
|
||||||
const (
|
const (
|
||||||
SDA_PIN = PA12 // SDA: SERCOM3/PAD[0]
|
SDA_PIN = PA12 // SDA: SERCOM2/PAD[0]
|
||||||
SCL_PIN = PA13 // SCL: SERCOM3/PAD[1]
|
SCL_PIN = PA13 // SCL: SERCOM2/PAD[1]
|
||||||
)
|
)
|
||||||
|
|
||||||
// I2C on the ItsyBitsy M4.
|
// I2C on the ItsyBitsy M4.
|
||||||
@@ -75,9 +85,11 @@ const (
|
|||||||
// SPI on the ItsyBitsy M4.
|
// SPI on the ItsyBitsy M4.
|
||||||
var (
|
var (
|
||||||
SPI0 = SPI{Bus: sam.SERCOM1_SPIM,
|
SPI0 = SPI{Bus: sam.SERCOM1_SPIM,
|
||||||
SCK: SPI0_SCK_PIN,
|
SCK: SPI0_SCK_PIN,
|
||||||
MOSI: SPI0_MOSI_PIN,
|
MOSI: SPI0_MOSI_PIN,
|
||||||
MISO: SPI0_MISO_PIN,
|
MISO: SPI0_MISO_PIN,
|
||||||
DOpad: spiTXPad2SCK3,
|
DOpad: spiTXPad2SCK3,
|
||||||
DIpad: sercomRXPad0}
|
DIpad: sercomRXPad3,
|
||||||
|
MISOPinMode: PinSERCOM,
|
||||||
|
}
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -0,0 +1,132 @@
|
|||||||
|
// +build sam,atsamd51,metro_m4_airlift
|
||||||
|
|
||||||
|
package machine
|
||||||
|
|
||||||
|
import "device/sam"
|
||||||
|
|
||||||
|
// used to reset into bootloader
|
||||||
|
const RESET_MAGIC_VALUE = 0xf01669ef
|
||||||
|
|
||||||
|
// GPIO Pins
|
||||||
|
const (
|
||||||
|
D0 = PA23 // UART0 RX/PWM available
|
||||||
|
D1 = PA22 // UART0 TX/PWM available
|
||||||
|
D2 = PB17 // PWM available
|
||||||
|
D3 = PB16 // PWM available
|
||||||
|
D4 = PB13 // PWM available
|
||||||
|
D5 = PB14 // PWM available
|
||||||
|
D6 = PB15 // PWM available
|
||||||
|
D7 = PB12 // PWM available
|
||||||
|
|
||||||
|
D8 = PA21 // PWM available
|
||||||
|
D9 = PA20 // PWM available
|
||||||
|
D10 = PA18 // can be used for PWM or UART1 TX
|
||||||
|
D11 = PA19 // can be used for PWM or UART1 RX
|
||||||
|
D12 = PA17 // PWM available
|
||||||
|
D13 = PA16 // PWM available
|
||||||
|
|
||||||
|
D40 = PB22 // built-in neopixel
|
||||||
|
)
|
||||||
|
|
||||||
|
// Analog pins
|
||||||
|
const (
|
||||||
|
A0 = PA02 // ADC/AIN[0]
|
||||||
|
A1 = PA05 // ADC/AIN[2]
|
||||||
|
A2 = PB06 // ADC/AIN[3]
|
||||||
|
A3 = PB00 // ADC/AIN[4] // NOTE: different between "airlift" and non-airlift versions
|
||||||
|
A4 = PB08 // ADC/AIN[5]
|
||||||
|
A5 = PB09 // ADC/AIN[10]
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
LED = D13
|
||||||
|
)
|
||||||
|
|
||||||
|
// UART0 aka USBCDC pins
|
||||||
|
const (
|
||||||
|
USBCDC_DM_PIN = PA24
|
||||||
|
USBCDC_DP_PIN = PA25
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
UART_TX_PIN = D1
|
||||||
|
UART_RX_PIN = D0
|
||||||
|
)
|
||||||
|
|
||||||
|
// Note: UART1 is on SERCOM3, defined in machine_atsamd51.go
|
||||||
|
|
||||||
|
const (
|
||||||
|
NINA_CS = PA15
|
||||||
|
NINA_ACK = PB04
|
||||||
|
NINA_GPIO0 = PB01
|
||||||
|
NINA_RESETN = PB05
|
||||||
|
|
||||||
|
NINA_TX = PA04
|
||||||
|
NINA_RX = PA07
|
||||||
|
NINA_RTS = PB23
|
||||||
|
)
|
||||||
|
|
||||||
|
// UART2 is on SERCOM0, defined in machine_atsamd51.go, and connects to the
|
||||||
|
// onboard ESP32-WROOM chip.
|
||||||
|
|
||||||
|
// I2C pins
|
||||||
|
const (
|
||||||
|
SDA_PIN = PB02 // SDA: SERCOM5/PAD[0]
|
||||||
|
SCL_PIN = PB03 // SCL: SERCOM5/PAD[1]
|
||||||
|
)
|
||||||
|
|
||||||
|
// I2C on the Metro M4.
|
||||||
|
var (
|
||||||
|
I2C0 = I2C{Bus: sam.SERCOM5_I2CM,
|
||||||
|
SDA: SDA_PIN,
|
||||||
|
SCL: SCL_PIN,
|
||||||
|
PinMode: PinSERCOMAlt}
|
||||||
|
)
|
||||||
|
|
||||||
|
// SPI pins
|
||||||
|
const (
|
||||||
|
SPI0_SCK_PIN = PA13 // SCK: SERCOM2/PAD[1]
|
||||||
|
SPI0_MOSI_PIN = PA12 // MOSI: SERCOM2/PAD[0]
|
||||||
|
SPI0_MISO_PIN = PA14 // MISO: SERCOM2/PAD[2]
|
||||||
|
|
||||||
|
NINA_MOSI = SPI0_MOSI_PIN
|
||||||
|
NINA_MISO = SPI0_MISO_PIN
|
||||||
|
NINA_SCK = SPI0_SCK_PIN
|
||||||
|
)
|
||||||
|
|
||||||
|
// SPI on the Metro M4.
|
||||||
|
var (
|
||||||
|
SPI0 = SPI{
|
||||||
|
Bus: sam.SERCOM2_SPIM,
|
||||||
|
SCK: SPI0_SCK_PIN,
|
||||||
|
MOSI: SPI0_MOSI_PIN,
|
||||||
|
MISO: SPI0_MISO_PIN,
|
||||||
|
DOpad: spiTXPad0SCK1,
|
||||||
|
DIpad: sercomRXPad2,
|
||||||
|
MISOPinMode: PinSERCOM,
|
||||||
|
MOSIPinMode: PinSERCOM,
|
||||||
|
SCKPinMode: PinSERCOM,
|
||||||
|
}
|
||||||
|
NINA_SPI = SPI0
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
SPI1_SCK_PIN = D12 // MISO: SERCOM1/PAD[1]
|
||||||
|
SPI1_MOSI_PIN = D11 // MOSI: SERCOM1/PAD[3]
|
||||||
|
SPI1_MISO_PIN = D13 // SCK: SERCOM1/PAD[0]
|
||||||
|
)
|
||||||
|
|
||||||
|
// SPI1 on the Metro M4 on pins 11,12,13
|
||||||
|
var (
|
||||||
|
SPI1 = SPI{
|
||||||
|
Bus: sam.SERCOM1_SPIM,
|
||||||
|
SCK: SPI1_SCK_PIN,
|
||||||
|
MOSI: SPI1_MOSI_PIN,
|
||||||
|
MISO: SPI1_MISO_PIN,
|
||||||
|
DOpad: spiTXPad3SCK1,
|
||||||
|
DIpad: sercomRXPad0,
|
||||||
|
MISOPinMode: PinSERCOM,
|
||||||
|
MOSIPinMode: PinSERCOM,
|
||||||
|
SCKPinMode: PinSERCOM,
|
||||||
|
}
|
||||||
|
)
|
||||||
@@ -0,0 +1,61 @@
|
|||||||
|
// +build pinetime_devkit0
|
||||||
|
|
||||||
|
package machine
|
||||||
|
|
||||||
|
// Board pins for the PineTime.
|
||||||
|
// Details: https://wiki.pine64.org/index.php/PineTime
|
||||||
|
|
||||||
|
// The PineTime has a low-frequency (32kHz) crystal oscillator on board.
|
||||||
|
const HasLowFrequencyCrystal = true
|
||||||
|
|
||||||
|
// LEDs simply expose the three brightness level LEDs on the PineTime. They can
|
||||||
|
// be useful for simple "hello world" style programs.
|
||||||
|
const (
|
||||||
|
LED = LED1
|
||||||
|
LED1 = LCD_BACKLIGHT_HIGH
|
||||||
|
LED2 = LCD_BACKLIGHT_MID
|
||||||
|
LED3 = LCD_BACKLIGHT_LOW
|
||||||
|
)
|
||||||
|
|
||||||
|
// UART pins for PineTime. Note that RX is set to NoPin as RXD is not listed in
|
||||||
|
// the PineTime schematic 1.0:
|
||||||
|
// http://files.pine64.org/doc/PineTime/PineTime%20Port%20Assignment%20rev1.0.pdf
|
||||||
|
const (
|
||||||
|
UART_TX_PIN Pin = 11 // TP29 (TXD)
|
||||||
|
UART_RX_PIN Pin = NoPin
|
||||||
|
)
|
||||||
|
|
||||||
|
// SPI pins for the PineTime.
|
||||||
|
const (
|
||||||
|
SPI0_SCK_PIN Pin = 2
|
||||||
|
SPI0_MOSI_PIN Pin = 3
|
||||||
|
SPI0_MISO_PIN Pin = 4
|
||||||
|
)
|
||||||
|
|
||||||
|
// I2C pins for the PineTime.
|
||||||
|
const (
|
||||||
|
SDA_PIN Pin = 6
|
||||||
|
SCL_PIN Pin = 7
|
||||||
|
)
|
||||||
|
|
||||||
|
// Button pins. For some reason, there are two pins for the button.
|
||||||
|
const (
|
||||||
|
BUTTON_IN Pin = 13
|
||||||
|
BUTTON_OUT Pin = 15
|
||||||
|
)
|
||||||
|
|
||||||
|
// Pin for the vibrator.
|
||||||
|
const VIBRATOR_PIN Pin = 16
|
||||||
|
|
||||||
|
// LCD pins, using the naming convention of the official docs:
|
||||||
|
// http://files.pine64.org/doc/PineTime/PineTime%20Port%20Assignment%20rev1.0.pdf
|
||||||
|
const (
|
||||||
|
LCD_SCK = SPI0_SCK_PIN
|
||||||
|
LCD_SDI = SPI0_MOSI_PIN
|
||||||
|
LCD_RS Pin = 18
|
||||||
|
LCD_CS Pin = 25
|
||||||
|
LCD_RESET Pin = 26
|
||||||
|
LCD_BACKLIGHT_LOW Pin = 14
|
||||||
|
LCD_BACKLIGHT_MID Pin = 22
|
||||||
|
LCD_BACKLIGHT_HIGH Pin = 23
|
||||||
|
)
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
// +build x9pro
|
||||||
|
|
||||||
|
package machine
|
||||||
|
|
||||||
|
// https://hackaday.io/project/144350-hacking-wearables-for-mental-health-and-more/details
|
||||||
|
const (
|
||||||
|
LED Pin = 4 // HR LED pin
|
||||||
|
UART_TX_PIN Pin = NoPin
|
||||||
|
UART_RX_PIN Pin = NoPin
|
||||||
|
SCL_PIN Pin = NoPin
|
||||||
|
SDA_PIN Pin = NoPin
|
||||||
|
SPI0_SCK_PIN Pin = 18
|
||||||
|
SPI0_MISO_PIN Pin = 19
|
||||||
|
SPI0_MOSI_PIN Pin = 20
|
||||||
|
)
|
||||||
|
|
||||||
|
// LCD pins.
|
||||||
|
const (
|
||||||
|
OLED_CS Pin = 15 // chip select
|
||||||
|
OLED_RES Pin = 14 // reset pin
|
||||||
|
OLED_DC Pin = 13 // data/command
|
||||||
|
OLED_SCK Pin = 12 // SPI clock
|
||||||
|
OLED_MOSI Pin = 11 // SPI MOSI (master-out, slave-in)
|
||||||
|
OLED_LED_POW Pin = 16
|
||||||
|
OLED_IC_POW Pin = 17
|
||||||
|
)
|
||||||
|
|
||||||
|
const HasLowFrequencyCrystal = true
|
||||||
@@ -12,14 +12,18 @@ func (p Pin) Configure(config PinConfig) {
|
|||||||
if config.Mode == PinOutput { // set output bit
|
if config.Mode == PinOutput { // set output bit
|
||||||
if p < 8 {
|
if p < 8 {
|
||||||
avr.DDRD.SetBits(1 << uint8(p))
|
avr.DDRD.SetBits(1 << uint8(p))
|
||||||
} else {
|
} else if p < 14 {
|
||||||
avr.DDRB.SetBits(1 << uint8(p-8))
|
avr.DDRB.SetBits(1 << uint8(p-8))
|
||||||
|
} else {
|
||||||
|
avr.DDRC.SetBits(1 << uint8(p-14))
|
||||||
}
|
}
|
||||||
} else { // configure input: clear output bit
|
} else { // configure input: clear output bit
|
||||||
if p < 8 {
|
if p < 8 {
|
||||||
avr.DDRD.ClearBits(1 << uint8(p))
|
avr.DDRD.ClearBits(1 << uint8(p))
|
||||||
} else {
|
} else if p < 14 {
|
||||||
avr.DDRB.ClearBits(1 << uint8(p-8))
|
avr.DDRB.ClearBits(1 << uint8(p-8))
|
||||||
|
} else {
|
||||||
|
avr.DDRC.ClearBits(1 << uint8(p-14))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -29,17 +33,22 @@ func (p Pin) Get() bool {
|
|||||||
if p < 8 {
|
if p < 8 {
|
||||||
val := avr.PIND.Get() & (1 << uint8(p))
|
val := avr.PIND.Get() & (1 << uint8(p))
|
||||||
return (val > 0)
|
return (val > 0)
|
||||||
} else {
|
} else if p < 14 {
|
||||||
val := avr.PINB.Get() & (1 << uint8(p-8))
|
val := avr.PINB.Get() & (1 << uint8(p-8))
|
||||||
return (val > 0)
|
return (val > 0)
|
||||||
|
} else {
|
||||||
|
val := avr.PINC.Get() & (1 << uint8(p-14))
|
||||||
|
return (val > 0)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p Pin) getPortMask() (*volatile.Register8, uint8) {
|
func (p Pin) getPortMask() (*volatile.Register8, uint8) {
|
||||||
if p < 8 {
|
if p < 8 {
|
||||||
return avr.PORTD, 1 << uint8(p)
|
return avr.PORTD, 1 << uint8(p)
|
||||||
} else {
|
} else if p < 14 {
|
||||||
return avr.PORTB, 1 << uint8(p-8)
|
return avr.PORTB, 1 << uint8(p-8)
|
||||||
|
} else {
|
||||||
|
return avr.PORTC, 1 << uint8(p-14)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -877,29 +877,29 @@ func (i2s I2S) Configure(config I2SConfig) {
|
|||||||
// now set serializer data size.
|
// now set serializer data size.
|
||||||
switch config.DataFormat {
|
switch config.DataFormat {
|
||||||
case I2SDataFormat8bit:
|
case I2SDataFormat8bit:
|
||||||
i2s.Bus.SERCTRL1.SetBits(sam.I2S_SERCTRL_DATASIZE_8)
|
i2s.Bus.SERCTRL1.SetBits(sam.I2S_SERCTRL_DATASIZE_8 << sam.I2S_SERCTRL_DATASIZE_Pos)
|
||||||
|
|
||||||
case I2SDataFormat16bit:
|
case I2SDataFormat16bit:
|
||||||
i2s.Bus.SERCTRL1.SetBits(sam.I2S_SERCTRL_DATASIZE_16)
|
i2s.Bus.SERCTRL1.SetBits(sam.I2S_SERCTRL_DATASIZE_16 << sam.I2S_SERCTRL_DATASIZE_Pos)
|
||||||
|
|
||||||
case I2SDataFormat24bit:
|
case I2SDataFormat24bit:
|
||||||
i2s.Bus.SERCTRL1.SetBits(sam.I2S_SERCTRL_DATASIZE_24)
|
i2s.Bus.SERCTRL1.SetBits(sam.I2S_SERCTRL_DATASIZE_24 << sam.I2S_SERCTRL_DATASIZE_Pos)
|
||||||
|
|
||||||
case I2SDataFormat32bit:
|
case I2SDataFormat32bit:
|
||||||
case I2SDataFormatDefault:
|
case I2SDataFormatDefault:
|
||||||
i2s.Bus.SERCTRL1.SetBits(sam.I2S_SERCTRL_DATASIZE_32)
|
i2s.Bus.SERCTRL1.SetBits(sam.I2S_SERCTRL_DATASIZE_32 << sam.I2S_SERCTRL_DATASIZE_Pos)
|
||||||
}
|
}
|
||||||
|
|
||||||
// set serializer slot adjustment
|
// set serializer slot adjustment
|
||||||
if config.Standard == I2SStandardLSB {
|
if config.Standard == I2SStandardLSB {
|
||||||
// adjust right
|
// adjust right
|
||||||
i2s.Bus.SERCTRL1.ClearBits(sam.I2S_SERCTRL_SLOTADJ)
|
i2s.Bus.SERCTRL1.ClearBits(sam.I2S_SERCTRL_SLOTADJ)
|
||||||
|
|
||||||
|
// transfer LSB first
|
||||||
|
i2s.Bus.SERCTRL1.SetBits(sam.I2S_SERCTRL_BITREV)
|
||||||
} else {
|
} else {
|
||||||
// adjust left
|
// adjust left
|
||||||
i2s.Bus.SERCTRL1.SetBits(sam.I2S_SERCTRL_SLOTADJ)
|
i2s.Bus.SERCTRL1.SetBits(sam.I2S_SERCTRL_SLOTADJ)
|
||||||
|
|
||||||
// reverse bit order?
|
|
||||||
i2s.Bus.SERCTRL1.SetBits(sam.I2S_SERCTRL_BITREV)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// set serializer mode.
|
// set serializer mode.
|
||||||
|
|||||||
@@ -54,7 +54,7 @@ func (p Pin) Get() bool {
|
|||||||
if p < 32 {
|
if p < 32 {
|
||||||
return (sam.PORT.IN0.Get()>>uint8(p))&1 > 0
|
return (sam.PORT.IN0.Get()>>uint8(p))&1 > 0
|
||||||
} else {
|
} else {
|
||||||
return (sam.PORT.IN1.Get()>>(uint8(p)-32))&1 > 0
|
return (sam.PORT.IN1.Get()>>uint8(p-32))&1 > 0
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -77,7 +77,7 @@ func (p Pin) Configure(config PinConfig) {
|
|||||||
sam.PORT.DIRCLR0.Set(1 << uint8(p))
|
sam.PORT.DIRCLR0.Set(1 << uint8(p))
|
||||||
p.setPinCfg(sam.PORT_PINCFG0_INEN)
|
p.setPinCfg(sam.PORT_PINCFG0_INEN)
|
||||||
} else {
|
} else {
|
||||||
sam.PORT.DIRCLR1.Set(1<<uint8(p) - 32)
|
sam.PORT.DIRCLR1.Set(1 << uint8(p-32))
|
||||||
p.setPinCfg(sam.PORT_PINCFG0_INEN)
|
p.setPinCfg(sam.PORT_PINCFG0_INEN)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -87,8 +87,8 @@ func (p Pin) Configure(config PinConfig) {
|
|||||||
sam.PORT.OUTCLR0.Set(1 << uint8(p))
|
sam.PORT.OUTCLR0.Set(1 << uint8(p))
|
||||||
p.setPinCfg(sam.PORT_PINCFG0_INEN | sam.PORT_PINCFG0_PULLEN)
|
p.setPinCfg(sam.PORT_PINCFG0_INEN | sam.PORT_PINCFG0_PULLEN)
|
||||||
} else {
|
} else {
|
||||||
sam.PORT.DIRCLR1.Set(1<<uint8(p) - 32)
|
sam.PORT.DIRCLR1.Set(1 << uint8(p-32))
|
||||||
sam.PORT.OUTCLR1.Set(1<<uint8(p) - 32)
|
sam.PORT.OUTCLR1.Set(1 << uint8(p-32))
|
||||||
p.setPinCfg(sam.PORT_PINCFG0_INEN | sam.PORT_PINCFG0_PULLEN)
|
p.setPinCfg(sam.PORT_PINCFG0_INEN | sam.PORT_PINCFG0_PULLEN)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -98,8 +98,8 @@ func (p Pin) Configure(config PinConfig) {
|
|||||||
sam.PORT.OUTSET0.Set(1 << uint8(p))
|
sam.PORT.OUTSET0.Set(1 << uint8(p))
|
||||||
p.setPinCfg(sam.PORT_PINCFG0_INEN | sam.PORT_PINCFG0_PULLEN)
|
p.setPinCfg(sam.PORT_PINCFG0_INEN | sam.PORT_PINCFG0_PULLEN)
|
||||||
} else {
|
} else {
|
||||||
sam.PORT.DIRCLR1.Set(1<<uint8(p) - 32)
|
sam.PORT.DIRCLR1.Set(1 << uint8(p-32))
|
||||||
sam.PORT.OUTSET1.Set(1<<uint8(p) - 32)
|
sam.PORT.OUTSET1.Set(1 << uint8(p-32))
|
||||||
p.setPinCfg(sam.PORT_PINCFG0_INEN | sam.PORT_PINCFG0_PULLEN)
|
p.setPinCfg(sam.PORT_PINCFG0_INEN | sam.PORT_PINCFG0_PULLEN)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+224
-23
@@ -110,6 +110,153 @@ const (
|
|||||||
PB31 Pin = 63
|
PB31 Pin = 63
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// 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) {
|
||||||
|
group, pin_in_group := p.getPinGrouping()
|
||||||
|
return &sam.PORT.GROUP[group].OUTSET.Reg, 1 << pin_in_group
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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) {
|
||||||
|
group, pin_in_group := p.getPinGrouping()
|
||||||
|
return &sam.PORT.GROUP[group].OUTCLR.Reg, 1 << pin_in_group
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set the pin to high or low.
|
||||||
|
// Warning: only use this on an output pin!
|
||||||
|
func (p Pin) Set(high bool) {
|
||||||
|
group, pin_in_group := p.getPinGrouping()
|
||||||
|
if high {
|
||||||
|
sam.PORT.GROUP[group].OUTSET.Set(1 << pin_in_group)
|
||||||
|
} else {
|
||||||
|
sam.PORT.GROUP[group].OUTCLR.Set(1 << pin_in_group)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get returns the current value of a GPIO pin.
|
||||||
|
func (p Pin) Get() bool {
|
||||||
|
group, pin_in_group := p.getPinGrouping()
|
||||||
|
return (sam.PORT.GROUP[group].IN.Get()>>pin_in_group)&1 > 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// Toggle switches an output pin from low to high or from high to low.
|
||||||
|
// Warning: only use this on an output pin!
|
||||||
|
func (p Pin) Toggle() {
|
||||||
|
group, pin_in_group := p.getPinGrouping()
|
||||||
|
sam.PORT.GROUP[group].OUTTGL.Set(1 << pin_in_group)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Configure this pin with the given configuration.
|
||||||
|
func (p Pin) Configure(config PinConfig) {
|
||||||
|
group, pin_in_group := p.getPinGrouping()
|
||||||
|
switch config.Mode {
|
||||||
|
case PinOutput:
|
||||||
|
sam.PORT.GROUP[group].DIRSET.Set(1 << pin_in_group)
|
||||||
|
// output is also set to input enable so pin can read back its own value
|
||||||
|
p.setPinCfg(sam.PORT_GROUP_PINCFG_INEN)
|
||||||
|
|
||||||
|
case PinInput:
|
||||||
|
sam.PORT.GROUP[group].DIRCLR.Set(1 << pin_in_group)
|
||||||
|
p.setPinCfg(sam.PORT_GROUP_PINCFG_INEN)
|
||||||
|
|
||||||
|
case PinInputPulldown:
|
||||||
|
sam.PORT.GROUP[group].DIRCLR.Set(1 << pin_in_group)
|
||||||
|
sam.PORT.GROUP[group].OUTCLR.Set(1 << pin_in_group)
|
||||||
|
p.setPinCfg(sam.PORT_GROUP_PINCFG_INEN | sam.PORT_GROUP_PINCFG_PULLEN)
|
||||||
|
|
||||||
|
case PinInputPullup:
|
||||||
|
sam.PORT.GROUP[group].DIRCLR.Set(1 << pin_in_group)
|
||||||
|
sam.PORT.GROUP[group].OUTSET.Set(1 << pin_in_group)
|
||||||
|
p.setPinCfg(sam.PORT_GROUP_PINCFG_INEN | sam.PORT_GROUP_PINCFG_PULLEN)
|
||||||
|
|
||||||
|
case PinSERCOM:
|
||||||
|
if p&1 > 0 {
|
||||||
|
// odd pin, so save the even pins
|
||||||
|
val := p.getPMux() & sam.PORT_GROUP_PMUX_PMUXE_Msk
|
||||||
|
p.setPMux(val | (uint8(PinSERCOM) << sam.PORT_GROUP_PMUX_PMUXO_Pos))
|
||||||
|
} else {
|
||||||
|
// even pin, so save the odd pins
|
||||||
|
val := p.getPMux() & sam.PORT_GROUP_PMUX_PMUXO_Msk
|
||||||
|
p.setPMux(val | (uint8(PinSERCOM) << sam.PORT_GROUP_PMUX_PMUXE_Pos))
|
||||||
|
}
|
||||||
|
// enable port config
|
||||||
|
p.setPinCfg(sam.PORT_GROUP_PINCFG_PMUXEN | sam.PORT_GROUP_PINCFG_DRVSTR | sam.PORT_GROUP_PINCFG_INEN)
|
||||||
|
|
||||||
|
case PinSERCOMAlt:
|
||||||
|
if p&1 > 0 {
|
||||||
|
// odd pin, so save the even pins
|
||||||
|
val := p.getPMux() & sam.PORT_GROUP_PMUX_PMUXE_Msk
|
||||||
|
p.setPMux(val | (uint8(PinSERCOMAlt) << sam.PORT_GROUP_PMUX_PMUXO_Pos))
|
||||||
|
} else {
|
||||||
|
// even pin, so save the odd pins
|
||||||
|
val := p.getPMux() & sam.PORT_GROUP_PMUX_PMUXO_Msk
|
||||||
|
p.setPMux(val | (uint8(PinSERCOMAlt) << sam.PORT_GROUP_PMUX_PMUXE_Pos))
|
||||||
|
}
|
||||||
|
// enable port config
|
||||||
|
p.setPinCfg(sam.PORT_GROUP_PINCFG_PMUXEN | sam.PORT_GROUP_PINCFG_DRVSTR)
|
||||||
|
|
||||||
|
case PinCom:
|
||||||
|
if p&1 > 0 {
|
||||||
|
// odd pin, so save the even pins
|
||||||
|
val := p.getPMux() & sam.PORT_GROUP_PMUX_PMUXE_Msk
|
||||||
|
p.setPMux(val | (uint8(PinCom) << sam.PORT_GROUP_PMUX_PMUXO_Pos))
|
||||||
|
} else {
|
||||||
|
// even pin, so save the odd pins
|
||||||
|
val := p.getPMux() & sam.PORT_GROUP_PMUX_PMUXO_Msk
|
||||||
|
p.setPMux(val | (uint8(PinCom) << sam.PORT_GROUP_PMUX_PMUXE_Pos))
|
||||||
|
}
|
||||||
|
// enable port config
|
||||||
|
p.setPinCfg(sam.PORT_GROUP_PINCFG_PMUXEN)
|
||||||
|
case PinAnalog:
|
||||||
|
if p&1 > 0 {
|
||||||
|
// odd pin, so save the even pins
|
||||||
|
val := p.getPMux() & sam.PORT_GROUP_PMUX_PMUXE_Msk
|
||||||
|
p.setPMux(val | (uint8(PinAnalog) << sam.PORT_GROUP_PMUX_PMUXO_Pos))
|
||||||
|
} else {
|
||||||
|
// even pin, so save the odd pins
|
||||||
|
val := p.getPMux() & sam.PORT_GROUP_PMUX_PMUXO_Msk
|
||||||
|
p.setPMux(val | (uint8(PinAnalog) << sam.PORT_GROUP_PMUX_PMUXE_Pos))
|
||||||
|
}
|
||||||
|
// enable port config
|
||||||
|
p.setPinCfg(sam.PORT_GROUP_PINCFG_PMUXEN | sam.PORT_GROUP_PINCFG_DRVSTR)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// getPMux returns the value for the correct PMUX register for this pin.
|
||||||
|
func (p Pin) getPMux() uint8 {
|
||||||
|
group, pin_in_group := p.getPinGrouping()
|
||||||
|
return sam.PORT.GROUP[group].PMUX[pin_in_group>>1].Get()
|
||||||
|
}
|
||||||
|
|
||||||
|
// setPMux sets the value for the correct PMUX register for this pin.
|
||||||
|
func (p Pin) setPMux(val uint8) {
|
||||||
|
group, pin_in_group := p.getPinGrouping()
|
||||||
|
sam.PORT.GROUP[group].PMUX[pin_in_group>>1].Set(val)
|
||||||
|
}
|
||||||
|
|
||||||
|
// getPinCfg returns the value for the correct PINCFG register for this pin.
|
||||||
|
func (p Pin) getPinCfg() uint8 {
|
||||||
|
group, pin_in_group := p.getPinGrouping()
|
||||||
|
return sam.PORT.GROUP[group].PINCFG[pin_in_group].Get()
|
||||||
|
}
|
||||||
|
|
||||||
|
// setPinCfg sets the value for the correct PINCFG register for this pin.
|
||||||
|
func (p Pin) setPinCfg(val uint8) {
|
||||||
|
group, pin_in_group := p.getPinGrouping()
|
||||||
|
sam.PORT.GROUP[group].PINCFG[pin_in_group].Set(val)
|
||||||
|
}
|
||||||
|
|
||||||
|
// getPinGrouping calculates the gpio group and pin id from the pin number.
|
||||||
|
// Pins are split into groups of 32, and each group has its own set of
|
||||||
|
// control registers.
|
||||||
|
func (p Pin) getPinGrouping() (uint8, uint8) {
|
||||||
|
group := uint8(p) >> 5
|
||||||
|
pin_in_group := uint8(p) & 0x1f
|
||||||
|
return group, pin_in_group
|
||||||
|
}
|
||||||
|
|
||||||
// InitADC initializes the ADC.
|
// InitADC initializes the ADC.
|
||||||
func InitADC() {
|
func InitADC() {
|
||||||
// ADC Bias Calibration
|
// ADC Bias Calibration
|
||||||
@@ -239,11 +386,8 @@ func (a ADC) Get() uint16 {
|
|||||||
for bus.SYNCBUSY.HasBits(sam.ADC_SYNCBUSY_INPUTCTRL) {
|
for bus.SYNCBUSY.HasBits(sam.ADC_SYNCBUSY_INPUTCTRL) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Selection for the positive ADC input
|
// Selection for the positive ADC input channel
|
||||||
bus.INPUTCTRL.ClearBits(sam.ADC_INPUTCTRL_MUXPOS_Msk)
|
bus.INPUTCTRL.SetBits((uint16(ch) & sam.ADC_INPUTCTRL_MUXPOS_Msk) << sam.ADC_INPUTCTRL_MUXPOS_Pos)
|
||||||
for bus.SYNCBUSY.HasBits(sam.ADC_SYNCBUSY_ENABLE) {
|
|
||||||
}
|
|
||||||
bus.INPUTCTRL.SetBits(uint16(ch << sam.ADC_INPUTCTRL_MUXPOS_Pos))
|
|
||||||
for bus.SYNCBUSY.HasBits(sam.ADC_SYNCBUSY_ENABLE) {
|
for bus.SYNCBUSY.HasBits(sam.ADC_SYNCBUSY_ENABLE) {
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -329,6 +473,13 @@ var (
|
|||||||
Buffer: NewRingBuffer(),
|
Buffer: NewRingBuffer(),
|
||||||
Mode: PinSERCOMAlt,
|
Mode: PinSERCOMAlt,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// The second hardware serial port on the SAMD51. Uses the SERCOM0 interface.
|
||||||
|
UART2 = UART{
|
||||||
|
Buffer: NewRingBuffer(),
|
||||||
|
Bus: sam.SERCOM0_USART_INT,
|
||||||
|
Mode: PinSERCOMAlt,
|
||||||
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@@ -365,6 +516,8 @@ func (uart UART) Configure(config UARTConfig) {
|
|||||||
// determine pads
|
// determine pads
|
||||||
var txpad, rxpad int
|
var txpad, rxpad int
|
||||||
switch config.TX {
|
switch config.TX {
|
||||||
|
case PA04:
|
||||||
|
txpad = sercomTXPad0
|
||||||
case PA10:
|
case PA10:
|
||||||
txpad = sercomTXPad2
|
txpad = sercomTXPad2
|
||||||
case PA18:
|
case PA18:
|
||||||
@@ -376,6 +529,10 @@ func (uart UART) Configure(config UARTConfig) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
switch config.RX {
|
switch config.RX {
|
||||||
|
case PA06:
|
||||||
|
rxpad = sercomRXPad2
|
||||||
|
case PA07:
|
||||||
|
rxpad = sercomRXPad3
|
||||||
case PA11:
|
case PA11:
|
||||||
rxpad = sercomRXPad3
|
rxpad = sercomRXPad3
|
||||||
case PA18:
|
case PA18:
|
||||||
@@ -443,11 +600,20 @@ func (uart UART) Configure(config UARTConfig) {
|
|||||||
// setup interrupt on receive
|
// setup interrupt on receive
|
||||||
uart.Bus.INTENSET.Set(sam.SERCOM_USART_INT_INTENSET_RXC)
|
uart.Bus.INTENSET.Set(sam.SERCOM_USART_INT_INTENSET_RXC)
|
||||||
|
|
||||||
// Enable RX IRQ. Currently assumes SERCOM3.
|
// Enable RX IRQ.
|
||||||
arm.EnableIRQ(sam.IRQ_SERCOM3_0)
|
switch uart.Bus {
|
||||||
arm.EnableIRQ(sam.IRQ_SERCOM3_1)
|
case sam.SERCOM0_USART_INT:
|
||||||
arm.EnableIRQ(sam.IRQ_SERCOM3_2)
|
arm.EnableIRQ(sam.IRQ_SERCOM0_0)
|
||||||
arm.EnableIRQ(sam.IRQ_SERCOM3_OTHER)
|
arm.EnableIRQ(sam.IRQ_SERCOM0_1)
|
||||||
|
arm.EnableIRQ(sam.IRQ_SERCOM0_2)
|
||||||
|
arm.EnableIRQ(sam.IRQ_SERCOM0_OTHER)
|
||||||
|
default:
|
||||||
|
// Currently assumes SERCOM3
|
||||||
|
arm.EnableIRQ(sam.IRQ_SERCOM3_0)
|
||||||
|
arm.EnableIRQ(sam.IRQ_SERCOM3_1)
|
||||||
|
arm.EnableIRQ(sam.IRQ_SERCOM3_2)
|
||||||
|
arm.EnableIRQ(sam.IRQ_SERCOM3_OTHER)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetBaudRate sets the communication speed for the UART.
|
// SetBaudRate sets the communication speed for the UART.
|
||||||
@@ -499,6 +665,32 @@ func handleUART1() {
|
|||||||
UART1.Bus.INTFLAG.SetBits(sam.SERCOM_USART_INT_INTFLAG_RXC)
|
UART1.Bus.INTFLAG.SetBits(sam.SERCOM_USART_INT_INTFLAG_RXC)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//go:export SERCOM0_0_IRQHandler
|
||||||
|
func handleSERCOM0_0() {
|
||||||
|
handleUART2()
|
||||||
|
}
|
||||||
|
|
||||||
|
//go:export SERCOM0_1_IRQHandler
|
||||||
|
func handleSERCOM0_1() {
|
||||||
|
handleUART2()
|
||||||
|
}
|
||||||
|
|
||||||
|
//go:export SERCOM0_2_IRQHandler
|
||||||
|
func handleSERCOM0_2() {
|
||||||
|
handleUART2()
|
||||||
|
}
|
||||||
|
|
||||||
|
//go:export SERCOM0_OTHER_IRQHandler
|
||||||
|
func handleSERCOM0_OTHER() {
|
||||||
|
handleUART2()
|
||||||
|
}
|
||||||
|
|
||||||
|
func handleUART2() {
|
||||||
|
// should reset IRQ
|
||||||
|
UART2.Receive(byte((UART2.Bus.DATA.Get() & 0xFF)))
|
||||||
|
UART2.Bus.INTFLAG.SetBits(sam.SERCOM_USART_INT_INTFLAG_RXC)
|
||||||
|
}
|
||||||
|
|
||||||
// I2C on the SAMD51.
|
// I2C on the SAMD51.
|
||||||
type I2C struct {
|
type I2C struct {
|
||||||
Bus *sam.SERCOM_I2CM_Type
|
Bus *sam.SERCOM_I2CM_Type
|
||||||
@@ -737,12 +929,15 @@ func (i2c I2C) readByte() byte {
|
|||||||
|
|
||||||
// SPI
|
// SPI
|
||||||
type SPI struct {
|
type SPI struct {
|
||||||
Bus *sam.SERCOM_SPIM_Type
|
Bus *sam.SERCOM_SPIM_Type
|
||||||
SCK Pin
|
SCK Pin
|
||||||
MOSI Pin
|
MOSI Pin
|
||||||
MISO Pin
|
MISO Pin
|
||||||
DOpad int
|
DOpad int
|
||||||
DIpad int
|
DIpad int
|
||||||
|
SCKPinMode PinMode
|
||||||
|
MOSIPinMode PinMode
|
||||||
|
MISOPinMode PinMode
|
||||||
}
|
}
|
||||||
|
|
||||||
// SPIConfig is used to store config info for SPI.
|
// SPIConfig is used to store config info for SPI.
|
||||||
@@ -757,10 +952,6 @@ type SPIConfig struct {
|
|||||||
|
|
||||||
// Configure is intended to setup the SPI interface.
|
// Configure is intended to setup the SPI interface.
|
||||||
func (spi SPI) Configure(config SPIConfig) {
|
func (spi SPI) Configure(config SPIConfig) {
|
||||||
config.SCK = spi.SCK
|
|
||||||
config.MOSI = spi.MOSI
|
|
||||||
config.MISO = spi.MISO
|
|
||||||
|
|
||||||
doPad := spi.DOpad
|
doPad := spi.DOpad
|
||||||
diPad := spi.DIpad
|
diPad := spi.DIpad
|
||||||
|
|
||||||
@@ -775,9 +966,19 @@ func (spi SPI) Configure(config SPIConfig) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// enable pins
|
// enable pins
|
||||||
config.SCK.Configure(PinConfig{Mode: PinSERCOMAlt})
|
if spi.SCKPinMode == 0 {
|
||||||
config.MOSI.Configure(PinConfig{Mode: PinSERCOMAlt})
|
spi.SCKPinMode = PinSERCOMAlt
|
||||||
config.MISO.Configure(PinConfig{Mode: PinSERCOMAlt})
|
}
|
||||||
|
if spi.MOSIPinMode == 0 {
|
||||||
|
spi.MOSIPinMode = PinSERCOMAlt
|
||||||
|
}
|
||||||
|
if spi.MISOPinMode == 0 {
|
||||||
|
spi.MISOPinMode = PinSERCOMAlt
|
||||||
|
}
|
||||||
|
|
||||||
|
spi.SCK.Configure(PinConfig{Mode: spi.SCKPinMode})
|
||||||
|
spi.MOSI.Configure(PinConfig{Mode: spi.MOSIPinMode})
|
||||||
|
spi.MISO.Configure(PinConfig{Mode: spi.MISOPinMode})
|
||||||
|
|
||||||
// reset SERCOM
|
// reset SERCOM
|
||||||
spi.Bus.CTRLA.SetBits(sam.SERCOM_SPIM_CTRLA_SWRST)
|
spi.Bus.CTRLA.SetBits(sam.SERCOM_SPIM_CTRLA_SWRST)
|
||||||
|
|||||||
@@ -6,196 +6,3 @@
|
|||||||
// http://ww1.microchip.com/downloads/en/DeviceDoc/60001507C.pdf
|
// http://ww1.microchip.com/downloads/en/DeviceDoc/60001507C.pdf
|
||||||
//
|
//
|
||||||
package machine
|
package machine
|
||||||
|
|
||||||
import (
|
|
||||||
"device/sam"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Return the register and mask to enable a given GPIO pin. This can be used to
|
|
||||||
// implement bit-banged drivers.
|
|
||||||
func (p Pin) PortMaskSet() (*uint32, uint32) {
|
|
||||||
if p < 32 {
|
|
||||||
return &sam.PORT.GROUP[0].OUTSET.Reg, 1 << uint8(p)
|
|
||||||
} else {
|
|
||||||
return &sam.PORT.GROUP[1].OUTSET.Reg, 1 << uint8(p-32)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Return the register and mask to disable a given port. This can be used to
|
|
||||||
// implement bit-banged drivers.
|
|
||||||
func (p Pin) PortMaskClear() (*uint32, uint32) {
|
|
||||||
if p < 32 {
|
|
||||||
return &sam.PORT.GROUP[0].OUTCLR.Reg, 1 << uint8(p)
|
|
||||||
} else {
|
|
||||||
return &sam.PORT.GROUP[1].OUTCLR.Reg, 1 << uint8(p-32)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set the pin to high or low.
|
|
||||||
// Warning: only use this on an output pin!
|
|
||||||
func (p Pin) Set(high bool) {
|
|
||||||
if p < 32 {
|
|
||||||
if high {
|
|
||||||
sam.PORT.GROUP[0].OUTSET.Set(1 << uint8(p))
|
|
||||||
} else {
|
|
||||||
sam.PORT.GROUP[0].OUTCLR.Set(1 << uint8(p))
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if high {
|
|
||||||
sam.PORT.GROUP[1].OUTSET.Set(1 << uint8(p-32))
|
|
||||||
} else {
|
|
||||||
sam.PORT.GROUP[1].OUTCLR.Set(1 << uint8(p-32))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get returns the current value of a GPIO pin.
|
|
||||||
func (p Pin) Get() bool {
|
|
||||||
if p < 32 {
|
|
||||||
return (sam.PORT.GROUP[0].IN.Get()>>uint8(p))&1 > 0
|
|
||||||
} else {
|
|
||||||
return (sam.PORT.GROUP[1].IN.Get()>>(uint8(p)-32))&1 > 0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Configure this pin with the given configuration.
|
|
||||||
func (p Pin) Configure(config PinConfig) {
|
|
||||||
switch config.Mode {
|
|
||||||
case PinOutput:
|
|
||||||
if p < 32 {
|
|
||||||
sam.PORT.GROUP[0].DIRSET.Set(1 << uint8(p))
|
|
||||||
// output is also set to input enable so pin can read back its own value
|
|
||||||
p.setPinCfg(sam.PORT_GROUP_PINCFG_INEN)
|
|
||||||
} else {
|
|
||||||
sam.PORT.GROUP[1].DIRSET.Set(1 << uint8(p-32))
|
|
||||||
// output is also set to input enable so pin can read back its own value
|
|
||||||
p.setPinCfg(sam.PORT_GROUP_PINCFG_INEN)
|
|
||||||
}
|
|
||||||
|
|
||||||
case PinInput:
|
|
||||||
if p < 32 {
|
|
||||||
sam.PORT.GROUP[0].DIRCLR.Set(1 << uint8(p))
|
|
||||||
p.setPinCfg(sam.PORT_GROUP_PINCFG_INEN)
|
|
||||||
} else {
|
|
||||||
sam.PORT.GROUP[1].DIRCLR.Set(1<<uint8(p) - 32)
|
|
||||||
p.setPinCfg(sam.PORT_GROUP_PINCFG_INEN)
|
|
||||||
}
|
|
||||||
|
|
||||||
case PinInputPulldown:
|
|
||||||
if p < 32 {
|
|
||||||
sam.PORT.GROUP[0].DIRCLR.Set(1 << uint8(p))
|
|
||||||
sam.PORT.GROUP[0].OUTCLR.Set(1 << uint8(p))
|
|
||||||
p.setPinCfg(sam.PORT_GROUP_PINCFG_INEN | sam.PORT_GROUP_PINCFG_PULLEN)
|
|
||||||
} else {
|
|
||||||
sam.PORT.GROUP[1].DIRCLR.Set(1<<uint8(p) - 32)
|
|
||||||
sam.PORT.GROUP[1].OUTCLR.Set(1<<uint8(p) - 32)
|
|
||||||
p.setPinCfg(sam.PORT_GROUP_PINCFG_INEN | sam.PORT_GROUP_PINCFG_PULLEN)
|
|
||||||
}
|
|
||||||
|
|
||||||
case PinInputPullup:
|
|
||||||
if p < 32 {
|
|
||||||
sam.PORT.GROUP[0].DIRCLR.Set(1 << uint8(p))
|
|
||||||
sam.PORT.GROUP[0].OUTSET.Set(1 << uint8(p))
|
|
||||||
p.setPinCfg(sam.PORT_GROUP_PINCFG_INEN | sam.PORT_GROUP_PINCFG_PULLEN)
|
|
||||||
} else {
|
|
||||||
sam.PORT.GROUP[1].DIRCLR.Set(1<<uint8(p) - 32)
|
|
||||||
sam.PORT.GROUP[1].OUTSET.Set(1<<uint8(p) - 32)
|
|
||||||
p.setPinCfg(sam.PORT_GROUP_PINCFG_INEN | sam.PORT_GROUP_PINCFG_PULLEN)
|
|
||||||
}
|
|
||||||
|
|
||||||
case PinSERCOM:
|
|
||||||
if p&1 > 0 {
|
|
||||||
// odd pin, so save the even pins
|
|
||||||
val := p.getPMux() & sam.PORT_GROUP_PMUX_PMUXE_Msk
|
|
||||||
p.setPMux(val | (uint8(PinSERCOM) << sam.PORT_GROUP_PMUX_PMUXO_Pos))
|
|
||||||
} else {
|
|
||||||
// even pin, so save the odd pins
|
|
||||||
val := p.getPMux() & sam.PORT_GROUP_PMUX_PMUXO_Msk
|
|
||||||
p.setPMux(val | (uint8(PinSERCOM) << sam.PORT_GROUP_PMUX_PMUXE_Pos))
|
|
||||||
}
|
|
||||||
// enable port config
|
|
||||||
p.setPinCfg(sam.PORT_GROUP_PINCFG_PMUXEN | sam.PORT_GROUP_PINCFG_DRVSTR | sam.PORT_GROUP_PINCFG_INEN)
|
|
||||||
|
|
||||||
case PinSERCOMAlt:
|
|
||||||
if p&1 > 0 {
|
|
||||||
// odd pin, so save the even pins
|
|
||||||
val := p.getPMux() & sam.PORT_GROUP_PMUX_PMUXE_Msk
|
|
||||||
p.setPMux(val | (uint8(PinSERCOMAlt) << sam.PORT_GROUP_PMUX_PMUXO_Pos))
|
|
||||||
} else {
|
|
||||||
// even pin, so save the odd pins
|
|
||||||
val := p.getPMux() & sam.PORT_GROUP_PMUX_PMUXO_Msk
|
|
||||||
p.setPMux(val | (uint8(PinSERCOMAlt) << sam.PORT_GROUP_PMUX_PMUXE_Pos))
|
|
||||||
}
|
|
||||||
// enable port config
|
|
||||||
p.setPinCfg(sam.PORT_GROUP_PINCFG_PMUXEN | sam.PORT_GROUP_PINCFG_DRVSTR)
|
|
||||||
|
|
||||||
case PinCom:
|
|
||||||
if p&1 > 0 {
|
|
||||||
// odd pin, so save the even pins
|
|
||||||
val := p.getPMux() & sam.PORT_GROUP_PMUX_PMUXE_Msk
|
|
||||||
p.setPMux(val | (uint8(PinCom) << sam.PORT_GROUP_PMUX_PMUXO_Pos))
|
|
||||||
} else {
|
|
||||||
// even pin, so save the odd pins
|
|
||||||
val := p.getPMux() & sam.PORT_GROUP_PMUX_PMUXO_Msk
|
|
||||||
p.setPMux(val | (uint8(PinCom) << sam.PORT_GROUP_PMUX_PMUXE_Pos))
|
|
||||||
}
|
|
||||||
// enable port config
|
|
||||||
p.setPinCfg(sam.PORT_GROUP_PINCFG_PMUXEN)
|
|
||||||
case PinAnalog:
|
|
||||||
if p&1 > 0 {
|
|
||||||
// odd pin, so save the even pins
|
|
||||||
val := p.getPMux() & sam.PORT_GROUP_PMUX_PMUXE_Msk
|
|
||||||
p.setPMux(val | (uint8(PinAnalog) << sam.PORT_GROUP_PMUX_PMUXO_Pos))
|
|
||||||
} else {
|
|
||||||
// even pin, so save the odd pins
|
|
||||||
val := p.getPMux() & sam.PORT_GROUP_PMUX_PMUXO_Msk
|
|
||||||
p.setPMux(val | (uint8(PinAnalog) << sam.PORT_GROUP_PMUX_PMUXE_Pos))
|
|
||||||
}
|
|
||||||
// enable port config
|
|
||||||
p.setPinCfg(sam.PORT_GROUP_PINCFG_PMUXEN | sam.PORT_GROUP_PINCFG_DRVSTR)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// getPMux returns the value for the correct PMUX register for this pin.
|
|
||||||
func (p Pin) getPMux() uint8 {
|
|
||||||
switch {
|
|
||||||
case p < 32:
|
|
||||||
return sam.PORT.GROUP[0].PMUX[uint8(p)>>1].Get()
|
|
||||||
case p >= 32 && p < 64:
|
|
||||||
return sam.PORT.GROUP[1].PMUX[uint8(p-32)>>1].Get()
|
|
||||||
default:
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// setPMux sets the value for the correct PMUX register for this pin.
|
|
||||||
func (p Pin) setPMux(val uint8) {
|
|
||||||
switch {
|
|
||||||
case p < 32:
|
|
||||||
sam.PORT.GROUP[0].PMUX[uint8(p)>>1].Set(val)
|
|
||||||
case p >= 32 && p < 64:
|
|
||||||
sam.PORT.GROUP[1].PMUX[uint8(p-32)>>1].Set(val)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// getPinCfg returns the value for the correct PINCFG register for this pin.
|
|
||||||
func (p Pin) getPinCfg() uint8 {
|
|
||||||
switch {
|
|
||||||
case p < 32:
|
|
||||||
return sam.PORT.GROUP[0].PINCFG[p].Get()
|
|
||||||
case p >= 32 && p <= 64:
|
|
||||||
return sam.PORT.GROUP[1].PINCFG[p-32].Get()
|
|
||||||
default:
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// setPinCfg sets the value for the correct PINCFG register for this pin.
|
|
||||||
func (p Pin) setPinCfg(val uint8) {
|
|
||||||
switch {
|
|
||||||
case p < 32:
|
|
||||||
sam.PORT.GROUP[0].PINCFG[p].Set(val)
|
|
||||||
case p >= 32 && p <= 64:
|
|
||||||
sam.PORT.GROUP[1].PINCFG[p-32].Set(val)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -0,0 +1,8 @@
|
|||||||
|
// +build sam,atsamd51,atsamd51j19
|
||||||
|
|
||||||
|
// Peripheral abstraction layer for the atsamd51.
|
||||||
|
//
|
||||||
|
// Datasheet:
|
||||||
|
// http://ww1.microchip.com/downloads/en/DeviceDoc/60001507C.pdf
|
||||||
|
//
|
||||||
|
package machine
|
||||||
@@ -6,18 +6,30 @@ import (
|
|||||||
"device/sifive"
|
"device/sifive"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const CPU_FREQUENCY = 16000000
|
||||||
|
|
||||||
type PinMode uint8
|
type PinMode uint8
|
||||||
|
|
||||||
const (
|
const (
|
||||||
PinInput PinMode = iota
|
PinInput PinMode = iota
|
||||||
PinOutput
|
PinOutput
|
||||||
|
PinPWM
|
||||||
|
PinSPI
|
||||||
|
PinI2C = PinSPI
|
||||||
)
|
)
|
||||||
|
|
||||||
// Configure this pin with the given configuration.
|
// Configure this pin with the given configuration.
|
||||||
func (p Pin) Configure(config PinConfig) {
|
func (p Pin) Configure(config PinConfig) {
|
||||||
sifive.GPIO0.INPUT_EN.SetBits(1 << uint8(p))
|
sifive.GPIO0.INPUT_EN.SetBits(1 << uint8(p))
|
||||||
if config.Mode == PinOutput {
|
switch config.Mode {
|
||||||
|
case PinOutput:
|
||||||
sifive.GPIO0.OUTPUT_EN.SetBits(1 << uint8(p))
|
sifive.GPIO0.OUTPUT_EN.SetBits(1 << uint8(p))
|
||||||
|
case PinPWM:
|
||||||
|
sifive.GPIO0.IOF_EN.SetBits(1 << uint8(p))
|
||||||
|
sifive.GPIO0.IOF_SEL.SetBits(1 << uint8(p))
|
||||||
|
case PinSPI:
|
||||||
|
sifive.GPIO0.IOF_EN.SetBits(1 << uint8(p))
|
||||||
|
sifive.GPIO0.IOF_SEL.ClearBits(1 << uint8(p))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -59,3 +71,93 @@ func (uart UART) WriteByte(c byte) {
|
|||||||
|
|
||||||
sifive.UART0.TXDATA.Set(uint32(c))
|
sifive.UART0.TXDATA.Set(uint32(c))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SPI on the FE310. The normal SPI0 is actually a quad-SPI meant for flash, so it is best
|
||||||
|
// to use SPI1 or SPI2 port for most applications.
|
||||||
|
type SPI struct {
|
||||||
|
Bus *sifive.QSPI_Type
|
||||||
|
}
|
||||||
|
|
||||||
|
// SPIConfig is used to store config info for SPI.
|
||||||
|
type SPIConfig struct {
|
||||||
|
Frequency uint32
|
||||||
|
SCK Pin
|
||||||
|
MOSI Pin
|
||||||
|
MISO Pin
|
||||||
|
LSBFirst bool
|
||||||
|
Mode uint8
|
||||||
|
}
|
||||||
|
|
||||||
|
// Configure is intended to setup the SPI interface.
|
||||||
|
func (spi SPI) Configure(config SPIConfig) error {
|
||||||
|
// Use default pins if not set.
|
||||||
|
if config.SCK == 0 && config.MOSI == 0 && config.MISO == 0 {
|
||||||
|
config.SCK = SPI0_SCK_PIN
|
||||||
|
config.MOSI = SPI0_MOSI_PIN
|
||||||
|
config.MISO = SPI0_MISO_PIN
|
||||||
|
}
|
||||||
|
|
||||||
|
// enable pins for SPI
|
||||||
|
config.SCK.Configure(PinConfig{Mode: PinSPI})
|
||||||
|
config.MOSI.Configure(PinConfig{Mode: PinSPI})
|
||||||
|
config.MISO.Configure(PinConfig{Mode: PinSPI})
|
||||||
|
|
||||||
|
// set default frequency
|
||||||
|
if config.Frequency == 0 {
|
||||||
|
config.Frequency = 4000000
|
||||||
|
}
|
||||||
|
|
||||||
|
// div = (SPI_CFG(dev)->f_sys / (2 * frequency)) - 1;
|
||||||
|
div := CPU_FREQUENCY/(2*config.Frequency) - 1
|
||||||
|
spi.Bus.DIV.Set(div)
|
||||||
|
|
||||||
|
// set mode
|
||||||
|
switch config.Mode {
|
||||||
|
case 0:
|
||||||
|
spi.Bus.MODE.ClearBits(sifive.QSPI_MODE_PHASE)
|
||||||
|
spi.Bus.MODE.ClearBits(sifive.QSPI_MODE_POLARITY)
|
||||||
|
case 1:
|
||||||
|
spi.Bus.MODE.SetBits(sifive.QSPI_MODE_PHASE)
|
||||||
|
spi.Bus.MODE.ClearBits(sifive.QSPI_MODE_POLARITY)
|
||||||
|
case 2:
|
||||||
|
spi.Bus.MODE.ClearBits(sifive.QSPI_MODE_PHASE)
|
||||||
|
spi.Bus.MODE.SetBits(sifive.QSPI_MODE_POLARITY)
|
||||||
|
case 3:
|
||||||
|
spi.Bus.MODE.SetBits(sifive.QSPI_MODE_PHASE | sifive.QSPI_MODE_POLARITY)
|
||||||
|
default: // to mode 0
|
||||||
|
spi.Bus.MODE.ClearBits(sifive.QSPI_MODE_PHASE)
|
||||||
|
spi.Bus.MODE.ClearBits(sifive.QSPI_MODE_POLARITY)
|
||||||
|
}
|
||||||
|
|
||||||
|
// frame length
|
||||||
|
spi.Bus.FMT.SetBits(8 << sifive.QSPI_FMT_LENGTH_Pos)
|
||||||
|
|
||||||
|
// Set single line operation, by clearing all bits
|
||||||
|
spi.Bus.FMT.ClearBits(sifive.QSPI_FMT_PROTOCOL_Msk)
|
||||||
|
|
||||||
|
// set bit transfer order
|
||||||
|
if config.LSBFirst {
|
||||||
|
spi.Bus.FMT.SetBits(sifive.QSPI_FMT_ENDIAN)
|
||||||
|
} else {
|
||||||
|
spi.Bus.FMT.ClearBits(sifive.QSPI_FMT_ENDIAN)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Transfer writes/reads a single byte using the SPI interface.
|
||||||
|
func (spi SPI) Transfer(w byte) (byte, error) {
|
||||||
|
// wait for tx ready
|
||||||
|
for spi.Bus.TXDATA.HasBits(sifive.QSPI_TXDATA_FULL) {
|
||||||
|
}
|
||||||
|
|
||||||
|
// write data
|
||||||
|
spi.Bus.TXDATA.Set(uint32(w))
|
||||||
|
|
||||||
|
// wait until receive has data
|
||||||
|
for spi.Bus.RXDATA.HasBits(sifive.QSPI_RXDATA_EMPTY) {
|
||||||
|
}
|
||||||
|
|
||||||
|
// return data
|
||||||
|
return byte(spi.Bus.RXDATA.Get() & sifive.QSPI_RXDATA_DATA_Msk), nil
|
||||||
|
}
|
||||||
|
|||||||
+1
-1
@@ -1,4 +1,4 @@
|
|||||||
// +build sam stm32,!stm32f407
|
// +build sam stm32,!stm32f407 fe310
|
||||||
|
|
||||||
package machine
|
package machine
|
||||||
|
|
||||||
|
|||||||
@@ -375,6 +375,8 @@ func (t Type) Align() int {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
return alignment
|
return alignment
|
||||||
|
case Array:
|
||||||
|
return t.Elem().Align()
|
||||||
default:
|
default:
|
||||||
panic("unimplemented: alignment of type")
|
panic("unimplemented: alignment of type")
|
||||||
}
|
}
|
||||||
|
|||||||
+210
-26
@@ -37,11 +37,86 @@ func chanDebug(ch *channel) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// channelBlockedList is a list of channel operations on a specific channel which are currently blocked.
|
||||||
|
type channelBlockedList struct {
|
||||||
|
// next is a pointer to the next blocked channel operation on the same channel.
|
||||||
|
next *channelBlockedList
|
||||||
|
|
||||||
|
// t is the task associated with this channel operation.
|
||||||
|
// If this channel operation is not part of a select, then the pointer field of the state holds the data buffer.
|
||||||
|
// If this channel operation is part of a select, then the pointer field of the state holds the recieve buffer.
|
||||||
|
// If this channel operation is a receive, then the data field should be set to zero when resuming due to channel closure.
|
||||||
|
t *task
|
||||||
|
|
||||||
|
// s is a pointer to the channel select state corresponding to this operation.
|
||||||
|
// This will be nil if and only if this channel operation is not part of a select statement.
|
||||||
|
// If this is a send operation, then the send buffer can be found in this select state.
|
||||||
|
s *chanSelectState
|
||||||
|
|
||||||
|
// allSelectOps is a slice containing all of the channel operations involved with this select statement.
|
||||||
|
// Before resuming the task, all other channel operations on this select statement should be canceled by removing them from their corresponding lists.
|
||||||
|
allSelectOps []channelBlockedList
|
||||||
|
}
|
||||||
|
|
||||||
|
// remove takes the current list of blocked channel operations and removes the specified operation.
|
||||||
|
// This returns the resulting list, or nil if the resulting list is empty.
|
||||||
|
// A nil receiver is treated as an empty list.
|
||||||
|
func (b *channelBlockedList) remove(old *channelBlockedList) *channelBlockedList {
|
||||||
|
if b == old {
|
||||||
|
return b.next
|
||||||
|
}
|
||||||
|
c := b
|
||||||
|
for ; c != nil && c.next != old; c = c.next {
|
||||||
|
}
|
||||||
|
if c != nil {
|
||||||
|
c.next = old.next
|
||||||
|
}
|
||||||
|
return b
|
||||||
|
}
|
||||||
|
|
||||||
|
// detatch removes all other channel operations that are part of the same select statement.
|
||||||
|
// If the input is not part of a select statement, this is a no-op.
|
||||||
|
// This must be called before resuming any task blocked on a channel operation in order to ensure that it is not placed on the runqueue twice.
|
||||||
|
func (b *channelBlockedList) detach() {
|
||||||
|
if b.allSelectOps == nil {
|
||||||
|
// nothing to do
|
||||||
|
return
|
||||||
|
}
|
||||||
|
for i, v := range b.allSelectOps {
|
||||||
|
// cancel all other channel operations that are part of this select statement
|
||||||
|
if &b.allSelectOps[i] == b {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if v.s.ch == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
v.s.ch.blocked = v.s.ch.blocked.remove(&b.allSelectOps[i])
|
||||||
|
if v.s.ch.blocked == nil {
|
||||||
|
if v.s.value == nil {
|
||||||
|
// recv operation
|
||||||
|
if v.s.ch.state != chanStateClosed {
|
||||||
|
v.s.ch.state = chanStateEmpty
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// send operation
|
||||||
|
if v.s.ch.bufUsed == 0 {
|
||||||
|
// unbuffered channel
|
||||||
|
v.s.ch.state = chanStateEmpty
|
||||||
|
} else {
|
||||||
|
// buffered channel
|
||||||
|
v.s.ch.state = chanStateBuf
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
chanDebug(v.s.ch)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
type channel struct {
|
type channel struct {
|
||||||
elementSize uintptr // the size of one value in this channel
|
elementSize uintptr // the size of one value in this channel
|
||||||
bufSize uintptr // size of buffer (in elements)
|
bufSize uintptr // size of buffer (in elements)
|
||||||
state chanState
|
state chanState
|
||||||
blocked *task
|
blocked *channelBlockedList
|
||||||
bufHead uintptr // head index of buffer (next push index)
|
bufHead uintptr // head index of buffer (next push index)
|
||||||
bufTail uintptr // tail index of buffer (next pop index)
|
bufTail uintptr // tail index of buffer (next pop index)
|
||||||
bufUsed uintptr // number of elements currently in buffer
|
bufUsed uintptr // number of elements currently in buffer
|
||||||
@@ -58,6 +133,63 @@ func chanMake(elementSize uintptr, bufSize uintptr) *channel {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// resumeRX resumes the next receiver and returns the destination pointer.
|
||||||
|
// If the ok value is true, then the caller is expected to store a value into this pointer.
|
||||||
|
func (ch *channel) resumeRX(ok bool) unsafe.Pointer {
|
||||||
|
// pop a blocked goroutine off the stack
|
||||||
|
var b *channelBlockedList
|
||||||
|
b, ch.blocked = ch.blocked, ch.blocked.next
|
||||||
|
|
||||||
|
// get destination pointer
|
||||||
|
dst := b.t.state().ptr
|
||||||
|
|
||||||
|
if !ok {
|
||||||
|
// the result value is zero
|
||||||
|
memzero(dst, ch.elementSize)
|
||||||
|
b.t.state().data = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
if b.s != nil {
|
||||||
|
// tell the select op which case resumed
|
||||||
|
b.t.state().ptr = unsafe.Pointer(b.s)
|
||||||
|
|
||||||
|
// detach associated operations
|
||||||
|
b.detach()
|
||||||
|
}
|
||||||
|
|
||||||
|
// push task onto runqueue
|
||||||
|
runqueuePushBack(b.t)
|
||||||
|
|
||||||
|
return dst
|
||||||
|
}
|
||||||
|
|
||||||
|
// resumeTX resumes the next sender and returns the source pointer.
|
||||||
|
// The caller is expected to read from the value in this pointer before yielding.
|
||||||
|
func (ch *channel) resumeTX() unsafe.Pointer {
|
||||||
|
// pop a blocked goroutine off the stack
|
||||||
|
var b *channelBlockedList
|
||||||
|
b, ch.blocked = ch.blocked, ch.blocked.next
|
||||||
|
|
||||||
|
// get source pointer
|
||||||
|
src := b.t.state().ptr
|
||||||
|
|
||||||
|
if b.s != nil {
|
||||||
|
// use state's source pointer
|
||||||
|
src = b.s.value
|
||||||
|
|
||||||
|
// tell the select op which case resumed
|
||||||
|
b.t.state().ptr = unsafe.Pointer(b.s)
|
||||||
|
|
||||||
|
// detach associated operations
|
||||||
|
b.detach()
|
||||||
|
}
|
||||||
|
|
||||||
|
// push task onto runqueue
|
||||||
|
runqueuePushBack(b.t)
|
||||||
|
|
||||||
|
return src
|
||||||
|
}
|
||||||
|
|
||||||
// push value to end of channel if space is available
|
// push value to end of channel if space is available
|
||||||
// returns whether there was space for the value in the buffer
|
// returns whether there was space for the value in the buffer
|
||||||
func (ch *channel) push(value unsafe.Pointer) bool {
|
func (ch *channel) push(value unsafe.Pointer) bool {
|
||||||
@@ -151,12 +283,10 @@ func (ch *channel) trySend(value unsafe.Pointer) bool {
|
|||||||
return false
|
return false
|
||||||
case chanStateRecv:
|
case chanStateRecv:
|
||||||
// unblock reciever
|
// unblock reciever
|
||||||
receiver := unblockChain(&ch.blocked, nil)
|
dst := ch.resumeRX(true)
|
||||||
|
|
||||||
// copy value to reciever
|
// copy value to reciever
|
||||||
receiverState := receiver.state()
|
memcpy(dst, value, ch.elementSize)
|
||||||
memcpy(receiverState.ptr, value, ch.elementSize)
|
|
||||||
receiverState.data = 1 // commaOk = true
|
|
||||||
|
|
||||||
// change state to empty if there are no more receivers
|
// change state to empty if there are no more receivers
|
||||||
if ch.blocked == nil {
|
if ch.blocked == nil {
|
||||||
@@ -191,9 +321,11 @@ func (ch *channel) tryRecv(value unsafe.Pointer) (bool, bool) {
|
|||||||
// try to pop the value directly from the buffer
|
// try to pop the value directly from the buffer
|
||||||
if ch.pop(value) {
|
if ch.pop(value) {
|
||||||
// unblock next sender if applicable
|
// unblock next sender if applicable
|
||||||
if sender := unblockChain(&ch.blocked, nil); sender != nil {
|
if ch.blocked != nil {
|
||||||
|
src := ch.resumeTX()
|
||||||
|
|
||||||
// push sender's value into buffer
|
// push sender's value into buffer
|
||||||
ch.push(sender.state().ptr)
|
ch.push(src)
|
||||||
|
|
||||||
if ch.blocked == nil {
|
if ch.blocked == nil {
|
||||||
// last sender unblocked - update state
|
// last sender unblocked - update state
|
||||||
@@ -207,10 +339,12 @@ func (ch *channel) tryRecv(value unsafe.Pointer) (bool, bool) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return true, true
|
return true, true
|
||||||
} else if sender := unblockChain(&ch.blocked, nil); sender != nil {
|
} else if ch.blocked != nil {
|
||||||
// unblock next sender if applicable
|
// unblock next sender if applicable
|
||||||
|
src := ch.resumeTX()
|
||||||
|
|
||||||
// copy sender's value
|
// copy sender's value
|
||||||
memcpy(value, sender.state().ptr, ch.elementSize)
|
memcpy(value, src, ch.elementSize)
|
||||||
|
|
||||||
if ch.blocked == nil {
|
if ch.blocked == nil {
|
||||||
// last sender unblocked - update state
|
// last sender unblocked - update state
|
||||||
@@ -294,7 +428,10 @@ func chanSend(ch *channel, value unsafe.Pointer) {
|
|||||||
ch.state = chanStateSend
|
ch.state = chanStateSend
|
||||||
senderState := sender.state()
|
senderState := sender.state()
|
||||||
senderState.ptr = value
|
senderState.ptr = value
|
||||||
ch.blocked, senderState.next = sender, ch.blocked
|
ch.blocked = &channelBlockedList{
|
||||||
|
next: ch.blocked,
|
||||||
|
t: sender,
|
||||||
|
}
|
||||||
chanDebug(ch)
|
chanDebug(ch)
|
||||||
yield()
|
yield()
|
||||||
senderState.ptr = nil
|
senderState.ptr = nil
|
||||||
@@ -320,8 +457,11 @@ func chanRecv(ch *channel, value unsafe.Pointer) bool {
|
|||||||
receiver := getCoroutine()
|
receiver := getCoroutine()
|
||||||
ch.state = chanStateRecv
|
ch.state = chanStateRecv
|
||||||
receiverState := receiver.state()
|
receiverState := receiver.state()
|
||||||
receiverState.ptr, receiverState.data = value, 0
|
receiverState.ptr, receiverState.data = value, 1
|
||||||
ch.blocked, receiverState.next = receiver, ch.blocked
|
ch.blocked = &channelBlockedList{
|
||||||
|
next: ch.blocked,
|
||||||
|
t: receiver,
|
||||||
|
}
|
||||||
chanDebug(ch)
|
chanDebug(ch)
|
||||||
yield()
|
yield()
|
||||||
ok := receiverState.data == 1
|
ok := receiverState.data == 1
|
||||||
@@ -348,15 +488,9 @@ func chanClose(ch *channel) {
|
|||||||
runtimePanic("close channel during send")
|
runtimePanic("close channel during send")
|
||||||
case chanStateRecv:
|
case chanStateRecv:
|
||||||
// unblock all receivers with the zero value
|
// unblock all receivers with the zero value
|
||||||
for rx := unblockChain(&ch.blocked, nil); rx != nil; rx = unblockChain(&ch.blocked, nil) {
|
ch.state = chanStateClosed
|
||||||
// get receiver state
|
for ch.blocked != nil {
|
||||||
state := rx.state()
|
ch.resumeRX(false)
|
||||||
|
|
||||||
// store the zero value
|
|
||||||
memzero(state.ptr, ch.elementSize)
|
|
||||||
|
|
||||||
// set the comma-ok value to false (channel closed)
|
|
||||||
state.data = 0
|
|
||||||
}
|
}
|
||||||
case chanStateEmpty, chanStateBuf:
|
case chanStateEmpty, chanStateBuf:
|
||||||
// Easy case. No available sender or receiver.
|
// Easy case. No available sender or receiver.
|
||||||
@@ -371,7 +505,60 @@ func chanClose(ch *channel) {
|
|||||||
//
|
//
|
||||||
// TODO: do this in a round-robin fashion (as specified in the Go spec) instead
|
// TODO: do this in a round-robin fashion (as specified in the Go spec) instead
|
||||||
// of picking the first one that can proceed.
|
// of picking the first one that can proceed.
|
||||||
func chanSelect(recvbuf unsafe.Pointer, states []chanSelectState, blocking bool) (uintptr, bool) {
|
func chanSelect(recvbuf unsafe.Pointer, states []chanSelectState, ops []channelBlockedList) (uintptr, bool) {
|
||||||
|
if selected, ok := tryChanSelect(recvbuf, states); selected != ^uintptr(0) {
|
||||||
|
// one channel was immediately ready
|
||||||
|
return selected, ok
|
||||||
|
}
|
||||||
|
|
||||||
|
// construct blocked operations
|
||||||
|
for i, v := range states {
|
||||||
|
ops[i] = channelBlockedList{
|
||||||
|
next: v.ch.blocked,
|
||||||
|
t: getCoroutine(),
|
||||||
|
s: &states[i],
|
||||||
|
allSelectOps: ops,
|
||||||
|
}
|
||||||
|
v.ch.blocked = &ops[i]
|
||||||
|
if v.value == nil {
|
||||||
|
// recv
|
||||||
|
switch v.ch.state {
|
||||||
|
case chanStateEmpty:
|
||||||
|
v.ch.state = chanStateRecv
|
||||||
|
case chanStateRecv:
|
||||||
|
// already in correct state
|
||||||
|
default:
|
||||||
|
runtimePanic("invalid channel state")
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// send
|
||||||
|
switch v.ch.state {
|
||||||
|
case chanStateEmpty:
|
||||||
|
v.ch.state = chanStateSend
|
||||||
|
case chanStateSend:
|
||||||
|
// already in correct state
|
||||||
|
case chanStateBuf:
|
||||||
|
// already in correct state
|
||||||
|
default:
|
||||||
|
runtimePanic("invalid channel state")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
chanDebug(v.ch)
|
||||||
|
}
|
||||||
|
|
||||||
|
// expose rx buffer
|
||||||
|
getCoroutine().state().ptr = recvbuf
|
||||||
|
getCoroutine().state().data = 1
|
||||||
|
|
||||||
|
// wait for one case to fire
|
||||||
|
yield()
|
||||||
|
|
||||||
|
// figure out which one fired and return the ok value
|
||||||
|
return (uintptr(getCoroutine().state().ptr) - uintptr(unsafe.Pointer(&states[0]))) / unsafe.Sizeof(chanSelectState{}), getCoroutine().state().data != 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// tryChanSelect is like chanSelect, but it does a non-blocking select operation.
|
||||||
|
func tryChanSelect(recvbuf unsafe.Pointer, states []chanSelectState) (uintptr, bool) {
|
||||||
// See whether we can receive from one of the channels.
|
// See whether we can receive from one of the channels.
|
||||||
for i, state := range states {
|
for i, state := range states {
|
||||||
if state.value == nil {
|
if state.value == nil {
|
||||||
@@ -389,8 +576,5 @@ func chanSelect(recvbuf unsafe.Pointer, states []chanSelectState, blocking bool)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if !blocking {
|
return ^uintptr(0), false
|
||||||
return ^uintptr(0), false
|
|
||||||
}
|
|
||||||
panic("unimplemented: blocking select")
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,7 +5,10 @@ package runtime
|
|||||||
// Interfaces are represented as a pair of {typecode, value}, where value can be
|
// Interfaces are represented as a pair of {typecode, value}, where value can be
|
||||||
// anything (including non-pointers).
|
// anything (including non-pointers).
|
||||||
|
|
||||||
import "unsafe"
|
import (
|
||||||
|
"reflect"
|
||||||
|
"unsafe"
|
||||||
|
)
|
||||||
|
|
||||||
type _interface struct {
|
type _interface struct {
|
||||||
typecode uintptr
|
typecode uintptr
|
||||||
@@ -23,17 +26,55 @@ func decomposeInterface(i _interface) (uintptr, unsafe.Pointer) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Return true iff both interfaces are equal.
|
// Return true iff both interfaces are equal.
|
||||||
func interfaceEqual(x, y _interface) bool {
|
func interfaceEqual(x, y interface{}) bool {
|
||||||
if x.typecode != y.typecode {
|
return reflectValueEqual(reflect.ValueOf(x), reflect.ValueOf(y))
|
||||||
// Different dynamic type so always unequal.
|
}
|
||||||
|
|
||||||
|
func reflectValueEqual(x, y reflect.Value) bool {
|
||||||
|
if x.Type() == 0 || y.Type() == 0 {
|
||||||
|
// One of them is nil.
|
||||||
|
return x.Type() == y.Type()
|
||||||
|
}
|
||||||
|
|
||||||
|
if x.Type() != y.Type() {
|
||||||
|
// The type is not the same, which means the interfaces are definitely
|
||||||
|
// not the same.
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
if x.typecode == 0 {
|
|
||||||
// Both interfaces are nil, so they are equal.
|
switch x.Type().Kind() {
|
||||||
|
case reflect.Bool:
|
||||||
|
return x.Bool() == y.Bool()
|
||||||
|
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
||||||
|
return x.Int() == y.Int()
|
||||||
|
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
|
||||||
|
return x.Uint() == y.Uint()
|
||||||
|
case reflect.Float32, reflect.Float64:
|
||||||
|
return x.Float() == y.Float()
|
||||||
|
case reflect.Complex64, reflect.Complex128:
|
||||||
|
return x.Complex() == y.Complex()
|
||||||
|
case reflect.String:
|
||||||
|
return x.String() == y.String()
|
||||||
|
case reflect.Chan, reflect.Ptr, reflect.UnsafePointer:
|
||||||
|
return x.Pointer() == y.Pointer()
|
||||||
|
case reflect.Array:
|
||||||
|
for i := 0; i < x.Len(); i++ {
|
||||||
|
if !reflectValueEqual(x.Index(i), y.Index(i)) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
return true
|
return true
|
||||||
|
case reflect.Struct:
|
||||||
|
for i := 0; i < x.NumField(); i++ {
|
||||||
|
if !reflectValueEqual(x.Field(i), y.Field(i)) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
default:
|
||||||
|
runtimePanic("comparing un-comparable type")
|
||||||
|
return false // unreachable
|
||||||
}
|
}
|
||||||
// TODO: depends on reflection.
|
|
||||||
panic("unimplemented: interface equality")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// interfaceTypeAssert is called when a type assert without comma-ok still
|
// interfaceTypeAssert is called when a type assert without comma-ok still
|
||||||
|
|||||||
+15
-2
@@ -83,14 +83,27 @@ func nanotime() int64 {
|
|||||||
return int64(ticks()) * tickMicros
|
return int64(ticks()) * tickMicros
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// timeOffset is how long the monotonic clock started after the Unix epoch. It
|
||||||
|
// should be a positive integer under normal operation or zero when it has not
|
||||||
|
// been set.
|
||||||
|
var timeOffset int64
|
||||||
|
|
||||||
//go:linkname now time.now
|
//go:linkname now time.now
|
||||||
func now() (sec int64, nsec int32, mono int64) {
|
func now() (sec int64, nsec int32, mono int64) {
|
||||||
mono = nanotime()
|
mono = nanotime()
|
||||||
sec = mono / (1000 * 1000 * 1000)
|
sec = (mono + timeOffset) / (1000 * 1000 * 1000)
|
||||||
nsec = int32(mono - sec*(1000*1000*1000))
|
nsec = int32((mono + timeOffset) - sec*(1000*1000*1000))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// AdjustTimeOffset adds the given offset to the built-in time offset. A
|
||||||
|
// positive value adds to the time (skipping some time), a negative value moves
|
||||||
|
// the clock into the past.
|
||||||
|
func AdjustTimeOffset(offset int64) {
|
||||||
|
// TODO: do this atomically?
|
||||||
|
timeOffset += offset
|
||||||
|
}
|
||||||
|
|
||||||
// Copied from the Go runtime source code.
|
// Copied from the Go runtime source code.
|
||||||
//go:linkname os_sigpipe os.sigpipe
|
//go:linkname os_sigpipe os.sigpipe
|
||||||
func os_sigpipe() {
|
func os_sigpipe() {
|
||||||
|
|||||||
@@ -81,21 +81,24 @@ func initClocks() {
|
|||||||
for !sam.OSCCTRL.STATUS.HasBits(sam.OSCCTRL_STATUS_DFLLRDY) {
|
for !sam.OSCCTRL.STATUS.HasBits(sam.OSCCTRL_STATUS_DFLLRDY) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// set GCLK7 to use DFLL48M as clock source
|
// set GCLK7 to run at 2MHz, using DFLL48M as clock source
|
||||||
|
// GCLK7 = 48MHz / 24 = 2MHz
|
||||||
sam.GCLK.GENCTRL[7].Set((sam.GCLK_GENCTRL_SRC_DFLL << sam.GCLK_GENCTRL_SRC_Pos) |
|
sam.GCLK.GENCTRL[7].Set((sam.GCLK_GENCTRL_SRC_DFLL << sam.GCLK_GENCTRL_SRC_Pos) |
|
||||||
(24 << sam.GCLK_GENCTRL_DIVSEL_Pos) |
|
(24 << sam.GCLK_GENCTRL_DIV_Pos) |
|
||||||
sam.GCLK_GENCTRL_GENEN)
|
sam.GCLK_GENCTRL_GENEN)
|
||||||
for sam.GCLK.SYNCBUSY.HasBits(sam.GCLK_SYNCBUSY_GENCTRL_GCLK7) {
|
for sam.GCLK.SYNCBUSY.HasBits(sam.GCLK_SYNCBUSY_GENCTRL_GCLK7) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set up the PLLs
|
// Set up the PLLs
|
||||||
|
|
||||||
// Set PLL0 at 120MHz
|
// Set PLL0 to run at 120MHz, using GCLK7 as clock source
|
||||||
sam.GCLK.PCHCTRL[1].Set(sam.GCLK_PCHCTRL_CHEN |
|
sam.GCLK.PCHCTRL[1].Set(sam.GCLK_PCHCTRL_CHEN |
|
||||||
(sam.GCLK_PCHCTRL_GEN_GCLK7 << sam.GCLK_PCHCTRL_GEN_Pos))
|
(sam.GCLK_PCHCTRL_GEN_GCLK7 << sam.GCLK_PCHCTRL_GEN_Pos))
|
||||||
|
|
||||||
|
// multiplier = 59 + 1 + (0/32) = 60
|
||||||
|
// PLL0 = 2MHz * 60 = 120MHz
|
||||||
sam.OSCCTRL.DPLL[0].DPLLRATIO.Set((0x0 << sam.OSCCTRL_DPLL_DPLLRATIO_LDRFRAC_Pos) |
|
sam.OSCCTRL.DPLL[0].DPLLRATIO.Set((0x0 << sam.OSCCTRL_DPLL_DPLLRATIO_LDRFRAC_Pos) |
|
||||||
(59 << sam.OSCCTRL_DPLL_DPLLRATIO_LDRFRAC_Pos))
|
(59 << sam.OSCCTRL_DPLL_DPLLRATIO_LDR_Pos))
|
||||||
for sam.OSCCTRL.DPLL[0].DPLLSYNCBUSY.HasBits(sam.OSCCTRL_DPLL_DPLLSYNCBUSY_DPLLRATIO) {
|
for sam.OSCCTRL.DPLL[0].DPLLSYNCBUSY.HasBits(sam.OSCCTRL_DPLL_DPLLSYNCBUSY_DPLLRATIO) {
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -108,12 +111,14 @@ func initClocks() {
|
|||||||
!sam.OSCCTRL.DPLL[0].DPLLSTATUS.HasBits(sam.OSCCTRL_DPLL_DPLLSTATUS_LOCK) {
|
!sam.OSCCTRL.DPLL[0].DPLLSTATUS.HasBits(sam.OSCCTRL_DPLL_DPLLSTATUS_LOCK) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// // Set PLL1 to 100MHz
|
// Set PLL1 to run at 100MHz, using GCLK7 as clock source
|
||||||
sam.GCLK.PCHCTRL[2].Set(sam.GCLK_PCHCTRL_CHEN |
|
sam.GCLK.PCHCTRL[2].Set(sam.GCLK_PCHCTRL_CHEN |
|
||||||
(sam.GCLK_PCHCTRL_GEN_GCLK7 << sam.GCLK_PCHCTRL_GEN_Pos))
|
(sam.GCLK_PCHCTRL_GEN_GCLK7 << sam.GCLK_PCHCTRL_GEN_Pos))
|
||||||
|
|
||||||
|
// multiplier = 49 + 1 + (0/32) = 50
|
||||||
|
// PLL1 = 2MHz * 50 = 100MHz
|
||||||
sam.OSCCTRL.DPLL[1].DPLLRATIO.Set((0x0 << sam.OSCCTRL_DPLL_DPLLRATIO_LDRFRAC_Pos) |
|
sam.OSCCTRL.DPLL[1].DPLLRATIO.Set((0x0 << sam.OSCCTRL_DPLL_DPLLRATIO_LDRFRAC_Pos) |
|
||||||
(49 << sam.OSCCTRL_DPLL_DPLLRATIO_LDR_Pos)) // this means 100 Mhz?
|
(49 << sam.OSCCTRL_DPLL_DPLLRATIO_LDR_Pos))
|
||||||
for sam.OSCCTRL.DPLL[1].DPLLSYNCBUSY.HasBits(sam.OSCCTRL_DPLL_DPLLSYNCBUSY_DPLLRATIO) {
|
for sam.OSCCTRL.DPLL[1].DPLLSYNCBUSY.HasBits(sam.OSCCTRL_DPLL_DPLLSYNCBUSY_DPLLRATIO) {
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -183,7 +188,7 @@ func initRTC() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// set to use ulp 32k oscillator
|
// set to use ulp 32k oscillator
|
||||||
sam.OSC32KCTRL.OSCULP32K.Set(sam.OSC32KCTRL_OSCULP32K_EN32K)
|
sam.OSC32KCTRL.OSCULP32K.SetBits(sam.OSC32KCTRL_OSCULP32K_EN32K)
|
||||||
sam.OSC32KCTRL.RTCCTRL.Set(sam.OSC32KCTRL_RTCCTRL_RTCSEL_ULP32K)
|
sam.OSC32KCTRL.RTCCTRL.Set(sam.OSC32KCTRL_RTCCTRL_RTCSEL_ULP32K)
|
||||||
|
|
||||||
// set Mode0 to 32-bit counter (mode 0) with prescaler 1 and GCLK2 is 32KHz/1
|
// set Mode0 to 32-bit counter (mode 0) with prescaler 1 and GCLK2 is 32KHz/1
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user