Compare commits

..

5 Commits

Author SHA1 Message Date
Ayke van Laethem ca7b95039d interp: do not rely on fixed type names
After merging multiple LLVM modules into one, types may have been
renamed. Therefore, the interp package should not rely on types to have
any particular name.
2020-05-03 15:43:45 +02:00
Ayke van Laethem 7939c060ce compiler: include type information about the runtime in the compiler
These types are often known to the compiler already. Moving them into
the compiler is an important step for two related reasons:

  * It makes the compiler better testable. Together with
    https://github.com/tinygo-org/tinygo/pull/1008 it will make it
    possible to test the compiler without having to load the runtime
    package.
  * It makes it easier to compile packages independently as the type
    information of the runtime package doesn't need to be present.

I had to use hack to get this to work well: internal/task.Task is now an
opaque struct. This is necessary because there is a dependency from
*runtime.channel -> *runtime.channelBlockedList -> *internal/task.Task.
I don't want to include the definition of the internal/task.Task struct
in the compiler directly as that would make changing the internal/task
package a lot harder and the compiler doesn't need to know the layout of
that struct anyway.
2020-05-03 15:43:45 +02:00
Ayke van Laethem dd04f34059 compiler: integrate ir package into compiler package
The ir package has long lost its original purpose (which was doing some
analysis and optimization on the Go SSA directly). There is very little
of it left, which is best integrated directly in the compiler package to
avoid unnecessary abstraction.
2020-05-03 15:43:45 +02:00
Ayke van Laethem f58e75c386 compiler: add tests
This commit adds a very small test case. More importantly, it adds a
framework for other tests to be added in the future.
2020-05-03 15:43:45 +02:00
Ayke van Laethem b8db79f6a6 compiler: compile all functions/methods, remove SimpleDCE
This is important because once we move to compiling packages
independently, SimpleDCE can't work anymore. Instead we'll have to
compile all parts of a package and cache that for later reuse.
2020-05-03 15:43:44 +02:00
401 changed files with 4056 additions and 16689 deletions
+29 -130
View File
@@ -37,47 +37,28 @@ commands:
sudo tar -C /usr/local -xf node-v10.15.1-linux-x64.tar.xz
sudo ln -s /usr/local/node-v10.15.1-linux-x64/bin/node /usr/bin/node
rm node-v10.15.1-linux-x64.tar.xz
install-chrome:
steps:
- run:
name: "Install Chrome"
command: |
wget https://dl.google.com/linux/direct/google-chrome-stable_current_amd64.deb
sudo apt install ./google-chrome-stable_current_amd64.deb
install-xtensa-toolchain:
parameters:
variant:
type: string
steps:
- run:
name: "Install Xtensa toolchain"
command: |
curl -L https://github.com/espressif/crosstool-NG/releases/download/esp-2020r2/xtensa-esp32-elf-gcc8_2_0-esp-2020r2-<<parameters.variant>>.tar.gz -o xtensa-esp32-elf-gcc8_2_0-esp-2020r2-<<parameters.variant>>.tar.gz
sudo tar -C /usr/local -xf xtensa-esp32-elf-gcc8_2_0-esp-2020r2-<<parameters.variant>>.tar.gz
sudo ln -s /usr/local/xtensa-esp32-elf/bin/xtensa-esp32-elf-ld /usr/local/bin/xtensa-esp32-elf-ld
rm xtensa-esp32-elf-gcc8_2_0-esp-2020r2-<<parameters.variant>>.tar.gz
llvm-source-linux:
steps:
- restore_cache:
keys:
- llvm-source-10-v1
- llvm-source-10-v0
- run:
name: "Fetch LLVM source"
command: make llvm-source
- save_cache:
key: llvm-source-10-v1
key: llvm-source-10-v0
paths:
- llvm-project
build-wasi-libc:
steps:
- restore_cache:
keys:
- wasi-libc-sysroot-v3
- wasi-libc-sysroot-v2
- run:
name: "Build wasi-libc"
command: make wasi-libc
- save_cache:
key: wasi-libc-sysroot-v3
key: wasi-libc-sysroot-v2
paths:
- lib/wasi-libc/sysroot
test-linux:
@@ -90,7 +71,6 @@ commands:
- apt-dependencies:
llvm: "<<parameters.llvm>>"
- install-node
- install-chrome
- restore_cache:
keys:
- go-cache-v2-{{ checksum "go.mod" }}-{{ .Environment.CIRCLE_PREVIOUS_BUILD_NUM }}
@@ -99,21 +79,20 @@ commands:
- run: go install -tags=llvm<<parameters.llvm>> .
- restore_cache:
keys:
- wasi-libc-sysroot-systemclang-v2
- wasi-libc-sysroot-systemclang-v1
- run: make wasi-libc
- save_cache:
key: wasi-libc-sysroot-systemclang-v2
key: wasi-libc-sysroot-systemclang-v1
paths:
- lib/wasi-libc/sysroot
- run: go test -v -tags=llvm<<parameters.llvm>> ./cgo ./compileopts ./interp ./transform .
- run: go test -v -tags=llvm<<parameters.llvm>> ./cgo ./compileopts ./compiler ./interp ./transform .
- run: make gen-device -j4
- run: make smoketest XTENSA=0
- run: make tinygo-test
- run: make wasmtest
- run: make smoketest
- save_cache:
key: go-cache-v2-{{ checksum "go.mod" }}-{{ .Environment.CIRCLE_BUILD_NUM }}
paths:
- ~/.cache/go-build
- ~/.cache/tinygo
- /go/pkg/mod
- run: make fmt-check
assert-test-linux:
@@ -134,8 +113,6 @@ commands:
gcc-avr \
avr-libc
- install-node
- install-xtensa-toolchain:
variant: "linux-amd64"
- restore_cache:
keys:
- go-cache-v2-{{ checksum "go.mod" }}-{{ .Environment.CIRCLE_PREVIOUS_BUILD_NUM }}
@@ -143,7 +120,7 @@ commands:
- llvm-source-linux
- restore_cache:
keys:
- llvm-build-10-linux-v1-assert
- llvm-build-10-linux-v0-assert
- run:
name: "Build LLVM"
command: |
@@ -161,7 +138,7 @@ commands:
make ASSERT=1 llvm-build
fi
- save_cache:
key: llvm-build-10-linux-v1-assert
key: llvm-build-10-linux-v0-assert
paths:
llvm-build
- run: make ASSERT=1
@@ -173,6 +150,7 @@ commands:
key: go-cache-v2-{{ checksum "go.mod" }}-{{ .Environment.CIRCLE_BUILD_NUM }}
paths:
- ~/.cache/go-build
- ~/.cache/tinygo
- /go/pkg/mod
- run: make gen-device -j4
- run: make smoketest TINYGO=build/tinygo
@@ -194,8 +172,6 @@ commands:
gcc-avr \
avr-libc
- install-node
- install-xtensa-toolchain:
variant: "linux-amd64"
- restore_cache:
keys:
- go-cache-v2-{{ checksum "go.mod" }}-{{ .Environment.CIRCLE_PREVIOUS_BUILD_NUM }}
@@ -203,7 +179,7 @@ commands:
- llvm-source-linux
- restore_cache:
keys:
- llvm-build-10-linux-v1
- llvm-build-10-linux-v0
- run:
name: "Build LLVM"
command: |
@@ -221,32 +197,25 @@ commands:
make llvm-build
fi
- save_cache:
key: llvm-build-10-linux-v1
key: llvm-build-10-linux-v0
paths:
llvm-build
- build-wasi-libc
- run:
name: "Test TinyGo"
command: make test
- run:
name: "Install fpm"
command: |
sudo apt-get install ruby ruby-dev
sudo gem install --no-document fpm
- run:
name: "Build TinyGo release"
command: |
make release deb -j3
make release -j3
cp -p build/release.tar.gz /tmp/tinygo.linux-amd64.tar.gz
cp -p build/release.deb /tmp/tinygo_amd64.deb
- store_artifacts:
path: /tmp/tinygo.linux-amd64.tar.gz
- store_artifacts:
path: /tmp/tinygo_amd64.deb
- save_cache:
key: go-cache-v2-{{ checksum "go.mod" }}-{{ .Environment.CIRCLE_BUILD_NUM }}
paths:
- ~/.cache/go-build
- ~/.cache/tinygo
- /go/pkg/mod
- run:
name: "Extract release tarball"
@@ -267,25 +236,23 @@ commands:
sudo tar -C /usr/local -xzf go1.14.darwin-amd64.tar.gz
ln -s /usr/local/go/bin/go /usr/local/bin/go
HOMEBREW_NO_AUTO_UPDATE=1 brew install qemu
- install-xtensa-toolchain:
variant: "macos"
- restore_cache:
keys:
- go-cache-macos-v2-{{ checksum "go.mod" }}-{{ .Environment.CIRCLE_PREVIOUS_BUILD_NUM }}
- go-cache-macos-v2-{{ checksum "go.mod" }}
- restore_cache:
keys:
- llvm-source-10-macos-v1
- llvm-source-10-macos-v0
- run:
name: "Fetch LLVM source"
command: make llvm-source
- save_cache:
key: llvm-source-10-macos-v1
key: llvm-source-10-macos-v0
paths:
- llvm-project
- restore_cache:
keys:
- llvm-build-10-macos-v1
- llvm-build-10-macos-v0
- run:
name: "Build LLVM"
command: |
@@ -297,17 +264,17 @@ commands:
make llvm-build
fi
- save_cache:
key: llvm-build-10-macos-v1
key: llvm-build-10-macos-v0
paths:
llvm-build
- restore_cache:
keys:
- wasi-libc-sysroot-macos-v2
- wasi-libc-sysroot-macos-v1
- run:
name: "Build wasi-libc"
command: make wasi-libc
- save_cache:
key: wasi-libc-sysroot-macos-v2
key: wasi-libc-sysroot-macos-v1
paths:
- lib/wasi-libc/sysroot
- run:
@@ -327,66 +294,19 @@ commands:
tar -C /usr/local/opt -xf /tmp/tinygo.darwin-amd64.tar.gz
ln -s /usr/local/opt/tinygo/bin/tinygo /usr/local/bin/tinygo
tinygo version
- run:
name: "Download SiFive GNU toolchain"
command: |
curl -O https://static.dev.sifive.com/dev-tools/riscv64-unknown-elf-gcc-8.2.0-2019.05.3-x86_64-apple-darwin.tar.gz
sudo tar -C /usr/local --strip-components=1 -xf riscv64-unknown-elf-gcc-8.2.0-2019.05.3-x86_64-apple-darwin.tar.gz
- run: make smoketest AVR=0
- save_cache:
key: go-cache-macos-v2-{{ checksum "go.mod" }}-{{ .Environment.CIRCLE_BUILD_NUM }}
paths:
- ~/.cache/go-build
- ~/.cache/tinygo
- /go/pkg/mod
arch-release:
steps:
- run:
name: Install dependencies
command: pacman -Sy --noconfirm openssh git pacman-contrib binutils
- run:
name: Create TinyGo user
command: useradd -m tinygo
- run:
name: Start SSH Agent
user: tinygo
command: eval $(ssh-agent -s)
- run:
name: Add ARCH_RELEASE_SSH_PRIVATE_KEY identity
user: tinygo
command: echo "${ARCH_RELEASE_SSH_PRIVATE_KEY}" | tr -d '\r' | ssh-add -
- run:
name: Create SSH directory
user: tinygo
command: mkdir -p ~/.ssh && chmod 700 ~/.ssh
- run:
name: Add aur.archlinux.org to known hosts
user: tinygo
command: ssh-keyscan aur.archlinux.org >> ~/.ssh/known_hosts
- run:
name: Clone tinygo-bin repo
user: tinygo
command: git clone ssh://aur@aur.archlinux.org/tinygo-bin.git ~/tinygo-bin
- run:
name: Update package version
user: tinygo
command: sed -i -E "s/(pkgver=)(.*)$/\1${CIRCLE_TAG}/" ~/tinygo-bin/PKGBUILD
- run:
name: Update file checksums
user: tinygo
command: cd ~/tinygo-bin && updpkgsums
- run:
name: Update .SRCINFO
user: tinygo
command: cd ~/tinygo-bin && makepkg --printsrcinfo > .SRCINFO
# Commit the update
- run:
name: Set git commit config
user: tinygo
command: |
git config --global user.email "tinygo-bot@tinygo.org" &&
git config --global user.name "TinyGo Release Bot"
- run:
name: Commit and push changes
user: tinygo
command: |
cd ~/tinygo-bin &&
git commit -a -m "Update tinygo-bin to v${CIRCLE_TAG}" &&
git push origin master
jobs:
test-llvm9-go111:
@@ -413,12 +333,6 @@ jobs:
steps:
- test-linux:
llvm: "10"
test-llvm10-go115:
docker:
- image: circleci/golang:1.15-buster
steps:
- test-linux:
llvm: "10"
assert-test-linux:
docker:
- image: circleci/golang:1.14-stretch
@@ -434,11 +348,6 @@ jobs:
xcode: "10.1.0"
steps:
- build-macos
arch-release:
docker:
- image: archlinux:latest
steps:
- arch-release
@@ -450,16 +359,6 @@ workflows:
- test-llvm10-go112
- test-llvm10-go113
- test-llvm10-go114
- test-llvm10-go115
- build-linux
- build-macos
- assert-test-linux
release:
jobs:
- arch-release:
filters:
branches:
ignore: /.*/
tags:
# Runs on every semver release
only: /v[0-9]+(\.[0-9]+)*(-.*)*/
-5
View File
@@ -3,19 +3,14 @@ docs/_build
src/device/avr/*.go
src/device/avr/*.ld
src/device/avr/*.s
src/device/esp/*.go
src/device/nrf/*.go
src/device/nrf/*.s
src/device/nxp/*.go
src/device/nxp/*.s
src/device/sam/*.go
src/device/sam/*.s
src/device/sifive/*.go
src/device/sifive/*.s
src/device/stm32/*.go
src/device/stm32/*.s
src/device/kendryte/*.go
src/device/kendryte/*.s
vendor
llvm-build
llvm-project
+1 -1
View File
@@ -9,7 +9,7 @@
url = https://github.com/avr-rust/avr-mcu.git
[submodule "lib/cmsis-svd"]
path = lib/cmsis-svd
url = https://github.com/tinygo-org/cmsis-svd
url = https://github.com/posborne/cmsis-svd
[submodule "lib/compiler-rt"]
path = lib/compiler-rt
url = https://github.com/llvm-mirror/compiler-rt.git
-12
View File
@@ -10,14 +10,6 @@ This guide describes how to statically link TinyGo against LLVM, libclang and
lld so that the binary can be easily moved between systems. It also shows how to
build a release tarball that includes this binary and all necessary extra files.
**Note**: this documentation describes how to build a statically linked release
tarball. If you want to develop TinyGo, you will probably want to follow a
different guide:
* [Linux](https://tinygo.org/getting-started/linux/#source-install)
* [macOS](https://tinygo.org/getting-started/macos/#source-install)
* [Windows](https://tinygo.org/getting-started/windows/#source-install)
## Dependencies
LLVM, Clang and LLD are quite light on dependencies, requiring only standard
@@ -92,10 +84,6 @@ Now that we have a working static build, it's time to make a release tarball:
make release
If you did not clone the repository with the `--recursive` option, you will get errors until you initialize the project submodules:
git submodule update --init
The release tarball is stored in build/release.tar.gz, and can be extracted with
the following command (for example in ~/lib):
-169
View File
@@ -1,172 +1,3 @@
0.15.0
---
* **command-line**
- add cached GOROOT to info subcommand
- embed git-hash in tinygo-dev executable
- implement tinygo targets to list usable targets
- use simpler file copy instead of file renaming to avoid issues on nrf52840 UF2 bootloaders
- use ToSlash() to specify program path
- support flashing esp32/esp8266 directly from tinygo
- when flashing call PortReset only on other than openocd
* **compiler**
- `compileopts`: add support for custom binary formats
- `compiler`: improve display of goroutine wrappers
- `interp`: don't panic in the Store method
- `interp`: replace some panics with error messages
- `interp`: show error line in first line of the traceback
- `loader`: be more robust when creating the cached GOROOT
- `loader`: rewrite/refactor much of the code to use go list directly
- `loader`: use ioutil.TempDir to create a temporary directory
- `stacksize`: deal with DW_CFA_advance_loc1
* **standard library**
- `runtime`: use waitForEvents when appropriate
* **wasm**
- `wasm`: Remove --no-threads from wasm-ld calls.
- `wasm`: update wasi-libc dependency
* **targets**
- `arduino-mega2560`: fix flashing on Windows
- `arm`: automatically determine stack sizes
- `arm64`: make dynamic loader structs and constants private
- `avr`: configure emulator in board files
- `cortexm`: fix stack size calculation with interrupts
- `flash`: add openocd settings to atsamd21 / atsamd51
- `flash`: add openocd settings to nrf5
- `microbit`: reelboard: flash using OpenOCD when needed
- `nintendoswitch`: Add dynamic loader for runtime loading PIE sections
- `nintendoswitch`: fix import cycle on dynamic_arm64.go
- `nintendoswitch`: Fix invalid memory read / write in print calls
- `nintendoswitch`: simplified assembly code
- `nintendoswitch`: support outputting .nro files directly
* **boards**
- `arduino-zero`: Adding support for the Arduino Zero (#1365)
- `atsamd2x`: fix BAUD value
- `atsamd5x`: fix BAUD value
- `bluepill`: Enable stm32's USART2 for the board and map it to UART1 tinygo's device
- `device/atsamd51x`: add all remaining bitfield values for PCHCTRLm Mapping
- `esp32`: add libgcc ROM functions to linker script
- `esp32`: add SPI support
- `esp32`: add support for basic GPIO
- `esp32`: add support for the Espressif ESP32 chip
- `esp32`: configure the I/O matrix for GPIO pins
- `esp32`: export machine.PortMask* for bitbanging implementations
- `esp8266`: add support for this chip
- `machine/atsamd51x,runtime/atsamd51x`: fixes needed for full support for all PWM pins. Also adds some useful constants to clarify peripheral clock usage
- `machine/itsybitsy-nrf52840`: add support for Adafruit Itsybitsy nrf52840 (#1243)
- `machine/stm32f4`: refactor common code and add new build tag stm32f4 (#1332)
- `nrf`: add SoftDevice support for the Circuit Playground Bluefruit
- `nrf`: call sd_app_evt_wait when the SoftDevice is enabled
- `nrf52840`: add build tags for SoftDevice support
- `nrf52840`: use higher priority for USB-CDC code
- `runtime/atsamd51x`: use PCHCTRL_GCLK_SERCOMX_SLOW for setting clocks on all SERCOM ports
- `stm32f405`: add basic UART handler
- `stm32f405`: add STM32F405 machine/runtime, and new board/target feather-stm32f405
* **build**
- `all`: run test binaries in the correct directory
- `build`: Fix arch release job
- `ci`: run `tinygo test` for known-working packages
- `ci`: set git-fetch-depth to 1
- `docker`: fix the problem with the wasm build (#1357)
- `Makefile`: check whether submodules have been downloaded in some common cases
* **docs**
- add ESP32, ESP8266, and Adafruit Feather STM32F405 to list of supported boards
0.14.1
---
* **command-line**
- support for Go 1.15
* **compiler**
- loader: work around Windows symlink limitation
0.14.0
---
* **command-line**
- fix `getDefaultPort()` on non-English Windows locales
- compileopts: improve error reporting of unsupported flags
- fix test subcommand
- use auto-retry to locate MSD for UF2 and HEX flashing
- fix touchSerialPortAt1200bps on Windows
- support package names with backslashes on Windows
* **compiler**
- fix a few crashes due to named types
- add support for atomic operations
- move the channel blocked list onto the stack
- fix -gc=none
- fix named string to `[]byte` slice conversion
- implement func value and builtin defers
- add proper parameter names to runtime.initAll, to fix a panic
- builder: fix picolibc include path
- builder: use newer version of gohex
- builder: try to determine stack size information at compile time
- builder: remove -opt=0
- interp: fix sync/atomic.Value load/store methods
- loader: add Go module support
- transform: fix debug information in func lowering pass
- transform: do not special-case zero or one implementations of a method call
- transform: introduce check for method calls on nil interfaces
- transform: gc: track 0-index GEPs to fix miscompilation
* **cgo**
- Add LDFlags support
* **standard library**
- extend stdlib to allow import of more packages
- replace master/slave terminology with appropriate alternatives (MOSI->SDO
etc)
- `internal/bytealg`: reimplement bytealg in pure Go
- `internal/task`: fix nil panic in (*internal/task.Stack).Pop
- `os`: add Args and stub it with mock data
- `os`: implement virtual filesystem support
- `reflect`: add Cap and Len support for map and chan
- `runtime`: fix return address in scheduler on RISC-V
- `runtime`: avoid recursion in printuint64 function
- `runtime`: replace ReadRegister with AsmFull inline assembly
- `runtime`: fix compilation errors when using gc.extalloc
- `runtime`: add cap and len support for chans
- `runtime`: refactor time handling (improving accuracy)
- `runtime`: make channels work in interrupts
- `runtime/interrupt`: add cross-chip disable/restore interrupt support
- `sync`: implement `sync.Cond`
- `sync`: add WaitGroup
* **targets**
- `arm`: allow nesting in DisableInterrupts and EnableInterrupts
- `arm`: make FPU configuraton consistent
- `arm`: do not mask fault handlers in critical sections
- `atmega2560`: fix pin mapping for pins D2, D5 and the L port
- `atsamd`: return an error when an incorrect PWM pin is used
- `atsamd`: add support for pin change interrupts
- `atsamd`: add DAC support
- `atsamd21`: add more ADC pins
- `atsamd51`: fix ROM / RAM size on atsamd51j20
- `atsamd51`: add more pins
- `atsamd51`: add more ADC pins
- `atsamd51`: add pin change interrupt settings
- `atsamd51`: extend pinPadMapping
- `arduino-nano33`: use (U)SB flag to ensure that device can be found when
not on default port
- `arduino-nano33`: remove (d)ebug flag to reduce console noise when flashing
- `avr`: use standard pin numbering
- `avr`: unify GPIO pin/port code
- `avr`: add support for PinInputPullup
- `avr`: work around codegen bug in LLVM 10
- `avr`: fix target triple
- `fe310`: remove extra println left in by mistake
- `feather-nrf52840`: add support for the Feather nRF52840
- `maixbit`: add board definition and dummy runtime
- `nintendoswitch`: Add experimental Nintendo Switch support without CRT
- `nrf`: expose the RAM base address
- `nrf`: add support for pin change interrupts
- `nrf`: add microbit-s110v8 target
- `nrf`: fix bug in SPI.Tx
- `nrf`: support debugging the PCA10056
- `pygamer`: add Adafruit PyGamer suport
- `riscv`: fix interrupt configuration bug
- `riscv`: disable linker relaxations during gp init
- `stm32f4disco`: add new target with ST-Link v2.1 debugger
- `teensy36`: add Teensy 3.6 support
- `wasm`: fix event handling
- `wasm`: add --no-demangle linker option
- `wioterminal`: add support for the Seeed Wio Terminal
- `xiao`: add support for the Seeed XIAO
0.13.1
---
* **standard library**
+1 -1
View File
@@ -32,7 +32,7 @@ Microcontrollers have lots of peripherals (I2C, SPI, ADC, etc.) and many don't h
## How to use our Github repository
The `release` branch of this repo will always have the latest released version of TinyGo. All of the active development work for the next release will take place in the `dev` branch. TinyGo will use semantic versioning and will create a tag/release for each release.
The `master` branch of this repo will always have the latest released version of TinyGo. All of the active development work for the next release will take place in the `dev` branch. TinyGo will use semantic versioning and will create a tag/release for each release.
Here is how to contribute back some code or documentation:
+1 -1
View File
@@ -15,4 +15,4 @@ Ayke van Laethem <aykevanlaethem@gmail.com>
Daniel Esteban <conejo@conejo.me>
Loon, LLC.
Ron Evans <ron@hybridgroup.com>
Nia Weiss <niaow1234@gmail.com>
Jaden Weiss <jaden@jadendw.dev>
+4 -4
View File
@@ -4,7 +4,7 @@ FROM golang:1.14 AS tinygo-base
RUN wget -O- https://apt.llvm.org/llvm-snapshot.gpg.key| apt-key add - && \
echo "deb http://apt.llvm.org/buster/ llvm-toolchain-buster-10 main" >> /etc/apt/sources.list && \
apt-get update && \
apt-get install -y llvm-10-dev libclang-10-dev lld-10 git
apt-get install -y llvm-10-dev libclang-10-dev git
COPY . /tinygo
@@ -27,10 +27,10 @@ COPY --from=tinygo-base /go/bin/tinygo /go/bin/tinygo
COPY --from=tinygo-base /tinygo/src /tinygo/src
COPY --from=tinygo-base /tinygo/targets /tinygo/targets
RUN cd /tinygo/ && \
RUN wget -O- https://apt.llvm.org/llvm-snapshot.gpg.key| apt-key add - && \
echo "deb http://apt.llvm.org/buster/ llvm-toolchain-buster-10 main" >> /etc/apt/sources.list && \
apt-get update && \
apt-get install -y make clang-10 libllvm10 lld-10 && \
make wasi-libc
apt-get install -y libllvm10 lld-10
# tinygo-avr stage installs the needed dependencies to compile TinyGo programs for AVR microcontrollers.
FROM tinygo-base AS tinygo-avr
+11 -89
View File
@@ -51,7 +51,7 @@ else
LLVM_OPTION += '-DLLVM_ENABLE_ASSERTIONS=OFF'
endif
.PHONY: all tinygo test $(LLVM_BUILDDIR) llvm-source clean fmt gen-device gen-device-nrf gen-device-nxp gen-device-avr
.PHONY: all tinygo test $(LLVM_BUILDDIR) llvm-source clean fmt gen-device gen-device-nrf gen-device-avr
LLVM_COMPONENTS = all-targets analysis asmparser asmprinter bitreader bitwriter codegen core coroutines coverage debuginfodwarf executionengine frontendopenmp instrumentation interpreter ipo irreader linker lto mc mcjit objcarcopts option profiledata scalaropts support target
@@ -111,17 +111,16 @@ endif
clean:
@rm -rf build
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_PATHS = ./*.go builder cgo compiler compiler/testdata interp loader src/device/arm src/examples src/machine src/os src/reflect src/runtime src/sync src/syscall src/internal/reflectlite transform
fmt:
@gofmt -l -w $(FMT_PATHS)
fmt-check:
@unformatted=$$(gofmt -l $(FMT_PATHS)); [ -z "$$unformatted" ] && exit 0; echo "Unformatted:"; for fn in $$unformatted; do echo " $$fn"; done; exit 1
gen-device: gen-device-avr gen-device-esp gen-device-nrf gen-device-sam gen-device-sifive gen-device-stm32 gen-device-kendryte gen-device-nxp
gen-device: gen-device-avr gen-device-nrf gen-device-sam gen-device-sifive gen-device-stm32
gen-device-avr:
@if [ ! -e lib/avr/README.md ]; then echo "Submodules have not been downloaded. Please download them using:\n git submodule update --init"; exit 1; fi
$(GO) build -o ./build/gen-device-avr ./tools/gen-device-avr/
./build/gen-device-avr lib/avr/packs/atmega src/device/avr/
./build/gen-device-avr lib/avr/packs/tiny src/device/avr/
@@ -130,18 +129,10 @@ gen-device-avr:
build/gen-device-svd: ./tools/gen-device-svd/*.go
$(GO) build -o $@ ./tools/gen-device-svd/
gen-device-esp: build/gen-device-svd
./build/gen-device-svd -source=https://github.com/posborne/cmsis-svd/tree/master/data/Espressif-Community -interrupts=software lib/cmsis-svd/data/Espressif-Community/ src/device/esp/
GO111MODULE=off $(GO) fmt ./src/device/esp
gen-device-nrf: build/gen-device-svd
./build/gen-device-svd -source=https://github.com/NordicSemiconductor/nrfx/tree/master/mdk lib/nrfx/mdk/ src/device/nrf/
GO111MODULE=off $(GO) fmt ./src/device/nrf
gen-device-nxp: build/gen-device-svd
./build/gen-device-svd -source=https://github.com/posborne/cmsis-svd/tree/master/data/NXP lib/cmsis-svd/data/NXP/ src/device/nxp/
GO111MODULE=off $(GO) fmt ./src/device/nxp
gen-device-sam: build/gen-device-svd
./build/gen-device-svd -source=https://github.com/posborne/cmsis-svd/tree/master/data/Atmel lib/cmsis-svd/data/Atmel/ src/device/sam/
GO111MODULE=off $(GO) fmt ./src/device/sam
@@ -150,10 +141,6 @@ gen-device-sifive: build/gen-device-svd
./build/gen-device-svd -source=https://github.com/posborne/cmsis-svd/tree/master/data/SiFive-Community -interrupts=software lib/cmsis-svd/data/SiFive-Community/ src/device/sifive/
GO111MODULE=off $(GO) fmt ./src/device/sifive
gen-device-kendryte: build/gen-device-svd
./build/gen-device-svd -source=https://github.com/posborne/cmsis-svd/tree/master/data/Kendryte-Community -interrupts=software lib/cmsis-svd/data/Kendryte-Community/ src/device/kendryte/
GO111MODULE=off $(GO) fmt ./src/device/kendryte
gen-device-stm32: build/gen-device-svd
./build/gen-device-svd -source=https://github.com/posborne/cmsis-svd/tree/master/data/STMicro lib/cmsis-svd/data/STMicro/ src/device/stm32/
GO111MODULE=off $(GO) fmt ./src/device/stm32
@@ -161,13 +148,13 @@ gen-device-stm32: build/gen-device-svd
# Get LLVM sources.
$(LLVM_PROJECTDIR)/README.md:
git clone -b xtensa_release_10.0.1 --depth=1 https://github.com/tinygo-org/llvm-project $(LLVM_PROJECTDIR)
git clone -b release/10.x https://github.com/llvm/llvm-project $(LLVM_PROJECTDIR)
llvm-source: $(LLVM_PROJECTDIR)/README.md
# Configure LLVM.
TINYGO_SOURCE_DIR=$(shell pwd)
$(LLVM_BUILDDIR)/build.ninja: llvm-source
mkdir -p $(LLVM_BUILDDIR); cd $(LLVM_BUILDDIR); cmake -G Ninja $(TINYGO_SOURCE_DIR)/$(LLVM_PROJECTDIR)/llvm "-DLLVM_TARGETS_TO_BUILD=X86;ARM;AArch64;RISCV;WebAssembly" "-DLLVM_EXPERIMENTAL_TARGETS_TO_BUILD=AVR;Xtensa" -DCMAKE_BUILD_TYPE=Release -DLIBCLANG_BUILD_STATIC=ON -DLLVM_ENABLE_TERMINFO=OFF -DLLVM_ENABLE_ZLIB=OFF -DLLVM_ENABLE_LIBEDIT=OFF -DLLVM_ENABLE_Z3_SOLVER=OFF -DLLVM_ENABLE_OCAMLDOC=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_PROJECTDIR)/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.
$(LLVM_BUILDDIR): $(LLVM_BUILDDIR)/build.ninja
@@ -178,31 +165,24 @@ $(LLVM_BUILDDIR): $(LLVM_BUILDDIR)/build.ninja
.PHONY: wasi-libc
wasi-libc: lib/wasi-libc/sysroot/lib/wasm32-wasi/libc.a
lib/wasi-libc/sysroot/lib/wasm32-wasi/libc.a:
@if [ ! -e lib/wasi-libc/Makefile ]; then echo "Submodules have not been downloaded. Please download them using:\n git submodule update --init"; exit 1; fi
cd lib/wasi-libc && make -j4 WASM_CC=$(CLANG) WASM_AR=$(LLVM_AR) WASM_NM=$(LLVM_NM)
# Build the Go compiler.
tinygo:
@if [ ! -f "$(LLVM_BUILDDIR)/bin/llvm-config" ]; then echo "Fetch and build LLVM first by running:"; echo " make llvm-source"; echo " make $(LLVM_BUILDDIR)"; exit 1; fi
CGO_CPPFLAGS="$(CGO_CPPFLAGS)" CGO_CXXFLAGS="$(CGO_CXXFLAGS)" CGO_LDFLAGS="$(CGO_LDFLAGS)" $(GO) build -buildmode exe -o build/tinygo$(EXE) -tags byollvm -ldflags="-X main.gitSha1=`git rev-parse --short HEAD`" .
CGO_CPPFLAGS="$(CGO_CPPFLAGS)" CGO_CXXFLAGS="$(CGO_CXXFLAGS)" CGO_LDFLAGS="$(CGO_LDFLAGS)" $(GO) build -o build/tinygo$(EXE) -tags byollvm .
test: wasi-libc
CGO_CPPFLAGS="$(CGO_CPPFLAGS)" CGO_CXXFLAGS="$(CGO_CXXFLAGS)" CGO_LDFLAGS="$(CGO_LDFLAGS)" $(GO) test -v -buildmode exe -tags byollvm ./cgo ./compileopts ./interp ./transform .
CGO_CPPFLAGS="$(CGO_CPPFLAGS)" CGO_CXXFLAGS="$(CGO_CXXFLAGS)" CGO_LDFLAGS="$(CGO_LDFLAGS)" $(GO) test -v -tags byollvm ./cgo ./compileopts ./compiler ./interp ./transform .
# Test known-working standard library packages.
# TODO: do this in one command, parallelize, and only show failing tests (no
# implied -v flag).
.PHONY: tinygo-test
tinygo-test:
$(TINYGO) test container/list
$(TINYGO) test container/ring
$(TINYGO) test text/scanner
cd tests/tinygotest && tinygo test
.PHONY: smoketest
smoketest:
$(TINYGO) version
# test all examples (except pwm)
# test all examples
$(TINYGO) build -size short -o test.hex -target=pca10040 examples/blinky1
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=pca10040 examples/adc
@@ -223,7 +203,7 @@ smoketest:
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=microbit examples/microbit-blink
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=pca10040 examples/pininterrupt
$(TINYGO) build -size short -o test.hex -target=pca10040 examples/pwm
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=pca10040 examples/serial
@$(MD5SUM) test.hex
@@ -251,8 +231,6 @@ smoketest:
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=microbit examples/echo
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=microbit-s110v8 examples/echo
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=nrf52840-mdk examples/blinky1
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=pca10031 examples/blinky1
@@ -279,10 +257,6 @@ smoketest:
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=stm32f4disco examples/blinky2
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=stm32f4disco-1 examples/blinky1
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=feather-stm32f405 examples/blinky1
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=circuitplay-bluefruit examples/blinky1
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=circuitplay-express examples/i2s
@@ -317,36 +291,11 @@ smoketest:
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=reelboard-s140v7 examples/blinky1
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=wioterminal examples/blinky1
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=pygamer examples/blinky1
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=xiao examples/blinky1
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=circuitplay-express examples/dac
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=pyportal examples/dac
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=feather-nrf52840 examples/blinky1
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=itsybitsy-nrf52840 examples/blinky1
@$(MD5SUM) test.hex
# test pwm
$(TINYGO) build -size short -o test.hex -target=itsybitsy-m0 examples/pwm
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=itsybitsy-m4 examples/pwm
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=feather-m4 examples/pwm
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=pyportal examples/pwm
@$(MD5SUM) test.hex
ifneq ($(AVR), 0)
$(TINYGO) build -size short -o test.hex -target=atmega1284p examples/serial
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=arduino examples/blinky1
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=arduino examples/pwm
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=arduino -scheduler=tasks examples/blinky1
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=arduino-nano examples/blinky1
@@ -355,31 +304,13 @@ ifneq ($(AVR), 0)
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=digispark -gc=leaking examples/blinky1
@$(MD5SUM) test.hex
endif
ifneq ($(XTENSA), 0)
$(TINYGO) build -size short -o test.bin -target=esp32-wroom-32 examples/blinky1
@$(MD5SUM) test.bin
$(TINYGO) build -size short -o test.bin -target=nodemcu examples/blinky1
@$(MD5SUM) test.bin
endif
$(TINYGO) build -size short -o test.hex -target=hifive1b examples/blinky1
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=maixbit examples/blinky1
@$(MD5SUM) test.hex
$(TINYGO) build -o wasm.wasm -target=wasm examples/wasm/export
$(TINYGO) build -o wasm.wasm -target=wasm examples/wasm/main
# test various compiler flags
$(TINYGO) build -size short -o test.hex -target=pca10040 -gc=none -scheduler=none examples/blinky1
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=pca10040 -opt=1 examples/blinky1
@$(MD5SUM) test.hex
$(TINYGO) build -o test.nro -target=nintendoswitch examples/serial
@$(MD5SUM) test.nro
wasmtest:
$(GO) test ./tests/wasm
build/release: tinygo gen-device wasi-libc
release: tinygo gen-device wasi-libc
@mkdir -p build/release/tinygo/bin
@mkdir -p build/release/tinygo/lib/clang/include
@mkdir -p build/release/tinygo/lib/CMSIS/CMSIS
@@ -414,13 +345,4 @@ build/release: tinygo gen-device wasi-libc
./build/tinygo build-library -target=armv6m-none-eabi -o build/release/tinygo/pkg/armv6m-none-eabi/picolibc.a picolibc
./build/tinygo build-library -target=armv7m-none-eabi -o build/release/tinygo/pkg/armv7m-none-eabi/picolibc.a picolibc
./build/tinygo build-library -target=armv7em-none-eabi -o build/release/tinygo/pkg/armv7em-none-eabi/picolibc.a picolibc
release: build/release
tar -czf build/release.tar.gz -C build/release tinygo
deb: build/release
@mkdir -p build/release-deb/usr/local/bin
@mkdir -p build/release-deb/usr/local/lib
cp -ar build/release/tinygo build/release-deb/usr/local/lib/tinygo
ln -sf ../lib/tinygo/bin/tinygo build/release-deb/usr/local/bin/tinygo
fpm -f -s dir -t deb -n tinygo -v $(shell grep "const Version = " goenv/version.go | awk '{print $$NF}') -m '@tinygo-org' --description='TinyGo is a Go compiler for small places.' --license='BSD 3-Clause' --url=https://tinygo.org/ --deb-changelog CHANGELOG.md -p build/release.deb -C ./build/release-deb
+1 -15
View File
@@ -43,35 +43,27 @@ See the [getting started instructions](https://tinygo.org/getting-started/) for
You can compile TinyGo programs for microcontrollers, WebAssembly and Linux.
The following 44 microcontroller boards are currently supported:
The following 32 microcontroller boards are currently supported:
* [Adafruit Circuit Playground Bluefruit](https://www.adafruit.com/product/4333)
* [Adafruit Circuit Playground Express](https://www.adafruit.com/product/3333)
* [Adafruit CLUE Alpha](https://www.adafruit.com/product/4500)
* [Adafruit Feather M0](https://www.adafruit.com/product/2772)
* [Adafruit Feather M4](https://www.adafruit.com/product/3857)
* [Adafruit Feather nRF52840 Express](https://www.adafruit.com/product/4062)
* [Adafruit Feather STM32F405 Express](https://www.adafruit.com/product/4382)
* [Adafruit ItsyBitsy M0](https://www.adafruit.com/product/3727)
* [Adafruit ItsyBitsy M4](https://www.adafruit.com/product/3800)
* [Adafruit ItsyBitsy nRF52840](https://www.adafruit.com/product/4481)
* [Adafruit Metro M4 Express Airlift](https://www.adafruit.com/product/4000)
* [Adafruit PyBadge](https://www.adafruit.com/product/4200)
* [Adafruit PyGamer](https://www.adafruit.com/product/4242)
* [Adafruit PyPortal](https://www.adafruit.com/product/4116)
* [Adafruit Trinket M0](https://www.adafruit.com/product/3500)
* [Arduino Mega 2560](https://store.arduino.cc/arduino-mega-2560-rev3)
* [Arduino Nano](https://store.arduino.cc/arduino-nano)
* [Arduino Nano33 IoT](https://store.arduino.cc/nano-33-iot)
* [Arduino Uno](https://store.arduino.cc/arduino-uno-rev3)
* [Arduino Zero](https://store.arduino.cc/usa/arduino-zero)
* [BBC micro:bit](https://microbit.org/)
* [Digispark](http://digistump.com/products/1)
* [ESP32](https://www.espressif.com/en/products/socs/esp32)
* [ESP8266](https://www.espressif.com/en/products/socs/esp8266)
* [Game Boy Advance](https://en.wikipedia.org/wiki/Game_Boy_Advance)
* [Makerdiary nRF52840-MDK](https://wiki.makerdiary.com/nrf52840-mdk/)
* [Nintendo Switch](https://www.nintendo.com/switch/)
* [Nordic Semiconductor PCA10031](https://www.nordicsemi.com/eng/Products/nRF51-Dongle)
* [Nordic Semiconductor PCA10040](https://www.nordicsemi.com/eng/Products/Bluetooth-low-energy/nRF52-DK)
* [Nordic Semiconductor PCA10056](https://www.nordicsemi.com/Software-and-Tools/Development-Kits/nRF52840-DK)
@@ -80,10 +72,6 @@ The following 44 microcontroller boards are currently supported:
* [Particle Xenon](https://docs.particle.io/datasheets/discontinued/xenon-datasheet/)
* [Phytec reel board](https://www.phytec.eu/product-eu/internet-of-things/reelboard/)
* [PineTime DevKit](https://www.pine64.org/pinetime/)
* [PJRC Teensy 3.6](https://www.pjrc.com/store/teensy36.html)
* [Seeed Wio Terminal](https://www.seeedstudio.com/Wio-Terminal-p-4509.html)
* [Seeed Seeeduino XIAO](https://www.seeedstudio.com/Seeeduino-XIAO-Arduino-Microcontroller-SAMD21-Cortex-M0+-p-4426.html)
* [Seeed Sipeed MAix BiT](https://www.seeedstudio.com/Sipeed-MAix-BiT-for-RISC-V-AI-IoT-p-2872.html)
* [SiFIve HiFive1](https://www.sifive.com/boards/hifive1)
* [ST Micro "Nucleo F103RB"](https://www.st.com/en/evaluation-tools/nucleo-f103rb.html)
* [ST Micro STM32F103XX "Bluepill"](http://wiki.stm32duino.com/index.php?title=Blue_Pill)
@@ -149,5 +137,3 @@ The original reasoning was: if [Python](https://micropython.org/) can run on mic
This project is licensed under the BSD 3-clause license, just like the [Go project](https://golang.org/LICENSE) itself.
Some code has been copied from the LLVM project and is therefore licensed under [a variant of the Apache 2.0 license](http://releases.llvm.org/10.0.0/LICENSE.TXT). This has been clearly indicated in the header of these files.
Some code has been copied and/or ported from Paul Stoffregen's Teensy libraries and is therefore licensed under PJRC's license. This has been clearly indicated in the header of these files.
+10 -15
View File
@@ -1,7 +1,7 @@
# Avoid lengthy LLVM rebuilds on each newly pushed branch. Pull requests will
# be built anyway.
trigger:
- release
- master
- dev
jobs:
@@ -12,27 +12,22 @@ jobs:
steps:
- task: GoTool@0
inputs:
version: '1.15'
version: '1.14.1'
- checkout: self
fetchDepth: 1
- task: Cache@2
- task: CacheBeta@0
displayName: Cache LLVM source
inputs:
key: llvm-source-10-windows-v1
key: llvm-source-10-windows-v0
path: llvm-project
- task: Bash@3
displayName: Download LLVM source
inputs:
targetType: inline
script: |
make llvm-source
# Workaround for bad symlinks:
# https://github.com/microsoft/azure-pipelines-tasks/issues/13418
rm -f llvm-project/libcxx/test/std/input.output/filesystems/Inputs/static_test_env/bad_symlink
script: make llvm-source
- task: CacheBeta@0
displayName: Cache LLVM build
inputs:
key: llvm-build-10-windows-v1
key: llvm-build-10-windows-v0
path: llvm-build
- task: Bash@3
displayName: Build LLVM
@@ -48,11 +43,11 @@ jobs:
displayName: Install QEMU
inputs:
targetType: inline
script: choco install qemu --version=2020.06.12
script: choco install qemu
- task: CacheBeta@0
displayName: Cache wasi-libc sysroot
inputs:
key: wasi-libc-sysroot-v3
key: wasi-libc-sysroot-v2
path: lib/wasi-libc/sysroot
- task: Bash@3
displayName: Build wasi-libc
@@ -74,7 +69,7 @@ jobs:
script: |
export PATH="$PATH:./llvm-build/bin:/c/Program Files/qemu"
unset GOROOT
make build/release -j4
make release -j4
- publish: $(System.DefaultWorkingDirectory)/build/release/tinygo
displayName: Publish zip as artifact
artifact: tinygo
@@ -85,4 +80,4 @@ jobs:
script: |
export PATH="$PATH:./llvm-build/bin:/c/Program Files/qemu"
unset GOROOT
make smoketest TINYGO=build/tinygo AVR=0 XTENSA=0
make smoketest TINYGO=build/tinygo AVR=0
+9 -321
View File
@@ -4,14 +4,11 @@
package builder
import (
"debug/elf"
"encoding/binary"
"errors"
"fmt"
"io/ioutil"
"os"
"path/filepath"
"sort"
"strconv"
"strings"
@@ -19,40 +16,26 @@ import (
"github.com/tinygo-org/tinygo/compiler"
"github.com/tinygo-org/tinygo/goenv"
"github.com/tinygo-org/tinygo/interp"
"github.com/tinygo-org/tinygo/stacksize"
"github.com/tinygo-org/tinygo/transform"
"tinygo.org/x/go-llvm"
)
// BuildResult is the output of a build. This includes the binary itself and
// some other metadata that is obtained while building the binary.
type BuildResult struct {
// A path to the output binary. It will be removed after Build returns, so
// if it should be kept it must be copied or moved away.
Binary string
// The directory of the main package. This is useful for testing as the test
// binary must be run in the directory of the tested package.
MainDir string
}
// 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(BuildResult) error) error {
func Build(pkgName, outpath string, config *compileopts.Config, action func(string) error) error {
// Compile Go code to IR.
machine, err := compiler.NewTargetMachine(config)
if err != nil {
return err
}
buildOutput, errs := compiler.Compile(pkgName, machine, config)
mod, extraFiles, errs := compiler.Compile(pkgName, machine, config)
if errs != nil {
return newMultiError(errs)
}
mod := buildOutput.Mod
if config.Options.PrintIR {
fmt.Println("; Generated LLVM IR:")
@@ -90,15 +73,8 @@ func Build(pkgName, outpath string, config *compileopts.Config, action func(Buil
// exactly.
errs = nil
switch config.Options.Opt {
/*
Currently, turning optimizations off causes compile failures.
We rely on the optimizer removing some dead symbols.
Avoid providing an option that does not work right now.
In the future once everything has been fixed we can re-enable this.
case "none", "0":
errs = transform.Optimize(mod, config, 0, 0, 0) // -O0
*/
case "none", "0":
errs = transform.Optimize(mod, config, 0, 0, 0) // -O0
case "1":
errs = transform.Optimize(mod, config, 1, 0, 0) // -O1
case "2":
@@ -129,13 +105,6 @@ func Build(pkgName, outpath string, config *compileopts.Config, action func(Buil
}
}
// Make sure stack sizes are loaded from a separate section so they can be
// modified after linking.
var stackSizeLoads []string
if config.AutomaticStackSize() {
stackSizeLoads = transform.CreateStackSizeLoads(mod, config)
}
// Generate output.
outext := filepath.Ext(outpath)
switch outext {
@@ -209,7 +178,7 @@ func Build(pkgName, outpath string, config *compileopts.Config, action func(Buil
}
// Compile C files in packages.
for i, file := range buildOutput.ExtraFiles {
for i, file := range extraFiles {
outpath := filepath.Join(dir, "pkg"+strconv.Itoa(i)+"-"+filepath.Base(file)+".o")
err := runCCompiler(config.Target.Compiler, append(config.CFlags(), "-c", "-o", outpath, file)...)
if err != nil {
@@ -218,36 +187,12 @@ func Build(pkgName, outpath string, config *compileopts.Config, action func(Buil
ldflags = append(ldflags, outpath)
}
if len(buildOutput.ExtraLDFlags) > 0 {
ldflags = append(ldflags, buildOutput.ExtraLDFlags...)
}
// Link the object files together.
err = link(config.Target.Linker, ldflags...)
if err != nil {
return &commandError{"failed to link", executable, err}
}
var calculatedStacks []string
var stackSizes map[string]functionStackSize
if config.Options.PrintStacks || config.AutomaticStackSize() {
// Try to determine stack sizes at compile time.
// Don't do this by default as it usually doesn't work on
// unsupported architectures.
calculatedStacks, stackSizes, err = determineStackSizes(mod, executable)
if err != nil {
return err
}
}
if config.AutomaticStackSize() {
// Modify the .tinygo_stacksizes section that contains a stack size
// for each goroutine.
err = modifyStackSizes(executable, stackSizeLoads, stackSizes)
if err != nil {
return fmt.Errorf("could not modify stack sizes: %w", err)
}
}
if config.Options.PrintSizes == "short" || config.Options.PrintSizes == "full" {
sizes, err := loadProgramSize(executable)
if err != nil {
@@ -267,278 +212,21 @@ func Build(pkgName, outpath string, config *compileopts.Config, action func(Buil
}
}
// Print goroutine stack sizes, as far as possible.
if config.Options.PrintStacks {
printStacks(calculatedStacks, stackSizes)
}
// Get an Intel .hex file or .bin file from the .elf file.
outputBinaryFormat := config.BinaryFormat(outext)
switch outputBinaryFormat {
case "elf":
// do nothing, file is already in ELF format
case "hex", "bin":
// Extract raw binary, either encoding it as a hex file or as a raw
// firmware file.
if outext == ".hex" || outext == ".bin" || outext == ".gba" {
tmppath = filepath.Join(dir, "main"+outext)
err := objcopy(executable, tmppath, outputBinaryFormat)
err := objcopy(executable, tmppath)
if err != nil {
return err
}
case "uf2":
} else if outext == ".uf2" {
// Get UF2 from the .elf file.
tmppath = filepath.Join(dir, "main"+outext)
err := convertELFFileToUF2File(executable, tmppath, config.Target.UF2FamilyID)
if err != nil {
return err
}
case "esp32", "esp8266":
// Special format for the ESP family of chips (parsed by the ROM
// bootloader).
tmppath = filepath.Join(dir, "main"+outext)
err := makeESPFirmareImage(executable, tmppath, outputBinaryFormat)
if err != nil {
return err
}
default:
return fmt.Errorf("unknown output binary format: %s", outputBinaryFormat)
}
return action(BuildResult{
Binary: tmppath,
MainDir: buildOutput.MainDir,
})
}
}
// functionStackSizes keeps stack size information about a single function
// (usually a goroutine).
type functionStackSize struct {
humanName string
stackSize uint64
stackSizeType stacksize.SizeType
missingStackSize *stacksize.CallNode
}
// determineStackSizes tries to determine the stack sizes of all started
// goroutines and of the reset vector. The LLVM module is necessary to find
// functions that call a function pointer.
func determineStackSizes(mod llvm.Module, executable string) ([]string, map[string]functionStackSize, error) {
var callsIndirectFunction []string
gowrappers := []string{}
gowrapperNames := make(map[string]string)
for fn := mod.FirstFunction(); !fn.IsNil(); fn = llvm.NextFunction(fn) {
// Determine which functions call a function pointer.
for bb := fn.FirstBasicBlock(); !bb.IsNil(); bb = llvm.NextBasicBlock(bb) {
for inst := bb.FirstInstruction(); !inst.IsNil(); inst = llvm.NextInstruction(inst) {
if inst.IsACallInst().IsNil() {
continue
}
if callee := inst.CalledValue(); callee.IsAFunction().IsNil() && callee.IsAInlineAsm().IsNil() {
callsIndirectFunction = append(callsIndirectFunction, fn.Name())
}
}
}
// Get a list of "go wrappers", small wrapper functions that decode
// parameters when starting a new goroutine.
attr := fn.GetStringAttributeAtIndex(-1, "tinygo-gowrapper")
if !attr.IsNil() {
gowrappers = append(gowrappers, fn.Name())
gowrapperNames[fn.Name()] = attr.GetStringValue()
}
}
sort.Strings(gowrappers)
// Load the ELF binary.
f, err := elf.Open(executable)
if err != nil {
return nil, nil, fmt.Errorf("could not load executable for stack size analysis: %w", err)
}
defer f.Close()
// Determine the frame size of each function (if available) and the callgraph.
functions, err := stacksize.CallGraph(f, callsIndirectFunction)
if err != nil {
return nil, nil, fmt.Errorf("could not parse executable for stack size analysis: %w", err)
}
// Goroutines need to be started and finished and take up some stack space
// that way. This can be measured by measuing the stack size of
// tinygo_startTask.
if numFuncs := len(functions["tinygo_startTask"]); numFuncs != 1 {
return nil, nil, fmt.Errorf("expected exactly one definition of tinygo_startTask, got %d", numFuncs)
}
baseStackSize, baseStackSizeType, baseStackSizeFailedAt := functions["tinygo_startTask"][0].StackSize()
sizes := make(map[string]functionStackSize)
// Add the reset handler function, for convenience. The reset handler runs
// startup code and the scheduler. The listed stack size is not the full
// stack size: interrupts are not counted.
var resetFunction string
switch f.Machine {
case elf.EM_ARM:
// Note: all interrupts happen on this stack so the real size is bigger.
resetFunction = "Reset_Handler"
}
if resetFunction != "" {
funcs := functions[resetFunction]
if len(funcs) != 1 {
return nil, nil, fmt.Errorf("expected exactly one definition of %s in the callgraph, found %d", resetFunction, len(funcs))
}
stackSize, stackSizeType, missingStackSize := funcs[0].StackSize()
sizes[resetFunction] = functionStackSize{
stackSize: stackSize,
stackSizeType: stackSizeType,
missingStackSize: missingStackSize,
humanName: resetFunction,
}
}
// Add all goroutine wrapper functions.
for _, name := range gowrappers {
funcs := functions[name]
if len(funcs) != 1 {
return nil, nil, fmt.Errorf("expected exactly one definition of %s in the callgraph, found %d", name, len(funcs))
}
humanName := gowrapperNames[name]
if humanName == "" {
humanName = name // fallback
}
stackSize, stackSizeType, missingStackSize := funcs[0].StackSize()
if baseStackSizeType != stacksize.Bounded {
// It was not possible to determine the stack size at compile time
// because tinygo_startTask does not have a fixed stack size. This
// can happen when using -opt=1.
stackSizeType = baseStackSizeType
missingStackSize = baseStackSizeFailedAt
} else if stackSize < baseStackSize {
// This goroutine has a very small stack, but still needs to fit all
// registers to start and suspend the goroutine. Otherwise a stack
// overflow will occur even before the goroutine is started.
stackSize = baseStackSize
}
sizes[name] = functionStackSize{
stackSize: stackSize,
stackSizeType: stackSizeType,
missingStackSize: missingStackSize,
humanName: humanName,
}
}
if resetFunction != "" {
return append([]string{resetFunction}, gowrappers...), sizes, nil
}
return gowrappers, sizes, nil
}
// modifyStackSizes modifies the .tinygo_stacksizes section with the updated
// stack size information. Before this modification, all stack sizes in the
// section assume the default stack size (which is relatively big).
func modifyStackSizes(executable string, stackSizeLoads []string, stackSizes map[string]functionStackSize) error {
fp, err := os.OpenFile(executable, os.O_RDWR, 0)
if err != nil {
return err
}
defer fp.Close()
elfFile, err := elf.NewFile(fp)
if err != nil {
return err
}
section := elfFile.Section(".tinygo_stacksizes")
if section == nil {
return errors.New("could not find .tinygo_stacksizes section")
}
if section.Size != section.FileSize {
// Sanity check.
return fmt.Errorf("expected .tinygo_stacksizes to have identical size and file size, got %d and %d", section.Size, section.FileSize)
}
// Read all goroutine stack sizes.
data := make([]byte, section.Size)
_, err = fp.ReadAt(data, int64(section.Offset))
if err != nil {
return err
}
if len(stackSizeLoads)*4 != len(data) {
// Note: while AVR should use 2 byte stack sizes, even 64-bit platforms
// should probably stick to 4 byte stack sizes as a larger than 4GB
// stack doesn't make much sense.
return errors.New("expected 4 byte stack sizes")
}
// Modify goroutine stack sizes with a compile-time known worst case stack
// size.
for i, name := range stackSizeLoads {
fn, ok := stackSizes[name]
if !ok {
return fmt.Errorf("could not find symbol %s in ELF file", name)
}
if fn.stackSizeType == stacksize.Bounded {
stackSize := uint32(fn.stackSize)
// Adding 4 for the stack canary. Even though the size may be
// automatically determined, stack overflow checking is still
// important as the stack size cannot be determined for all
// goroutines.
stackSize += 4
// Add stack size used by interrupts.
switch elfFile.Machine {
case elf.EM_ARM:
// On Cortex-M (assumed here), this stack size is 8 words or 32
// bytes. This is only to store the registers that the interrupt
// may modify, the interrupt will switch to the interrupt stack
// (MSP).
// Some background:
// https://interrupt.memfault.com/blog/cortex-m-rtos-context-switching
stackSize += 32
}
// Finally write the stack size to the binary.
binary.LittleEndian.PutUint32(data[i*4:], stackSize)
}
}
// Write back the modified stack sizes.
_, err = fp.WriteAt(data, int64(section.Offset))
if err != nil {
return err
}
return nil
}
// printStacks prints the maximum stack depth for functions that are started as
// goroutines. Stack sizes cannot always be determined statically, in particular
// recursive functions and functions that call interface methods or function
// pointers may have an unknown stack depth (depending on what the optimizer
// manages to optimize away).
//
// It might print something like the following:
//
// function stack usage (in bytes)
// Reset_Handler 316
// examples/blinky2.led1 92
// runtime.run$1 300
func printStacks(calculatedStacks []string, stackSizes map[string]functionStackSize) {
// Print the sizes of all stacks.
fmt.Printf("%-32s %s\n", "function", "stack usage (in bytes)")
for _, name := range calculatedStacks {
fn := stackSizes[name]
switch fn.stackSizeType {
case stacksize.Bounded:
fmt.Printf("%-32s %d\n", fn.humanName, fn.stackSize)
case stacksize.Unknown:
fmt.Printf("%-32s unknown, %s does not have stack frame information\n", fn.humanName, fn.missingStackSize)
case stacksize.Recursive:
fmt.Printf("%-32s recursive, %s may call itself\n", fn.humanName, fn.missingStackSize)
case stacksize.IndirectCall:
fmt.Printf("%-32s unknown, %s calls a function pointer\n", fn.humanName, fn.missingStackSize)
}
return action(tmppath)
}
}
+3 -3
View File
@@ -21,12 +21,12 @@ func NewConfig(options *compileopts.Options) (*compileopts.Config, error) {
if goroot == "" {
return nil, errors.New("cannot locate $GOROOT, please set it manually")
}
major, minor, err := goenv.GetGorootVersion(goroot)
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 > 15 {
return nil, fmt.Errorf("requires go version 1.11 through 1.15, got go%d.%d", major, minor)
if major != 1 || minor < 11 || minor > 14 {
return nil, fmt.Errorf("requires go version 1.11, 1.12, 1.13, or 1.14, got go%d.%d", major, minor)
}
clangHeaderPath := getClangHeaderPath(goenv.Get("TINYGOROOT"))
return &compileopts.Config{
+58
View File
@@ -1,13 +1,71 @@
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.
-153
View File
@@ -1,153 +0,0 @@
package builder
// This file implements support for writing ESP image files. These image files
// are read by the ROM bootloader so have to be in a particular format.
//
// In the future, it may be necessary to implement support for other image
// formats, such as the ESP8266 image formats (again, used by the ROM bootloader
// to load the firmware).
import (
"bytes"
"crypto/sha256"
"debug/elf"
"encoding/binary"
"fmt"
"io/ioutil"
"sort"
)
type espImageSegment struct {
addr uint32
data []byte
}
// makeESPFirmare converts an input ELF file to an image file for an ESP32 or
// ESP8266 chip. This is a special purpose image format just for the ESP chip
// family, and is parsed by the on-chip mask ROM bootloader.
//
// The following documentation has been used:
// https://github.com/espressif/esptool/wiki/Firmware-Image-Format
// https://github.com/espressif/esp-idf/blob/8fbb63c2a701c22ccf4ce249f43aded73e134a34/components/bootloader_support/include/esp_image_format.h#L58
// https://github.com/espressif/esptool/blob/master/esptool.py
func makeESPFirmareImage(infile, outfile, format string) error {
inf, err := elf.Open(infile)
if err != nil {
return err
}
defer inf.Close()
// Load all segments to be written to the image. These are actually ELF
// sections, not true ELF segments (similar to how esptool does it).
var segments []*espImageSegment
for _, section := range inf.Sections {
if section.Type != elf.SHT_PROGBITS || section.Size == 0 || section.Flags&elf.SHF_ALLOC == 0 {
continue
}
data, err := section.Data()
if err != nil {
return fmt.Errorf("failed to read section data: %w", err)
}
for len(data)%4 != 0 {
// Align segment to 4 bytes.
data = append(data, 0)
}
if uint64(uint32(section.Addr)) != section.Addr {
return fmt.Errorf("section address too big: 0x%x", section.Addr)
}
segments = append(segments, &espImageSegment{
addr: uint32(section.Addr),
data: data,
})
}
// Sort the segments by address. This is what esptool does too.
sort.SliceStable(segments, func(i, j int) bool { return segments[i].addr < segments[j].addr })
// Calculate checksum over the segment data. This is used in the image
// footer.
checksum := uint8(0xef)
for _, segment := range segments {
for _, b := range segment.data {
checksum ^= b
}
}
// Write first to an in-memory buffer, primarily so that we can easily
// calculate a hash over the entire image.
// An added benefit is that we don't need to check for errors all the time.
outf := &bytes.Buffer{}
// Image header.
switch format {
case "esp32":
// Header format:
// https://github.com/espressif/esp-idf/blob/8fbb63c2/components/bootloader_support/include/esp_image_format.h#L58
binary.Write(outf, binary.LittleEndian, struct {
magic uint8
segment_count uint8
spi_mode uint8
spi_speed_size uint8
entry_addr uint32
wp_pin uint8
spi_pin_drv [3]uint8
reserved [11]uint8
hash_appended bool
}{
magic: 0xE9,
segment_count: byte(len(segments)),
spi_mode: 0, // irrelevant, replaced by esptool when flashing
spi_speed_size: 0, // spi_speed, spi_size: replaced by esptool when flashing
entry_addr: uint32(inf.Entry),
wp_pin: 0xEE, // disable WP pin
hash_appended: true, // add a SHA256 hash
})
case "esp8266":
// Header format:
// https://github.com/espressif/esptool/wiki/Firmware-Image-Format
// Basically a truncated version of the ESP32 header.
binary.Write(outf, binary.LittleEndian, struct {
magic uint8
segment_count uint8
spi_mode uint8
spi_speed_size uint8
entry_addr uint32
}{
magic: 0xE9,
segment_count: byte(len(segments)),
spi_mode: 0, // irrelevant, replaced by esptool when flashing
spi_speed_size: 0x20, // spi_speed, spi_size: replaced by esptool when flashing
entry_addr: uint32(inf.Entry),
})
default:
return fmt.Errorf("builder: unknown binary format %#v, expected esp32 or esp8266", format)
}
// Write all segments to the image.
// https://github.com/espressif/esptool/wiki/Firmware-Image-Format#segment
for _, segment := range segments {
binary.Write(outf, binary.LittleEndian, struct {
addr uint32
length uint32
}{
addr: segment.addr,
length: uint32(len(segment.data)),
})
outf.Write(segment.data)
}
// Footer, including checksum.
// The entire image size must be a multiple of 16, so pad the image to one
// byte less than that before writing the checksum.
outf.Write(make([]byte, 15-outf.Len()%16))
outf.WriteByte(checksum)
if format == "esp32" {
// SHA256 hash (to protect against image corruption, not for security).
hash := sha256.Sum256(outf.Bytes())
outf.Write(hash[:])
}
// Write the image to the output file.
return ioutil.WriteFile(outfile, outf.Bytes(), 0666)
}
+1 -4
View File
@@ -69,14 +69,11 @@ func (l *Library) Load(target string) (path string, err error) {
// Precalculate the flags to the compiler invocation.
args := append(l.cflags(), "-c", "-Oz", "-g", "-ffunction-sections", "-fdata-sections", "-Wno-macro-redefined", "--target="+target, "-fdebug-prefix-map="+dir+"="+remapDir)
if strings.HasPrefix(target, "arm") || strings.HasPrefix(target, "thumb") {
args = append(args, "-fshort-enums", "-fomit-frame-pointer", "-mfloat-abi=soft")
args = append(args, "-fshort-enums", "-fomit-frame-pointer")
}
if strings.HasPrefix(target, "riscv32-") {
args = append(args, "-march=rv32imac", "-mabi=ilp32", "-fforce-enable-int128")
}
if strings.HasPrefix(target, "riscv64-") {
args = append(args, "-march=rv64gc", "-mabi=lp64")
}
// Compile all sources.
var objs []string
+16 -20
View File
@@ -4,15 +4,12 @@ import (
"debug/elf"
"io/ioutil"
"os"
"path/filepath"
"sort"
"github.com/marcinbor85/gohex"
)
// maxPadBytes is the maximum allowed bytes to be padded in a rom extraction
// this value is currently defined by Nintendo Switch Page Alignment (4096 bytes)
const maxPadBytes = 4095
// objcopyError is an error returned by functions that act like objcopy.
type objcopyError struct {
Op string
@@ -74,12 +71,7 @@ func extractROM(path string) (uint64, []byte, error) {
var rom []byte
for _, prog := range progs {
if prog.Paddr != progs[0].Paddr+uint64(len(rom)) {
diff := prog.Paddr - (progs[0].Paddr + uint64(len(rom)))
if diff > maxPadBytes {
return 0, nil, objcopyError{"ROM segments are non-contiguous: " + path, nil}
}
// Pad the difference
rom = append(rom, make([]byte, diff)...)
return 0, nil, objcopyError{"ROM segments are non-contiguous: " + path, nil}
}
data, err := ioutil.ReadAll(prog.Open())
if err != nil {
@@ -101,7 +93,7 @@ func extractROM(path string) (uint64, []byte, error) {
// objcopy converts an ELF file to a different (simpler) output file format:
// .bin or .hex. It extracts only the .text section.
func objcopy(infile, outfile, binaryFormat string) error {
func objcopy(infile, outfile string) error {
f, err := os.OpenFile(outfile, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0666)
if err != nil {
return err
@@ -115,20 +107,24 @@ func objcopy(infile, outfile, binaryFormat string) error {
}
// Write to the file, in the correct format.
switch binaryFormat {
case "hex":
// Intel hex file, includes the firmware start address.
switch filepath.Ext(outfile) {
case ".gba":
// The address is not stored in a .gba file.
_, err := f.Write(data)
return err
case ".bin":
// The address is not stored in a .bin file (therefore you
// should use .hex files in most cases).
_, err := f.Write(data)
return err
case ".hex":
mem := gohex.NewMemory()
err := mem.AddBinary(uint32(addr), data)
if err != nil {
return objcopyError{"failed to create .hex file", err}
}
return mem.DumpIntelHex(f, 16)
case "bin":
// The start address is not stored in raw firmware files (therefore you
// should use .hex files in most cases).
_, err := f.Write(data)
return err
mem.DumpIntelHex(f, 16) // TODO: handle error
return nil
default:
panic("unreachable")
}
+3 -17
View File
@@ -41,7 +41,6 @@ type cgoPackage struct {
elaboratedTypes map[string]*elaboratedTypeInfo
enums map[string]enumInfo
anonStructNum int
ldflags []string
}
// constantInfo stores some information about a CGo constant found by libclang
@@ -157,7 +156,7 @@ typedef unsigned long long _Cgo_ulonglong;
// newly created *ast.File that should be added to the list of to-be-parsed
// files. If there is one or more error, it returns these in the []error slice
// but still modifies the AST.
func Process(files []*ast.File, dir string, fset *token.FileSet, cflags []string) (*ast.File, []string, []error) {
func Process(files []*ast.File, dir string, fset *token.FileSet, cflags []string) (*ast.File, []error) {
p := &cgoPackage{
dir: dir,
fset: fset,
@@ -184,7 +183,7 @@ func Process(files []*ast.File, dir string, fset *token.FileSet, cflags []string
// Find the absolute path for this package.
packagePath, err := filepath.Abs(fset.File(files[0].Pos()).Name())
if err != nil {
return nil, nil, []error{
return nil, []error{
scanner.Error{
Pos: fset.Position(files[0].Pos()),
Msg: "cgo: cannot find absolute path: " + err.Error(), // TODO: wrap this error
@@ -360,19 +359,6 @@ func Process(files []*ast.File, dir string, fset *token.FileSet, cflags []string
}
makePathsAbsolute(flags, packagePath)
cflags = append(cflags, flags...)
case "LDFLAGS":
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 := checkLinkerFlags(name, flags); err != nil {
p.addErrorAfter(comment.Slash, comment.Text[:lineStart+colon+1], err.Error())
continue
}
makePathsAbsolute(flags, packagePath)
p.ldflags = append(p.ldflags, flags...)
default:
startPos := strings.LastIndex(line[4:colon], name) + 4
p.addErrorAfter(comment.Slash, comment.Text[:lineStart+startPos], "invalid #cgo line: "+name)
@@ -426,7 +412,7 @@ func Process(files []*ast.File, dir string, fset *token.FileSet, cflags []string
// Print the newly generated in-memory AST, for debugging.
//ast.Print(fset, p.generated)
return p.generated, p.ldflags, p.errors
return p.generated, p.errors
}
// makePathsAbsolute converts some common path compiler flags (-I, -L) from
+1 -1
View File
@@ -50,7 +50,7 @@ func TestCGo(t *testing.T) {
}
// Process the AST with CGo.
cgoAST, _, cgoErrors := Process([]*ast.File{f}, "testdata", fset, cflags)
cgoAST, cgoErrors := Process([]*ast.File{f}, "testdata", fset, cflags)
// Check the AST for type errors.
var typecheckErrors []error
+3 -3
View File
@@ -246,9 +246,9 @@ func tinygo_clang_globals_visitor(c, parent C.GoCXCursor, client_data C.CXClient
}
value := source[len(name):]
// Try to convert this #define into a Go constant expression.
expr, scannerError := parseConst(pos+token.Pos(len(name)), p.fset, value)
if scannerError != nil {
p.errors = append(p.errors, *scannerError)
expr, err := parseConst(pos+token.Pos(len(name)), p.fset, value)
if err != nil {
p.errors = append(p.errors, err)
}
if expr != nil {
// Parsing was successful.
-7
View File
@@ -21,13 +21,6 @@ package main
#if defined(NOTDEFINED)
#warning flag must not be defined
#endif
// Check Compiler flags
#cgo LDFLAGS: -lc
// This flag is not valid ldflags
#cgo LDFLAGS: -does-not-exists
*/
import "C"
-1
View File
@@ -1,7 +1,6 @@
// CGo errors:
// testdata/flags.go:5:7: invalid #cgo line: NOFLAGS
// testdata/flags.go:8:13: invalid flag: -fdoes-not-exist
// testdata/flags.go:29:14: invalid flag: -does-not-exists
package main
+2 -59
View File
@@ -129,8 +129,8 @@ func (c *Config) NeedsStackObjects() bool {
}
}
// Scheduler returns the scheduler implementation. Valid values are "none",
//"coroutines" and "tasks".
// Scheduler returns the scheduler implementation. Valid values are "coroutines"
// and "tasks".
func (c *Config) Scheduler() string {
if c.Options.Scheduler != "" {
return c.Options.Scheduler
@@ -164,16 +164,6 @@ func (c *Config) PanicStrategy() string {
return c.Options.PanicStrategy
}
// AutomaticStackSize returns whether goroutine stack sizes should be determined
// automatically at compile time, if possible. If it is false, no attempt is
// made.
func (c *Config) AutomaticStackSize() bool {
if c.Target.AutoStackSize != nil && c.Scheduler() == "tasks" {
return *c.Target.AutoStackSize
}
return false
}
// CFlags returns the flags to pass to the C compiler. This is necessary for CGo
// preprocessing.
func (c *Config) CFlags() []string {
@@ -186,9 +176,6 @@ func (c *Config) CFlags() []string {
cflags = append(cflags, "-nostdlibinc", "-Xclang", "-internal-isystem", "-Xclang", filepath.Join(root, "lib", "picolibc", "newlib", "libc", "include"))
cflags = append(cflags, "-I"+filepath.Join(root, "lib/picolibc-include"))
}
if c.Debug() {
cflags = append(cflags, "-g")
}
return cflags
}
@@ -239,31 +226,6 @@ func (c *Config) Debug() bool {
return c.Options.Debug
}
// BinaryFormat returns an appropriate binary format, based on the file
// extension and the configured binary format in the target JSON file.
func (c *Config) BinaryFormat(ext string) string {
switch ext {
case ".bin", ".gba", ".nro":
// The simplest format possible: dump everything in a raw binary file.
if c.Target.BinaryFormat != "" {
return c.Target.BinaryFormat
}
return "bin"
case ".hex":
// Similar to bin, but includes the start address and is thus usually a
// better format.
return "hex"
case ".uf2":
// Special purpose firmware format, mainly used on Adafruit boards.
// More information:
// https://github.com/Microsoft/uf2
return "uf2"
default:
// Use the ELF format for unrecognized file formats.
return "elf"
}
}
// 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.
@@ -310,25 +272,6 @@ func (c *Config) OpenOCDConfiguration() (args []string, err error) {
return args, nil
}
// CodeModel returns the code model used on this platform.
func (c *Config) CodeModel() string {
if c.Target.CodeModel != "" {
return c.Target.CodeModel
}
return "default"
}
// RelocationModel returns the relocation model in use on this platform. Valid
// values are "static", "pic", "dynamicnopic".
func (c *Config) RelocationModel() string {
if c.Target.RelocationModel != "" {
return c.Target.RelocationModel
}
return "static"
}
type TestConfig struct {
CompileTestBinary bool
// TODO: Filter the test functions to run, include verbose flag, etc
-63
View File
@@ -1,17 +1,5 @@
package compileopts
import (
"fmt"
"strings"
)
var (
validGCOptions = []string{"none", "leaking", "extalloc", "conservative"}
validSchedulerOptions = []string{"none", "tasks", "coroutines"}
validPrintSizeOptions = []string{"none", "short", "full"}
validPanicStrategyOptions = []string{"print", "trap"}
)
// Options contains extra options to give to the compiler. These options are
// usually passed from the command line.
type Options struct {
@@ -25,7 +13,6 @@ type Options struct {
VerifyIR bool
Debug bool
PrintSizes string
PrintStacks bool
CFlags []string
LDFlags []string
Tags string
@@ -34,53 +21,3 @@ type Options struct {
TestConfig TestConfig
Programmer string
}
// Verify performs a validation on the given options, raising an error if options are not valid.
func (o *Options) Verify() error {
if o.GC != "" {
valid := isInArray(validGCOptions, o.GC)
if !valid {
return fmt.Errorf(`invalid gc option '%s': valid values are %s`,
o.GC,
strings.Join(validGCOptions, ", "))
}
}
if o.Scheduler != "" {
valid := isInArray(validSchedulerOptions, o.Scheduler)
if !valid {
return fmt.Errorf(`invalid scheduler option '%s': valid values are %s`,
o.Scheduler,
strings.Join(validSchedulerOptions, ", "))
}
}
if o.PrintSizes != "" {
valid := isInArray(validPrintSizeOptions, o.PrintSizes)
if !valid {
return fmt.Errorf(`invalid size option '%s': valid values are %s`,
o.PrintSizes,
strings.Join(validPrintSizeOptions, ", "))
}
}
if o.PanicStrategy != "" {
valid := isInArray(validPanicStrategyOptions, o.PanicStrategy)
if !valid {
return fmt.Errorf(`invalid panic option '%s': valid values are %s`,
o.PanicStrategy,
strings.Join(validPanicStrategyOptions, ", "))
}
}
return nil
}
func isInArray(arr []string, item string) bool {
for _, i := range arr {
if i == item {
return true
}
}
return false
}
-138
View File
@@ -1,138 +0,0 @@
package compileopts_test
import (
"errors"
"testing"
"github.com/tinygo-org/tinygo/compileopts"
)
func TestVerifyOptions(t *testing.T) {
expectedGCError := errors.New(`invalid gc option 'incorrect': valid values are none, leaking, extalloc, conservative`)
expectedSchedulerError := errors.New(`invalid scheduler option 'incorrect': valid values are none, tasks, coroutines`)
expectedPrintSizeError := errors.New(`invalid size option 'incorrect': valid values are none, short, full`)
expectedPanicStrategyError := errors.New(`invalid panic option 'incorrect': valid values are print, trap`)
testCases := []struct {
name string
opts compileopts.Options
expectedError error
}{
{
name: "OptionsEmpty",
opts: compileopts.Options{},
},
{
name: "InvalidGCOption",
opts: compileopts.Options{
GC: "incorrect",
},
expectedError: expectedGCError,
},
{
name: "GCOptionNone",
opts: compileopts.Options{
GC: "none",
},
},
{
name: "GCOptionLeaking",
opts: compileopts.Options{
GC: "leaking",
},
},
{
name: "GCOptionExtalloc",
opts: compileopts.Options{
GC: "extalloc",
},
},
{
name: "GCOptionConservative",
opts: compileopts.Options{
GC: "conservative",
},
},
{
name: "InvalidSchedulerOption",
opts: compileopts.Options{
Scheduler: "incorrect",
},
expectedError: expectedSchedulerError,
},
{
name: "SchedulerOptionNone",
opts: compileopts.Options{
Scheduler: "none",
},
},
{
name: "SchedulerOptionTasks",
opts: compileopts.Options{
Scheduler: "tasks",
},
},
{
name: "SchedulerOptionCoroutines",
opts: compileopts.Options{
Scheduler: "coroutines",
},
},
{
name: "InvalidPrintSizeOption",
opts: compileopts.Options{
PrintSizes: "incorrect",
},
expectedError: expectedPrintSizeError,
},
{
name: "PrintSizeOptionNone",
opts: compileopts.Options{
PrintSizes: "none",
},
},
{
name: "PrintSizeOptionShort",
opts: compileopts.Options{
PrintSizes: "short",
},
},
{
name: "PrintSizeOptionFull",
opts: compileopts.Options{
PrintSizes: "full",
},
},
{
name: "InvalidPanicOption",
opts: compileopts.Options{
PanicStrategy: "incorrect",
},
expectedError: expectedPanicStrategyError,
},
{
name: "PanicOptionPrint",
opts: compileopts.Options{
PanicStrategy: "print",
},
},
{
name: "PanicOptionTrap",
opts: compileopts.Options{
PanicStrategy: "trap",
},
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
err := tc.opts.Verify()
if tc.expectedError != err {
if tc.expectedError.Error() != err.Error() {
t.Errorf("expected %v, got %v", tc.expectedError, err)
}
}
})
}
}
-21
View File
@@ -33,8 +33,6 @@ type TargetSpec struct {
Linker string `json:"linker"`
RTLib string `json:"rtlib"` // compiler runtime library (libgcc, compiler-rt)
Libc string `json:"libc"`
AutoStackSize *bool `json:"automatic-stack-size"` // Determine stack size automatically at compile time.
DefaultStackSize uint64 `json:"default-stack-size"` // Default stack size if the size couldn't be determined at compile time.
CFlags []string `json:"cflags"`
LDFlags []string `json:"ldflags"`
LinkerScript string `json:"linkerscript"`
@@ -47,13 +45,10 @@ type TargetSpec struct {
FlashVolume string `json:"msd-volume-name"`
FlashFilename string `json:"msd-firmware-name"`
UF2FamilyID string `json:"uf2-family-id"`
BinaryFormat string `json:"binary-format"`
OpenOCDInterface string `json:"openocd-interface"`
OpenOCDTarget string `json:"openocd-target"`
OpenOCDTransport string `json:"openocd-transport"`
JLinkDevice string `json:"jlink-device"`
CodeModel string `json:"code-model"`
RelocationModel string `json:"relocation-model"`
}
// copyProperties copies all properties that are set in spec2 into itself.
@@ -93,12 +88,6 @@ func (spec *TargetSpec) copyProperties(spec2 *TargetSpec) {
if spec2.Libc != "" {
spec.Libc = spec2.Libc
}
if spec2.AutoStackSize != nil {
spec.AutoStackSize = spec2.AutoStackSize
}
if spec2.DefaultStackSize != 0 {
spec.DefaultStackSize = spec2.DefaultStackSize
}
spec.CFlags = append(spec.CFlags, spec2.CFlags...)
spec.LDFlags = append(spec.LDFlags, spec2.LDFlags...)
if spec2.LinkerScript != "" {
@@ -129,9 +118,6 @@ func (spec *TargetSpec) copyProperties(spec2 *TargetSpec) {
if spec2.UF2FamilyID != "" {
spec.UF2FamilyID = spec2.UF2FamilyID
}
if spec2.BinaryFormat != "" {
spec.BinaryFormat = spec2.BinaryFormat
}
if spec2.OpenOCDInterface != "" {
spec.OpenOCDInterface = spec2.OpenOCDInterface
}
@@ -144,13 +130,6 @@ func (spec *TargetSpec) copyProperties(spec2 *TargetSpec) {
if spec2.JLinkDevice != "" {
spec.JLinkDevice = spec2.JLinkDevice
}
if spec2.CodeModel != "" {
spec.CodeModel = spec2.CodeModel
}
if spec2.RelocationModel != "" {
spec.RelocationModel = spec2.RelocationModel
}
}
// load reads a target specification from the JSON in the given io.Reader. It
+6 -6
View File
@@ -16,7 +16,7 @@ import (
// slice. This is required by the Go language spec: an index out of bounds must
// cause a panic.
func (b *builder) createLookupBoundsCheck(arrayLen, index llvm.Value, indexType types.Type) {
if b.fn.IsNoBounds() {
if b.info.nobounds {
// The //go:nobounds pragma was added to the function to avoid bounds
// checking.
return
@@ -48,7 +48,7 @@ func (b *builder) createLookupBoundsCheck(arrayLen, index llvm.Value, indexType
// biggest possible slice capacity, 'low' means len and 'high' means cap. The
// logic is the same in both cases.
func (b *builder) createSliceBoundsCheck(capacity, low, high, max llvm.Value, lowType, highType, maxType *types.Basic) {
if b.fn.IsNoBounds() {
if b.info.nobounds {
// The //go:nobounds pragma was added to the function to avoid bounds
// checking.
return
@@ -104,7 +104,7 @@ func (b *builder) createSliceBoundsCheck(capacity, low, high, max llvm.Value, lo
// createChanBoundsCheck creates a bounds check before creating a new channel to
// check that the value is not too big for runtime.chanMake.
func (b *builder) createChanBoundsCheck(elementSize uint64, bufSize llvm.Value, bufSizeType *types.Basic, pos token.Pos) {
if b.fn.IsNoBounds() {
if b.info.nobounds {
// The //go:nobounds pragma was added to the function to avoid bounds
// checking.
return
@@ -189,7 +189,7 @@ func (b *builder) createNilCheck(inst ssa.Value, ptr llvm.Value, blockPrefix str
// createNegativeShiftCheck creates an assertion that panics if the given shift value is negative.
// This function assumes that the shift value is signed.
func (b *builder) createNegativeShiftCheck(shift llvm.Value) {
if b.fn.IsNoBounds() {
if b.info.nobounds {
// Function disabled bounds checking - skip shift check.
return
}
@@ -212,8 +212,8 @@ func (b *builder) createRuntimeAssert(assert llvm.Value, blockPrefix, assertFunc
}
}
faultBlock := b.ctx.AddBasicBlock(b.fn.LLVMFn, blockPrefix+".throw")
nextBlock := b.ctx.AddBasicBlock(b.fn.LLVMFn, blockPrefix+".next")
faultBlock := b.ctx.AddBasicBlock(b.llvmFn, blockPrefix+".throw")
nextBlock := b.ctx.AddBasicBlock(b.llvmFn, blockPrefix+".next")
b.blockExits[b.currentBlock] = nextBlock // adjust outgoing block for phi nodes
// Now branch to the out-of-bounds or the regular block.
-57
View File
@@ -1,57 +0,0 @@
package compiler
import (
"golang.org/x/tools/go/ssa"
"tinygo.org/x/go-llvm"
)
// createAtomicOp lowers an atomic library call by lowering it as an LLVM atomic
// operation. It returns the result of the operation and true if the call could
// be lowered inline, and false otherwise.
func (b *builder) createAtomicOp(call *ssa.CallCommon) (llvm.Value, bool) {
name := call.Value.(*ssa.Function).Name()
switch name {
case "AddInt32", "AddInt64", "AddUint32", "AddUint64", "AddUintptr":
ptr := b.getValue(call.Args[0])
val := b.getValue(call.Args[1])
oldVal := b.CreateAtomicRMW(llvm.AtomicRMWBinOpAdd, ptr, val, llvm.AtomicOrderingSequentiallyConsistent, true)
// Return the new value, not the original value returned by atomicrmw.
return b.CreateAdd(oldVal, val, ""), true
case "SwapInt32", "SwapInt64", "SwapUint32", "SwapUint64", "SwapUintptr", "SwapPointer":
ptr := b.getValue(call.Args[0])
val := b.getValue(call.Args[1])
isPointer := val.Type().TypeKind() == llvm.PointerTypeKind
if isPointer {
// atomicrmw only supports integers, so cast to an integer.
val = b.CreatePtrToInt(val, b.uintptrType, "")
ptr = b.CreateBitCast(ptr, llvm.PointerType(val.Type(), 0), "")
}
oldVal := b.CreateAtomicRMW(llvm.AtomicRMWBinOpXchg, ptr, val, llvm.AtomicOrderingSequentiallyConsistent, true)
if isPointer {
oldVal = b.CreateIntToPtr(oldVal, b.i8ptrType, "")
}
return oldVal, true
case "CompareAndSwapInt32", "CompareAndSwapInt64", "CompareAndSwapUint32", "CompareAndSwapUint64", "CompareAndSwapUintptr", "CompareAndSwapPointer":
ptr := b.getValue(call.Args[0])
old := b.getValue(call.Args[1])
newVal := b.getValue(call.Args[2])
tuple := b.CreateAtomicCmpXchg(ptr, old, newVal, llvm.AtomicOrderingSequentiallyConsistent, llvm.AtomicOrderingSequentiallyConsistent, true)
swapped := b.CreateExtractValue(tuple, 1, "")
return swapped, true
case "LoadInt32", "LoadInt64", "LoadUint32", "LoadUint64", "LoadUintptr", "LoadPointer":
ptr := b.getValue(call.Args[0])
val := b.CreateLoad(ptr, "")
val.SetOrdering(llvm.AtomicOrderingSequentiallyConsistent)
val.SetAlignment(b.targetData.PrefTypeAlignment(val.Type())) // required
return val, true
case "StoreInt32", "StoreInt64", "StoreUint32", "StoreUint64", "StoreUintptr", "StorePointer":
ptr := b.getValue(call.Args[0])
val := b.getValue(call.Args[1])
store := b.CreateStore(val, ptr)
store.SetOrdering(llvm.AtomicOrderingSequentiallyConsistent)
store.SetAlignment(b.targetData.PrefTypeAlignment(val.Type())) // required
return store, true
default:
return llvm.Value{}, false
}
}
+4 -6
View File
@@ -34,14 +34,12 @@ const (
// createCall creates a new call to runtime.<fnName> with the given arguments.
func (b *builder) createRuntimeCall(fnName string, args []llvm.Value, name string) llvm.Value {
fullName := "runtime." + fnName
fn := b.mod.NamedFunction(fullName)
if fn.IsNil() {
panic("trying to call non-existent function: " + fullName)
}
llvmFn := b.getFunctionRaw(b.getRuntimeFuncType(fnName), functionInfo{
linkName: "runtime." + fnName,
})
args = append(args, llvm.Undef(b.i8ptrType)) // unused context parameter
args = append(args, llvm.ConstPointerNull(b.i8ptrType)) // coroutine handle
return b.createCall(fn, args, name)
return b.createCall(llvmFn, args, name)
}
// createCall creates a call to the given function with the arguments possibly
+6 -16
View File
@@ -12,7 +12,7 @@ import (
)
func (b *builder) createMakeChan(expr *ssa.MakeChan) llvm.Value {
elementSize := b.targetData.TypeAllocSize(b.getLLVMType(expr.Type().Underlying().(*types.Chan).Elem()))
elementSize := b.targetData.TypeAllocSize(b.getLLVMType(expr.Type().(*types.Chan).Elem()))
elementSizeValue := llvm.ConstInt(b.uintptrType, elementSize, false)
bufSize := b.getValue(expr.Size)
b.createChanBoundsCheck(elementSize, bufSize, expr.Size.Type().Underlying().(*types.Basic), expr.Pos())
@@ -35,37 +35,27 @@ func (b *builder) createChanSend(instr *ssa.Send) {
valueAlloca, valueAllocaCast, valueAllocaSize := b.createTemporaryAlloca(valueType, "chan.value")
b.CreateStore(chanValue, valueAlloca)
// Allocate blockedlist buffer.
channelBlockedList := b.mod.GetTypeByName("runtime.channelBlockedList")
channelBlockedListAlloca, channelBlockedListAllocaCast, channelBlockedListAllocaSize := b.createTemporaryAlloca(channelBlockedList, "chan.blockedList")
// Do the send.
b.createRuntimeCall("chanSend", []llvm.Value{ch, valueAllocaCast, channelBlockedListAlloca}, "")
b.createRuntimeCall("chanSend", []llvm.Value{ch, valueAllocaCast}, "")
// End the lifetime of the allocas.
// End the lifetime of the alloca.
// This also works around a bug in CoroSplit, at least in LLVM 8:
// https://bugs.llvm.org/show_bug.cgi?id=41742
b.emitLifetimeEnd(channelBlockedListAllocaCast, channelBlockedListAllocaSize)
b.emitLifetimeEnd(valueAllocaCast, valueAllocaSize)
}
// createChanRecv emits a pseudo chan receive operation. It is lowered to the
// actual channel receive operation during goroutine lowering.
func (b *builder) createChanRecv(unop *ssa.UnOp) llvm.Value {
valueType := b.getLLVMType(unop.X.Type().Underlying().(*types.Chan).Elem())
valueType := b.getLLVMType(unop.X.Type().(*types.Chan).Elem())
ch := b.getValue(unop.X)
// Allocate memory to receive into.
valueAlloca, valueAllocaCast, valueAllocaSize := b.createTemporaryAlloca(valueType, "chan.value")
// Allocate blockedlist buffer.
channelBlockedList := b.mod.GetTypeByName("runtime.channelBlockedList")
channelBlockedListAlloca, channelBlockedListAllocaCast, channelBlockedListAllocaSize := b.createTemporaryAlloca(channelBlockedList, "chan.blockedList")
// Do the receive.
commaOk := b.createRuntimeCall("chanRecv", []llvm.Value{ch, valueAllocaCast, channelBlockedListAlloca}, "")
commaOk := b.createRuntimeCall("chanRecv", []llvm.Value{ch, valueAllocaCast}, "")
received := b.CreateLoad(valueAlloca, "chan.received")
b.emitLifetimeEnd(channelBlockedListAllocaCast, channelBlockedListAllocaSize)
b.emitLifetimeEnd(valueAllocaCast, valueAllocaSize)
if unop.CommaOk {
@@ -127,7 +117,7 @@ func (b *builder) createSelect(expr *ssa.Select) llvm.Value {
switch state.Dir {
case types.RecvOnly:
// Make sure the receive buffer is big enough and has the correct alignment.
llvmType := b.getLLVMType(state.Chan.Type().Underlying().(*types.Chan).Elem())
llvmType := b.getLLVMType(state.Chan.Type().(*types.Chan).Elem())
if size := b.targetData.TypeAllocSize(llvmType); size > recvbufSize {
recvbufSize = size
}
+388 -291
View File
@@ -1,20 +1,25 @@
package compiler
//go:generate go run ./mkruntimetypes.go
import (
"debug/dwarf"
"errors"
"fmt"
"go/ast"
"go/build"
"go/constant"
"go/token"
"go/types"
"os"
"path/filepath"
"sort"
"strconv"
"strings"
"github.com/tinygo-org/tinygo/compileopts"
"github.com/tinygo-org/tinygo/compiler/llvmutil"
"github.com/tinygo-org/tinygo/ir"
"github.com/tinygo-org/tinygo/goenv"
"github.com/tinygo-org/tinygo/loader"
"golang.org/x/tools/go/ssa"
"tinygo.org/x/go-llvm"
@@ -46,18 +51,23 @@ type compilerContext struct {
targetData llvm.TargetData
intType llvm.Type
i8ptrType llvm.Type // for convenience
runtimeTypes map[string]types.Type
funcPtrAddrSpace int
uintptrType llvm.Type
ir *ir.Program
program *ssa.Program
diagnostics []error
astComments map[string]*ast.CommentGroup
runtimePkg *types.Package // package runtime
taskPkg *types.Package // package internal/task
}
// builder contains all information relevant to build a single function.
type builder struct {
*compilerContext
llvm.Builder
fn *ir.Function
fn *ssa.Function
llvmFn llvm.Value
info functionInfo
locals map[ssa.Value]llvm.Value // local variables
blockEntries map[*ssa.BasicBlock]llvm.BasicBlock // a *ssa.BasicBlock may be split up
blockExits map[*ssa.BasicBlock]llvm.BasicBlock // these are the exit blocks
@@ -68,17 +78,10 @@ type builder struct {
difunc llvm.Metadata
dilocals map[*types.Var]llvm.Metadata
allDeferFuncs []interface{}
deferFuncs map[*ir.Function]int
deferFuncs map[*ssa.Function]int
deferInvokeFuncs map[string]int
deferClosureFuncs map[*ir.Function]int
deferExprFuncs map[ssa.Value]int
deferClosureFuncs map[*ssa.Function]int
selectRecvBuf map[*ssa.Select]llvm.Value
deferBuiltinFuncs map[ssa.Value]deferBuiltin
}
type deferBuiltin struct {
funcName string
callback int
}
type phiNode struct {
@@ -95,75 +98,20 @@ func NewTargetMachine(config *compileopts.Config) (llvm.TargetMachine, error) {
return llvm.TargetMachine{}, err
}
features := strings.Join(config.Features(), ",")
var codeModel llvm.CodeModel
var relocationModel llvm.RelocMode
switch config.CodeModel() {
case "default":
codeModel = llvm.CodeModelDefault
case "tiny":
codeModel = llvm.CodeModelTiny
case "small":
codeModel = llvm.CodeModelSmall
case "kernel":
codeModel = llvm.CodeModelKernel
case "medium":
codeModel = llvm.CodeModelMedium
case "large":
codeModel = llvm.CodeModelLarge
}
switch config.RelocationModel() {
case "static":
relocationModel = llvm.RelocStatic
case "pic":
relocationModel = llvm.RelocPIC
case "dynamicnopic":
relocationModel = llvm.RelocDynamicNoPic
}
machine := target.CreateTargetMachine(config.Triple(), config.CPU(), features, llvm.CodeGenLevelDefault, relocationModel, codeModel)
machine := target.CreateTargetMachine(config.Triple(), config.CPU(), features, llvm.CodeGenLevelDefault, llvm.RelocStatic, llvm.CodeModelDefault)
return machine, nil
}
// CompilerOutput is returned from the Compile() call. It contains the compile
// output and information necessary to continue to compile and link the program.
type CompilerOutput struct {
// The LLVM module that contains the compiled but not optimized LLVM module
// for all the Go code in the program.
Mod llvm.Module
// ExtraFiles is a list of C source files included in packages that should
// be built and linked together with the main executable to form one
// program. They can be used from CGo, for example.
ExtraFiles []string
// ExtraLDFlags are linker flags obtained during CGo processing. These flags
// must be passed to the linker which links the entire executable.
ExtraLDFlags []string
// MainDir is the absolute directory path to the directory of the main
// package. This is useful for testing: tests must be run in the package
// directory that is being tested.
MainDir string
}
// Compile the given package path or .go file path. Return an error when this
// fails (in any stage). If successful it returns the LLVM module and a list of
// extra C files to be compiled. If not, one or more errors will be returned.
//
// The fact that it returns a list of filenames to compile is a layering
// violation. Eventually, this Compile function should only compile a single
// package and not the whole program, and loading of the program (including CGo
// processing) should be moved outside the compiler package.
func Compile(pkgName string, machine llvm.TargetMachine, config *compileopts.Config) (output CompilerOutput, errors []error) {
// newCompilerContext builds a new *compilerContext based on the provided
// configuration, ready to compile Go SSA to LLVM IR.
func newCompilerContext(pkgName string, machine llvm.TargetMachine, config *compileopts.Config) *compilerContext {
c := &compilerContext{
Config: config,
difiles: make(map[string]llvm.Metadata),
ditypes: make(map[types.Type]llvm.Metadata),
machine: machine,
targetData: machine.CreateTargetData(),
Config: config,
difiles: make(map[string]llvm.Metadata),
ditypes: make(map[types.Type]llvm.Metadata),
machine: machine,
targetData: machine.CreateTargetData(),
runtimeTypes: make(map[string]types.Type),
}
c.ctx = llvm.NewContext()
@@ -173,7 +121,6 @@ func Compile(pkgName string, machine llvm.TargetMachine, config *compileopts.Con
if c.Debug() {
c.dibuilder = llvm.NewDIBuilder(c.mod)
}
output.Mod = c.mod
c.uintptrType = c.ctx.IntType(c.targetData.PointerSize() * 8)
if c.targetData.PointerSize() <= 4 {
@@ -192,31 +139,122 @@ func Compile(pkgName string, machine llvm.TargetMachine, config *compileopts.Con
c.funcPtrAddrSpace = dummyFunc.Type().PointerAddressSpace()
dummyFunc.EraseFromParentAsFunction()
lprogram, err := loader.Load(c.Config, []string{pkgName}, c.ClangHeaders, types.Config{
Sizes: &stdSizes{
IntSize: int64(c.targetData.TypeAllocSize(c.intType)),
PtrSize: int64(c.targetData.PointerSize()),
MaxAlign: int64(c.targetData.PrefTypeAlignment(c.i8ptrType)),
}})
if err != nil {
return output, []error{err}
return c
}
// Compile the given package path or .go file path. Return an error when this
// fails (in any stage). If successful it returns the LLVM module and a list of
// extra C files to be compiled. If not, one or more errors will be returned.
//
// The fact that it returns a list of filenames to compile is a layering
// violation. Eventually, this Compile function should only compile a single
// package and not the whole program, and loading of the program (including CGo
// processing) should be moved outside the compiler package.
func Compile(pkgName string, machine llvm.TargetMachine, config *compileopts.Config) (llvm.Module, []string, []error) {
c := newCompilerContext(pkgName, machine, config)
// Prefix the GOPATH with the system GOROOT, as GOROOT is already set to
// the TinyGo root.
overlayGopath := goenv.Get("GOPATH")
if overlayGopath == "" {
overlayGopath = goenv.Get("GOROOT")
} else {
overlayGopath = goenv.Get("GOROOT") + string(filepath.ListSeparator) + overlayGopath
}
err = lprogram.Parse()
wd, err := os.Getwd()
if err != nil {
return output, []error{err}
return c.mod, nil, []error{err}
}
lprogram := &loader.Program{
Build: &build.Context{
GOARCH: c.GOARCH(),
GOOS: c.GOOS(),
GOROOT: goenv.Get("GOROOT"),
GOPATH: goenv.Get("GOPATH"),
CgoEnabled: c.CgoEnabled(),
UseAllFiles: false,
Compiler: "gc", // must be one of the recognized compilers
BuildTags: c.BuildTags(),
},
OverlayBuild: &build.Context{
GOARCH: c.GOARCH(),
GOOS: c.GOOS(),
GOROOT: goenv.Get("TINYGOROOT"),
GOPATH: overlayGopath,
CgoEnabled: c.CgoEnabled(),
UseAllFiles: false,
Compiler: "gc", // must be one of the recognized compilers
BuildTags: c.BuildTags(),
},
OverlayPath: func(path string) string {
// Return the (overlay) import path when it should be overlaid, and
// "" if it should not.
if strings.HasPrefix(path, tinygoPath+"/src/") {
// Avoid issues with packages that are imported twice, one from
// GOPATH and one from TINYGOPATH.
path = path[len(tinygoPath+"/src/"):]
}
switch path {
case "machine", "os", "reflect", "runtime", "runtime/interrupt", "runtime/volatile", "sync", "testing", "internal/reflectlite", "internal/task":
return path
default:
if strings.HasPrefix(path, "device/") || strings.HasPrefix(path, "examples/") {
return path
} else if path == "syscall" {
for _, tag := range c.BuildTags() {
if tag == "baremetal" || tag == "darwin" {
return path
}
}
}
}
return ""
},
TypeChecker: types.Config{
Sizes: &stdSizes{
IntSize: int64(c.targetData.TypeAllocSize(c.intType)),
PtrSize: int64(c.targetData.PointerSize()),
MaxAlign: int64(c.targetData.PrefTypeAlignment(c.i8ptrType)),
},
},
Dir: wd,
TINYGOROOT: goenv.Get("TINYGOROOT"),
CFlags: c.CFlags(),
ClangHeaders: c.ClangHeaders,
}
output.ExtraLDFlags = lprogram.LDFlags
output.MainDir = lprogram.MainPkg().Dir
c.ir = ir.NewProgram(lprogram)
if strings.HasSuffix(pkgName, ".go") {
_, err = lprogram.ImportFile(pkgName)
if err != nil {
return c.mod, nil, []error{err}
}
} else {
_, err = lprogram.Import(pkgName, wd, token.Position{
Filename: "build command-line-arguments",
})
if err != nil {
return c.mod, nil, []error{err}
}
}
// Run a simple dead code elimination pass.
err = c.ir.SimpleDCE()
_, err = lprogram.Import("runtime", "", token.Position{
Filename: "build default import",
})
if err != nil {
return output, []error{err}
return c.mod, nil, []error{err}
}
err = lprogram.Parse(c.TestConfig.CompileTestBinary)
if err != nil {
return c.mod, nil, []error{err}
}
c.program = lprogram.LoadSSA()
c.program.Build()
c.runtimePkg = c.program.ImportedPackage("runtime").Pkg
c.taskPkg = c.program.ImportedPackage("internal/task").Pkg
// Initialize debug information.
if c.Debug() {
c.cu = c.dibuilder.CreateCompileUnit(llvm.DICompileUnit{
@@ -230,66 +268,44 @@ func Compile(pkgName string, machine llvm.TargetMachine, config *compileopts.Con
c.loadASTComments(lprogram)
// Declare runtime types.
// TODO: lazily create runtime types in getLLVMRuntimeType when they are
// needed. Eventually this will be required anyway, when packages are
// compiled independently (and the runtime types are not available).
for _, member := range c.ir.Program.ImportedPackage("runtime").Members {
if member, ok := member.(*ssa.Type); ok {
if typ, ok := member.Type().(*types.Named); ok {
if _, ok := typ.Underlying().(*types.Struct); ok {
c.getLLVMType(typ)
// Predeclare the runtime.alloc function, which is used by the wordpack
// functionality.
c.getFunction(c.program.ImportedPackage("runtime").Members["alloc"].(*ssa.Function))
sortedPackages := sortPackages(c.program, pkgName)
// Find package initializers.
var initFuncs []llvm.Value
for _, pkg := range sortedPackages {
for _, member := range pkg.Members {
switch member := member.(type) {
case *ssa.Function:
if member.Synthetic == "package initializer" {
initFuncs = append(initFuncs, c.getFunction(member))
}
}
}
}
// Declare all functions.
for _, f := range c.ir.Functions {
c.createFunctionDeclaration(f)
}
// Add definitions to declarations.
var initFuncs []llvm.Value
irbuilder := c.ctx.NewBuilder()
defer irbuilder.Dispose()
for _, f := range c.ir.Functions {
if f.Synthetic == "package initializer" {
initFuncs = append(initFuncs, f.LLVMFn)
}
if f.CName() != "" {
continue
}
if f.Blocks == nil {
continue // external function
}
// Create the function definition.
b := builder{
compilerContext: c,
Builder: irbuilder,
fn: f,
locals: make(map[ssa.Value]llvm.Value),
dilocals: make(map[*types.Var]llvm.Metadata),
blockEntries: make(map[*ssa.BasicBlock]llvm.BasicBlock),
blockExits: make(map[*ssa.BasicBlock]llvm.BasicBlock),
}
b.createFunctionDefinition()
for _, pkg := range sortedPackages {
c.createPackage(pkg, irbuilder)
}
// After all packages are imported, add a synthetic initializer function
// that calls the initializer of each package.
initFn := c.ir.GetFunction(c.ir.Program.ImportedPackage("runtime").Members["initAll"].(*ssa.Function))
initFn.LLVMFn.SetLinkage(llvm.InternalLinkage)
initFn.LLVMFn.SetUnnamedAddr(true)
initFn := c.program.ImportedPackage("runtime").Members["initAll"].(*ssa.Function)
llvmInitFn := c.getFunction(initFn)
llvmInitFn.SetLinkage(llvm.InternalLinkage)
llvmInitFn.SetUnnamedAddr(true)
if c.Debug() {
difunc := c.attachDebugInfo(initFn)
pos := c.ir.Program.Fset.Position(initFn.Pos())
pos := c.program.Fset.Position(initFn.Pos())
irbuilder.SetCurrentDebugLocation(uint(pos.Line), uint(pos.Column), difunc, llvm.Metadata{})
}
initFn.LLVMFn.Param(0).SetName("context")
initFn.LLVMFn.Param(1).SetName("parentHandle")
block := c.ctx.AddBasicBlock(initFn.LLVMFn, "entry")
block := c.ctx.AddBasicBlock(llvmInitFn, "entry")
irbuilder.SetInsertPointAtEnd(block)
for _, fn := range initFuncs {
irbuilder.CreateCall(fn, []llvm.Value{llvm.Undef(c.i8ptrType), llvm.Undef(c.i8ptrType)}, "")
@@ -298,7 +314,7 @@ func Compile(pkgName string, machine llvm.TargetMachine, config *compileopts.Con
// Conserve for goroutine lowering. Without marking these as external, they
// would be optimized away.
realMain := c.mod.NamedFunction(c.ir.MainPkg().Pkg.Path() + ".main")
realMain := c.mod.NamedFunction(pkgName + ".main")
realMain.SetLinkage(llvm.ExternalLinkage) // keep alive until goroutine lowering
// Replace callMain placeholder with actual main function.
@@ -353,26 +369,92 @@ func Compile(pkgName string, machine llvm.TargetMachine, config *compileopts.Con
}
// Gather the list of (C) file paths that should be included in the build.
for _, pkg := range c.ir.LoaderProgram.Sorted() {
for _, filename := range pkg.CFiles {
output.ExtraFiles = append(output.ExtraFiles, filepath.Join(pkg.Dir, filename))
var extraFiles []string
for _, pkg := range lprogram.Sorted() {
for _, file := range pkg.CFiles {
extraFiles = append(extraFiles, filepath.Join(pkg.Package.Dir, file))
}
}
return output, c.diagnostics
return c.mod, extraFiles, c.diagnostics
}
// sortPackages returns a list of all packages, sorted by import order.
func sortPackages(program *ssa.Program, mainPath string) []*ssa.Package {
// Find the main package, which is a bit difficult when running a .go file
// directly.
mainPkg := program.ImportedPackage(mainPath)
if mainPkg == nil {
for _, pkgInfo := range program.AllPackages() {
if pkgInfo.Pkg.Name() == "main" {
if mainPkg != nil {
panic("more than one main package found")
}
mainPkg = pkgInfo
}
}
}
if mainPkg == nil {
panic("could not find main package")
}
packageList := []*ssa.Package{}
packageSet := map[string]struct{}{}
worklist := []string{"runtime", mainPath}
for len(worklist) != 0 {
pkgPath := worklist[0]
var pkg *ssa.Package
if pkgPath == mainPath {
pkg = mainPkg // necessary for compiling individual .go files
} else {
pkg = program.ImportedPackage(pkgPath)
}
if pkg == nil {
// Non-SSA package (e.g. cgo).
packageSet[pkgPath] = struct{}{}
worklist = worklist[1:]
continue
}
if _, ok := packageSet[pkgPath]; ok {
// Package already in the final package list.
worklist = worklist[1:]
continue
}
unsatisfiedImports := make([]string, 0)
imports := pkg.Pkg.Imports()
for _, pkg := range imports {
if _, ok := packageSet[pkg.Path()]; ok {
continue
}
unsatisfiedImports = append(unsatisfiedImports, pkg.Path())
}
if len(unsatisfiedImports) == 0 {
// All dependencies of this package are satisfied, so add this
// package to the list.
packageList = append(packageList, pkg)
packageSet[pkgPath] = struct{}{}
worklist = worklist[1:]
} else {
// Prepend all dependencies to the worklist and reconsider this
// package (by not removing it from the worklist). At that point, it
// must be possible to add it to packageList.
worklist = append(unsatisfiedImports, worklist...)
}
}
return packageList
}
// getLLVMRuntimeType obtains a named type from the runtime package and returns
// it as a LLVM type, creating it if necessary. It is a shorthand for
// getLLVMType(getRuntimeType(name)).
// it as a LLVM type, creating it if necessary.
func (c *compilerContext) getLLVMRuntimeType(name string) llvm.Type {
fullName := "runtime." + name
typ := c.mod.GetTypeByName(fullName)
if typ.IsNil() {
println(c.mod.String())
panic("could not find runtime type: " + fullName)
llvmType := c.mod.GetTypeByName(fullName)
if llvmType.IsNil() {
llvmType = c.getLLVMType(c.getRuntimeType(name))
}
return typ
return llvmType
}
// getLLVMType creates and returns a LLVM type for a Go type. In the case of
@@ -432,6 +514,10 @@ func (c *compilerContext) getLLVMType(goType types.Type) llvm.Type {
llvmType = c.ctx.StructCreateNamed(llvmName)
underlying := c.getLLVMType(st)
llvmType.StructSetBody(underlying.StructElementTypes(), false)
} else if typ.String() == "internal/task.Task" && llvmType.StructElementTypesCount() == 0 {
// Note: this struct is an opaque struct. Give it a body.
underlying := c.getLLVMType(st)
llvmType.StructSetBody(underlying.StructElementTypes(), false)
}
return llvmType
}
@@ -452,6 +538,23 @@ func (c *compilerContext) getLLVMType(goType types.Type) llvm.Type {
case *types.Struct:
members := make([]llvm.Type, typ.NumFields())
for i := 0; i < typ.NumFields(); i++ {
if ptr, ok := typ.Field(i).Type().(*types.Pointer); ok {
if named, ok := ptr.Elem().(*types.Named); ok && named.String() == "internal/task.Task" {
// Special workaround for internal/task.Task. It is
// referenced from the runtime.channel type, which
// references runtime.channelBlockedList, which references
// internal/task.Task. To avoid having to define
// internal/task.Task as a compiler-internal type, make the
// type opaque.
ptrTo := c.mod.GetTypeByName(named.String())
if ptrTo.IsNil() {
ptrTo = c.ctx.StructCreateNamed(named.String())
}
members[i] = llvm.PointerType(ptrTo, 0)
continue
}
}
members[i] = c.getLLVMType(typ.Field(i).Type())
}
return c.ctx.StructType(members, false)
@@ -555,11 +658,11 @@ func (c *compilerContext) createDIType(typ types.Type) llvm.Metadata {
Encoding: encoding,
})
case *types.Chan:
return c.getDIType(types.NewPointer(c.ir.Program.ImportedPackage("runtime").Members["channel"].(*ssa.Type).Type()))
return c.getDIType(types.NewPointer(c.getRuntimeType("channel")))
case *types.Interface:
return c.getDIType(c.ir.Program.ImportedPackage("runtime").Members["_interface"].(*ssa.Type).Type())
return c.getDIType(c.getRuntimeType("_interface"))
case *types.Map:
return c.getDIType(types.NewPointer(c.ir.Program.ImportedPackage("runtime").Members["hashmap"].(*ssa.Type).Type()))
return c.getDIType(types.NewPointer(c.getRuntimeType("hashmap")))
case *types.Named:
return c.dibuilder.CreateTypedef(llvm.DITypedef{
Type: c.getDIType(typ.Underlying()),
@@ -667,7 +770,7 @@ func (b *builder) getLocalVariable(variable *types.Var) llvm.Metadata {
return dilocal
}
pos := b.ir.Program.Fset.Position(variable.Pos())
pos := b.program.Fset.Position(variable.Pos())
// Check whether this is a function parameter.
for i, param := range b.fn.Params {
@@ -698,95 +801,17 @@ func (b *builder) getLocalVariable(variable *types.Var) llvm.Metadata {
return dilocal
}
// createFunctionDeclaration creates a LLVM function declaration without body.
// It can later be filled with frame.createFunctionDefinition().
func (c *compilerContext) createFunctionDeclaration(f *ir.Function) {
var retType llvm.Type
if f.Signature.Results() == nil {
retType = c.ctx.VoidType()
} else if f.Signature.Results().Len() == 1 {
retType = c.getLLVMType(f.Signature.Results().At(0).Type())
} else {
results := make([]llvm.Type, 0, f.Signature.Results().Len())
for i := 0; i < f.Signature.Results().Len(); i++ {
results = append(results, c.getLLVMType(f.Signature.Results().At(i).Type()))
}
retType = c.ctx.StructType(results, false)
}
var paramInfos []paramInfo
for _, param := range f.Params {
paramType := c.getLLVMType(param.Type())
paramFragmentInfos := expandFormalParamType(paramType, param.Name(), param.Type())
paramInfos = append(paramInfos, paramFragmentInfos...)
}
// Add an extra parameter as the function context. This context is used in
// closures and bound methods, but should be optimized away when not used.
if !f.IsExported() {
paramInfos = append(paramInfos, paramInfo{llvmType: c.i8ptrType, name: "context", flags: 0})
paramInfos = append(paramInfos, paramInfo{llvmType: c.i8ptrType, name: "parentHandle", flags: 0})
}
var paramTypes []llvm.Type
for _, info := range paramInfos {
paramTypes = append(paramTypes, info.llvmType)
}
fnType := llvm.FunctionType(retType, paramTypes, false)
name := f.LinkName()
f.LLVMFn = c.mod.NamedFunction(name)
if f.LLVMFn.IsNil() {
f.LLVMFn = llvm.AddFunction(c.mod, name, fnType)
}
dereferenceableOrNullKind := llvm.AttributeKindID("dereferenceable_or_null")
for i, info := range paramInfos {
if info.flags&paramIsDeferenceableOrNull == 0 {
continue
}
if info.llvmType.TypeKind() == llvm.PointerTypeKind {
el := info.llvmType.ElementType()
size := c.targetData.TypeAllocSize(el)
if size == 0 {
// dereferenceable_or_null(0) appears to be illegal in LLVM.
continue
}
dereferenceableOrNull := c.ctx.CreateEnumAttribute(dereferenceableOrNullKind, size)
f.LLVMFn.AddAttributeAtIndex(i+1, dereferenceableOrNull)
}
}
// External/exported functions may not retain pointer values.
// https://golang.org/cmd/cgo/#hdr-Passing_pointers
if f.IsExported() {
// Set the wasm-import-module attribute if the function's module is set.
if f.Module() != "" {
wasmImportModuleAttr := c.ctx.CreateStringAttribute("wasm-import-module", f.Module())
f.LLVMFn.AddFunctionAttr(wasmImportModuleAttr)
}
nocaptureKind := llvm.AttributeKindID("nocapture")
nocapture := c.ctx.CreateEnumAttribute(nocaptureKind, 0)
for i, typ := range paramTypes {
if typ.TypeKind() == llvm.PointerTypeKind {
f.LLVMFn.AddAttributeAtIndex(i+1, nocapture)
}
}
}
}
// attachDebugInfo adds debug info to a function declaration. It returns the
// DISubprogram metadata node.
func (c *compilerContext) attachDebugInfo(f *ir.Function) llvm.Metadata {
pos := c.ir.Program.Fset.Position(f.Syntax().Pos())
return c.attachDebugInfoRaw(f, f.LLVMFn, "", pos.Filename, pos.Line)
func (c *compilerContext) attachDebugInfo(f *ssa.Function) llvm.Metadata {
pos := c.program.Fset.Position(f.Syntax().Pos())
return c.attachDebugInfoRaw(f, c.getFunction(f), "", pos.Filename, pos.Line)
}
// attachDebugInfo adds debug info to a function declaration. It returns the
// DISubprogram metadata node. This method allows some more control over how
// debug info is added to the function.
func (c *compilerContext) attachDebugInfoRaw(f *ir.Function, llvmFn llvm.Value, suffix, filename string, line int) llvm.Metadata {
func (c *compilerContext) attachDebugInfoRaw(f *ssa.Function, llvmFn llvm.Value, suffix, filename string, line int) llvm.Metadata {
// Debug info for this function.
diparams := make([]llvm.Metadata, 0, len(f.Params))
for _, param := range f.Params {
@@ -799,7 +824,7 @@ func (c *compilerContext) attachDebugInfoRaw(f *ir.Function, llvmFn llvm.Value,
})
difunc := c.dibuilder.CreateFunction(c.getDIFile(filename), llvm.DIFunction{
Name: f.RelString(nil) + suffix,
LinkageName: f.LinkName() + suffix,
LinkageName: c.getFunctionInfo(f).linkName + suffix,
File: c.getDIFile(filename),
Line: line,
Type: diFuncType,
@@ -827,37 +852,99 @@ func (c *compilerContext) getDIFile(filename string) llvm.Metadata {
return c.difiles[filename]
}
// createFunctionDefinition builds the LLVM IR implementation for this function.
// The function must be declared but not yet defined, otherwise this function
// will create a diagnostic.
func (b *builder) createFunctionDefinition() {
if b.DumpSSA() {
fmt.Printf("\nfunc %s:\n", b.fn.Function)
func (c *compilerContext) createPackage(pkg *ssa.Package, irbuilder llvm.Builder) {
memberNames := make([]string, 0)
for name := range pkg.Members {
memberNames = append(memberNames, name)
}
if !b.fn.LLVMFn.IsDeclaration() {
sort.Strings(memberNames)
for _, name := range memberNames {
switch member := pkg.Members[name].(type) {
case *ssa.Function:
llvmFn := c.getFunction(member)
if member.Blocks == nil {
continue // external function
}
c.createFunction(irbuilder, member, llvmFn)
case *ssa.Type:
if types.IsInterface(member.Type()) {
// Interfaces don't have concrete methods.
continue
}
// Named type. We should make sure all methods are created.
// This includes both functions with pointer receivers and those
// without.
methods := getAllMethods(pkg.Prog, member.Type())
methods = append(methods, getAllMethods(pkg.Prog, types.NewPointer(member.Type()))...)
for _, method := range methods {
// Parse this method.
fn := pkg.Prog.MethodValue(method)
if fn.Blocks == nil {
continue // external function
}
if member.Type().String() != member.String() {
// This is a member on a type alias. Do not build such a
// function.
continue
}
c.createFunction(irbuilder, fn, c.getFunction(fn))
}
case *ssa.Global:
// Make sure the global is present and has an initializer.
c.getGlobal(member)
case *ssa.NamedConst:
// TODO: create DWARF entries for these.
default:
panic("unknown member type: " + member.String())
}
}
}
// createFunction builds the LLVM IR implementation for this function. The
// function must not yet be defined, otherwise this function will create a
// diagnostic.
func (c *compilerContext) createFunction(irbuilder llvm.Builder, fn *ssa.Function, llvmFn llvm.Value) {
b := builder{
compilerContext: c,
Builder: irbuilder,
fn: fn,
llvmFn: llvmFn,
info: c.getFunctionInfo(fn),
locals: make(map[ssa.Value]llvm.Value),
dilocals: make(map[*types.Var]llvm.Metadata),
blockEntries: make(map[*ssa.BasicBlock]llvm.BasicBlock),
blockExits: make(map[*ssa.BasicBlock]llvm.BasicBlock),
}
if b.DumpSSA() {
fmt.Printf("\nfunc %s:\n", b.fn)
}
if !b.llvmFn.IsDeclaration() {
errValue := b.fn.Name() + " redeclared in this program"
fnPos := getPosition(b.fn.LLVMFn)
fnPos := getPosition(b.llvmFn)
if fnPos.IsValid() {
errValue += "\n\tprevious declaration at " + fnPos.String()
}
b.addError(b.fn.Pos(), errValue)
return
}
if !b.fn.IsExported() {
b.fn.LLVMFn.SetLinkage(llvm.InternalLinkage)
b.fn.LLVMFn.SetUnnamedAddr(true)
if !b.info.exported {
b.llvmFn.SetLinkage(llvm.InternalLinkage)
b.llvmFn.SetUnnamedAddr(true)
}
// Some functions have a pragma controlling the inlining level.
switch b.fn.Inline() {
case ir.InlineHint:
switch b.info.inline {
case inlineHint:
// Add LLVM inline hint to functions with //go:inline pragma.
inline := b.ctx.CreateEnumAttribute(llvm.AttributeKindID("inlinehint"), 0)
b.fn.LLVMFn.AddFunctionAttr(inline)
case ir.InlineNone:
b.llvmFn.AddFunctionAttr(inline)
case inlineNone:
// Add LLVM attribute to always avoid inlining this function.
noinline := b.ctx.CreateEnumAttribute(llvm.AttributeKindID("noinline"), 0)
b.fn.LLVMFn.AddFunctionAttr(noinline)
b.llvmFn.AddFunctionAttr(noinline)
}
// Add debug info, if needed.
@@ -866,18 +953,18 @@ func (b *builder) createFunctionDefinition() {
// Package initializers have no debug info. Create some fake debug
// info to at least have *something*.
filename := b.fn.Package().Pkg.Path() + "/<init>"
b.difunc = b.attachDebugInfoRaw(b.fn, b.fn.LLVMFn, "", filename, 0)
b.difunc = b.attachDebugInfoRaw(b.fn, b.llvmFn, "", filename, 0)
} else if b.fn.Syntax() != nil {
// Create debug info file if needed.
b.difunc = b.attachDebugInfo(b.fn)
}
pos := b.ir.Program.Fset.Position(b.fn.Pos())
pos := b.program.Fset.Position(b.fn.Pos())
b.SetCurrentDebugLocation(uint(pos.Line), uint(pos.Column), b.difunc, llvm.Metadata{})
}
// Pre-create all basic blocks in the function.
for _, block := range b.fn.DomPreorder() {
llvmBlock := b.ctx.AddBasicBlock(b.fn.LLVMFn, block.Comment)
llvmBlock := b.ctx.AddBasicBlock(b.llvmFn, block.Comment)
b.blockEntries[block] = llvmBlock
b.blockExits[block] = llvmBlock
}
@@ -890,7 +977,7 @@ func (b *builder) createFunctionDefinition() {
llvmType := b.getLLVMType(param.Type())
fields := make([]llvm.Value, 0, 1)
for _, info := range expandFormalParamType(llvmType, param.Name(), param.Type()) {
param := b.fn.LLVMFn.Param(llvmParamIndex)
param := b.llvmFn.Param(llvmParamIndex)
param.SetName(info.name)
fields = append(fields, param)
llvmParamIndex++
@@ -921,8 +1008,8 @@ func (b *builder) createFunctionDefinition() {
// Load free variables from the context. This is a closure (or bound
// method).
var context llvm.Value
if !b.fn.IsExported() {
parentHandle := b.fn.LLVMFn.LastParam()
if !b.info.exported {
parentHandle := b.llvmFn.LastParam()
parentHandle.SetName("parentHandle")
context = llvm.PrevParam(parentHandle)
context.SetName("context")
@@ -973,7 +1060,7 @@ func (b *builder) createFunctionDefinition() {
continue
}
dbgVar := b.getLocalVariable(variable)
pos := b.ir.Program.Fset.Position(instr.Pos())
pos := b.program.Fset.Position(instr.Pos())
b.dibuilder.InsertValueAtEnd(b.getValue(instr.X), dbgVar, b.dibuilder.CreateExpression(nil), llvm.DebugLoc{
Line: uint(pos.Line),
Col: uint(pos.Column),
@@ -1016,13 +1103,18 @@ func (b *builder) createFunctionDefinition() {
b.trackValue(phi.llvm)
}
}
// Compile all anonymous functions part of this function.
for _, fn := range b.fn.AnonFuncs {
b.createFunction(b.Builder, fn, b.getFunction(fn))
}
}
// createInstruction builds the LLVM IR equivalent instructions for the
// particular Go SSA instruction.
func (b *builder) createInstruction(instr ssa.Instruction) {
if b.Debug() {
pos := b.ir.Program.Fset.Position(instr.Pos())
pos := b.program.Fset.Position(instr.Pos())
b.SetCurrentDebugLocation(uint(pos.Line), uint(pos.Column), b.difunc, llvm.Metadata{})
}
@@ -1058,7 +1150,7 @@ func (b *builder) createInstruction(instr ssa.Instruction) {
if callee := instr.Call.StaticCallee(); callee != nil {
// Static callee is known. This makes it easier to start a new
// goroutine.
calleeFn := b.ir.GetFunction(callee)
calleeFn := b.getFunction(callee)
var context llvm.Value
switch value := instr.Call.Value.(type) {
case *ssa.Function:
@@ -1073,14 +1165,14 @@ func (b *builder) createInstruction(instr ssa.Instruction) {
panic("StaticCallee returned an unexpected value")
}
params = append(params, context) // context parameter
b.createGoInstruction(calleeFn.LLVMFn, params, "", callee.Pos())
b.createGoInstruction(calleeFn, params, "", callee.Pos())
} else if !instr.Call.IsInvoke() {
// This is a function pointer.
// At the moment, two extra params are passed to the newly started
// goroutine:
// * The function context, for closures.
// * The function pointer (for tasks).
funcPtr, context := b.decodeFuncValue(b.getValue(instr.Call.Value), instr.Call.Value.Type().Underlying().(*types.Signature))
funcPtr, context := b.decodeFuncValue(b.getValue(instr.Call.Value), instr.Call.Value.Type().(*types.Signature))
params = append(params, context) // context parameter
switch b.Scheduler() {
case "none", "coroutines":
@@ -1121,7 +1213,7 @@ func (b *builder) createInstruction(instr ssa.Instruction) {
b.CreateRet(b.getValue(instr.Results[0]))
} else {
// Multiple return values. Put them all in a struct.
retVal := llvm.ConstNull(b.fn.LLVMFn.Type().ElementType().ReturnType())
retVal := llvm.ConstNull(b.llvmFn.Type().ElementType().ReturnType())
for i, result := range instr.Results {
val := b.getValue(result)
retVal = b.CreateInsertValue(retVal, val, i, "")
@@ -1177,7 +1269,9 @@ func (b *builder) createBuiltin(args []ssa.Value, callName string, pos token.Pos
var llvmCap llvm.Value
switch args[0].Type().(type) {
case *types.Chan:
llvmCap = b.createRuntimeCall("chanCap", []llvm.Value{value}, "cap")
// Channel. Buffered channels haven't been implemented yet so always
// return 0.
llvmCap = llvm.ConstInt(b.intType, 0, false)
case *types.Slice:
llvmCap = b.CreateExtractValue(value, 2, "cap")
default:
@@ -1233,7 +1327,9 @@ func (b *builder) createBuiltin(args []ssa.Value, callName string, pos token.Pos
// string or slice
llvmLen = b.CreateExtractValue(value, 1, "len")
case *types.Chan:
llvmLen = b.createRuntimeCall("chanLen", []llvm.Value{value}, "len")
// Channel. Buffered channels haven't been implemented yet so always
// return 0.
llvmLen = llvm.ConstInt(b.intType, 0, false)
case *types.Map:
llvmLen = b.createRuntimeCall("hashmapLen", []llvm.Value{value}, "len")
default:
@@ -1336,35 +1432,36 @@ func (b *builder) createFunctionCall(instr *ssa.CallCommon) (llvm.Value, error)
return b.createMemoryCopyCall(fn, instr.Args)
case name == "runtime.memzero":
return b.createMemoryZeroCall(instr.Args)
case name == "device.Asm" || name == "device/arm.Asm" || name == "device/avr.Asm" || name == "device/riscv.Asm":
case name == "device/arm.Asm" || name == "device/avr.Asm" || name == "device/riscv.Asm":
return b.createInlineAsm(instr.Args)
case name == "device.AsmFull" || name == "device/arm.AsmFull" || name == "device/avr.AsmFull" || name == "device/riscv.AsmFull":
case name == "device/arm.AsmFull" || name == "device/avr.AsmFull" || name == "device/riscv.AsmFull":
return b.createInlineAsmFull(instr)
case strings.HasPrefix(name, "device/arm.SVCall"):
return b.emitSVCall(instr.Args)
case strings.HasPrefix(name, "(device/riscv.CSR)."):
return b.emitCSROperation(instr)
// The device/riscv.CSR operations must happen on constants.
// However, the compiler also creates pointer receivers for this
// operation which do not provide constant parameters. Therefore, do
// not create the regular inline asm for such functions but leave
// the call to an undefined function instead.
recv := b.fn.Signature.Recv()
if recv == nil || recv.Type().String() != "*device/riscv.CSR" {
return b.emitCSROperation(instr)
}
case strings.HasPrefix(name, "syscall.Syscall"):
return b.createSyscall(instr)
case strings.HasPrefix(name, "runtime/volatile.Load"):
return b.createVolatileLoad(instr)
case strings.HasPrefix(name, "runtime/volatile.Store"):
return b.createVolatileStore(instr)
case strings.HasPrefix(name, "sync/atomic."):
val, ok := b.createAtomicOp(instr)
if ok {
// This call could be lowered as an atomic operation.
return val, nil
}
// This call couldn't be lowered as an atomic operation, it's
// probably something else. Continue as usual.
case name == "runtime/interrupt.New":
return b.createInterruptGlobal(instr)
}
targetFunc := b.ir.GetFunction(fn)
if targetFunc.LLVMFn.IsNil() {
return llvm.Value{}, b.makeError(instr.Pos(), "undefined function: "+targetFunc.LinkName())
callee = b.getFunction(fn)
info := b.getFunctionInfo(fn)
if callee.IsNil() {
return llvm.Value{}, b.makeError(instr.Pos(), "undefined function: "+info.linkName)
}
switch value := instr.Value.(type) {
case *ssa.Function:
@@ -1378,8 +1475,7 @@ func (b *builder) createFunctionCall(instr *ssa.CallCommon) (llvm.Value, error)
default:
panic("StaticCallee returned an unexpected value")
}
callee = targetFunc.LLVMFn
exported = targetFunc.IsExported()
exported = info.exported
} else if call, ok := instr.Value.(*ssa.Builtin); ok {
// Builtin function (append, close, delete, etc.).)
return b.createBuiltin(instr.Args, call.Name(), instr.Pos())
@@ -1414,14 +1510,15 @@ func (b *builder) createFunctionCall(instr *ssa.CallCommon) (llvm.Value, error)
func (b *builder) getValue(expr ssa.Value) llvm.Value {
switch expr := expr.(type) {
case *ssa.Const:
return b.createConst(b.fn.LinkName(), expr)
return b.createConst(b.info.linkName, expr)
case *ssa.Function:
fn := b.ir.GetFunction(expr)
if fn.IsExported() {
info := b.getFunctionInfo(expr)
if info.exported {
b.addError(expr.Pos(), "cannot use an exported function as value: "+expr.String())
return llvm.Undef(b.getLLVMType(expr.Type()))
}
return b.createFuncValue(fn.LLVMFn, llvm.Undef(b.i8ptrType), fn.Signature)
llvmFn := b.getFunction(expr)
return b.createFuncValue(llvmFn, llvm.Undef(b.i8ptrType), expr.Signature)
case *ssa.Global:
value := b.getGlobal(expr)
if value.IsNil() {
@@ -1551,7 +1648,7 @@ func (b *builder) createExpr(expr ssa.Value) (llvm.Value, error) {
index := b.getValue(expr.Index)
// Check bounds.
arrayLen := expr.X.Type().Underlying().(*types.Array).Len()
arrayLen := expr.X.Type().(*types.Array).Len()
arrayLenLLVM := llvm.ConstInt(b.uintptrType, uint64(arrayLen), false)
b.createLookupBoundsCheck(arrayLenLLVM, index, expr.Index.Type())
@@ -1663,8 +1760,8 @@ func (b *builder) createExpr(expr ssa.Value) (llvm.Value, error) {
}
// Bounds checking.
lenType := expr.Len.Type().Underlying().(*types.Basic)
capType := expr.Cap.Type().Underlying().(*types.Basic)
lenType := expr.Len.Type().(*types.Basic)
capType := expr.Cap.Type().(*types.Basic)
b.createSliceBoundsCheck(maxSize, sliceLen, sliceCap, sliceCap, lenType, capType, capType)
// Allocate the backing array.
@@ -2545,7 +2642,7 @@ func (b *builder) createConvert(typeFrom, typeTo types.Type, value llvm.Value, p
return llvm.Value{}, b.makeError(pos, "todo: convert: basic non-integer type: "+typeFrom.String()+" -> "+typeTo.String())
case *types.Slice:
if basic, ok := typeFrom.Underlying().(*types.Basic); !ok || basic.Info()&types.IsString == 0 {
if basic, ok := typeFrom.(*types.Basic); !ok || basic.Info()&types.IsString == 0 {
panic("can only convert from a string to a slice")
}
+138
View File
@@ -0,0 +1,138 @@
package compiler
import (
"bytes"
"flag"
"fmt"
"go/ast"
"go/parser"
"go/token"
"go/types"
"io/ioutil"
"path/filepath"
"strings"
"sync"
"testing"
"github.com/tinygo-org/tinygo/compileopts"
"github.com/tinygo-org/tinygo/compiler/ircheck"
"golang.org/x/tools/go/ssa"
"golang.org/x/tools/go/ssa/ssautil"
"tinygo.org/x/go-llvm"
)
var flagUpdate = flag.Bool("update", false, "update all tests")
func TestCompiler(t *testing.T) {
t.Parallel()
for _, name := range []string{"basic"} {
t.Run(name, func(t *testing.T) {
runCompilerTest(t, name)
})
}
}
func runCompilerTest(t *testing.T, name string) {
// 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)
}
files := []*ast.File{f}
// Create Go SSA from the AST.
var typecheckErrors []error
var typecheckErrorsLock sync.Mutex
typesConfig := types.Config{
Error: func(err error) {
typecheckErrorsLock.Lock()
defer typecheckErrorsLock.Unlock()
typecheckErrors = append(typecheckErrors, err)
},
Importer: simpleImporter{},
Sizes: types.SizesFor("gccgo", "arm"),
}
pkg, _, err := ssautil.BuildPackage(&typesConfig, fset, types.NewPackage("main", ""), files, ssa.SanityCheckFunctions|ssa.BareInits|ssa.GlobalDebug)
for _, err := range typecheckErrors {
t.Error(err)
}
if err != nil && len(typecheckErrors) == 0 {
// Only report errors when no type errors are found (an
// unexpected condition).
t.Error(err)
}
if t.Failed() {
return
}
// Configure the compiler.
config := compileopts.Config{
Options: &compileopts.Options{},
Target: &compileopts.TargetSpec{
Triple: "armv7m-none-eabi",
BuildTags: []string{"cortexm", "baremetal", "linux", "arm"},
Scheduler: "tasks",
},
}
machine, err := NewTargetMachine(&config)
if err != nil {
t.Fatal(err)
}
c := newCompilerContext("main", machine, &config)
c.runtimePkg = types.NewPackage("runtime", "runtime")
c.taskPkg = types.NewPackage("internal/task", "task")
irbuilder := c.ctx.NewBuilder()
defer irbuilder.Dispose()
// Create LLVM IR from the Go SSA.
c.createPackage(pkg, irbuilder)
// Check the IR with the LLVM verifier.
if err := llvm.VerifyModule(c.mod, llvm.PrintMessageAction); err != nil {
t.Error("verification error after IR construction")
}
// Check the IR with our own verifier (which checks for different things).
errs := ircheck.Module(c.mod)
for _, err := range errs {
t.Error(err)
}
// Check whether the IR matches the expected IR.
ir := c.mod.String()
ir = ir[strings.Index(ir, "\ntarget datalayout = ")+1:]
outfile := filepath.Join("testdata", name+".ll")
if *flagUpdate {
err := ioutil.WriteFile(outfile, []byte(ir), 0666)
if err != nil {
t.Error("could not read output file:", err)
}
} else {
ir2, err := ioutil.ReadFile(outfile)
if err != nil {
t.Fatal("could not read input file:", err)
}
ir2 = bytes.Replace(ir2, []byte("\r\n"), []byte("\n"), -1)
if ir != string(ir2) {
t.Error("output did not match")
}
}
}
// 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)
}
}
+30 -127
View File
@@ -15,8 +15,6 @@ package compiler
import (
"github.com/tinygo-org/tinygo/compiler/llvmutil"
"github.com/tinygo-org/tinygo/ir"
"go/types"
"golang.org/x/tools/go/ssa"
"tinygo.org/x/go-llvm"
)
@@ -26,11 +24,9 @@ import (
// calls.
func (b *builder) deferInitFunc() {
// Some setup.
b.deferFuncs = make(map[*ir.Function]int)
b.deferFuncs = make(map[*ssa.Function]int)
b.deferInvokeFuncs = make(map[string]int)
b.deferClosureFuncs = make(map[*ir.Function]int)
b.deferExprFuncs = make(map[ssa.Value]int)
b.deferBuiltinFuncs = make(map[ssa.Value]deferBuiltin)
b.deferClosureFuncs = make(map[*ssa.Function]int)
// Create defer list pointer.
deferType := llvm.PointerType(b.getLLVMRuntimeType("_defer"), 0)
@@ -107,13 +103,12 @@ func (b *builder) createDefer(instr *ssa.Defer) {
} else if callee, ok := instr.Call.Value.(*ssa.Function); ok {
// Regular function call.
fn := b.ir.GetFunction(callee)
if _, ok := b.deferFuncs[fn]; !ok {
b.deferFuncs[fn] = len(b.allDeferFuncs)
b.allDeferFuncs = append(b.allDeferFuncs, fn)
if _, ok := b.deferFuncs[callee]; !ok {
b.deferFuncs[callee] = len(b.allDeferFuncs)
b.allDeferFuncs = append(b.allDeferFuncs, callee)
}
callback := llvm.ConstInt(b.uintptrType, uint64(b.deferFuncs[fn]), false)
callback := llvm.ConstInt(b.uintptrType, uint64(b.deferFuncs[callee]), false)
// Collect all values to be put in the struct (starting with
// runtime._defer fields).
@@ -135,7 +130,7 @@ func (b *builder) createDefer(instr *ssa.Defer) {
context := b.CreateExtractValue(closure, 0, "")
// Get the callback number.
fn := b.ir.GetFunction(makeClosure.Fn.(*ssa.Function))
fn := makeClosure.Fn.(*ssa.Function)
if _, ok := b.deferClosureFuncs[fn]; !ok {
b.deferClosureFuncs[fn] = len(b.allDeferFuncs)
b.allDeferFuncs = append(b.allDeferFuncs, makeClosure)
@@ -154,54 +149,9 @@ func (b *builder) createDefer(instr *ssa.Defer) {
values = append(values, context)
valueTypes = append(valueTypes, context.Type())
} else if builtin, ok := instr.Call.Value.(*ssa.Builtin); ok {
var funcName string
switch builtin.Name() {
case "close":
funcName = "chanClose"
default:
b.addError(instr.Pos(), "todo: Implement defer for "+builtin.Name())
return
}
if _, ok := b.deferBuiltinFuncs[instr.Call.Value]; !ok {
b.deferBuiltinFuncs[instr.Call.Value] = deferBuiltin{
funcName,
len(b.allDeferFuncs),
}
b.allDeferFuncs = append(b.allDeferFuncs, instr.Call.Value)
}
callback := llvm.ConstInt(b.uintptrType, uint64(b.deferBuiltinFuncs[instr.Call.Value].callback), false)
// Collect all values to be put in the struct (starting with
// runtime._defer fields).
values = []llvm.Value{callback, next}
for _, param := range instr.Call.Args {
llvmParam := b.getValue(param)
values = append(values, llvmParam)
valueTypes = append(valueTypes, llvmParam.Type())
}
} else {
funcValue := b.getValue(instr.Call.Value)
if _, ok := b.deferExprFuncs[instr.Call.Value]; !ok {
b.deferExprFuncs[instr.Call.Value] = len(b.allDeferFuncs)
b.allDeferFuncs = append(b.allDeferFuncs, &instr.Call)
}
callback := llvm.ConstInt(b.uintptrType, uint64(b.deferExprFuncs[instr.Call.Value]), false)
// Collect all values to be put in the struct (starting with
// runtime._defer fields, followed by all parameters including the
// context pointer).
values = []llvm.Value{callback, next, funcValue}
valueTypes = append(valueTypes, funcValue.Type())
for _, param := range instr.Call.Args {
llvmParam := b.getValue(param)
values = append(values, llvmParam)
valueTypes = append(valueTypes, llvmParam.Type())
}
b.addError(instr.Pos(), "todo: defer on uncommon function call type")
return
}
// Make a struct out of the collected values to put in the defer frame.
@@ -251,10 +201,10 @@ func (b *builder) createRunDefers() {
// }
// Create loop.
loophead := b.ctx.AddBasicBlock(b.fn.LLVMFn, "rundefers.loophead")
loop := b.ctx.AddBasicBlock(b.fn.LLVMFn, "rundefers.loop")
unreachable := b.ctx.AddBasicBlock(b.fn.LLVMFn, "rundefers.default")
end := b.ctx.AddBasicBlock(b.fn.LLVMFn, "rundefers.end")
loophead := b.ctx.AddBasicBlock(b.llvmFn, "rundefers.loophead")
loop := b.ctx.AddBasicBlock(b.llvmFn, "rundefers.loop")
unreachable := b.ctx.AddBasicBlock(b.llvmFn, "rundefers.default")
end := b.ctx.AddBasicBlock(b.llvmFn, "rundefers.end")
b.CreateBr(loophead)
// Create loop head:
@@ -286,28 +236,21 @@ func (b *builder) createRunDefers() {
// Create switch case, for example:
// case 0:
// // run first deferred call
block := b.ctx.AddBasicBlock(b.fn.LLVMFn, "rundefers.callback")
block := b.ctx.AddBasicBlock(b.llvmFn, "rundefers.callback")
sw.AddCase(llvm.ConstInt(b.uintptrType, uint64(i), false), block)
b.SetInsertPointAtEnd(block)
switch callback := callback.(type) {
case *ssa.CallCommon:
// Call on an value or interface value.
// Get the real defer struct type and cast to it.
valueTypes := []llvm.Type{b.uintptrType, llvm.PointerType(b.getLLVMRuntimeType("_defer"), 0)}
// Call on an interface value.
if !callback.IsInvoke() {
//Expect funcValue to be passed through the defer frame.
valueTypes = append(valueTypes, b.getFuncType(callback.Signature()))
} else {
//Expect typecode
valueTypes = append(valueTypes, b.uintptrType, b.i8ptrType)
panic("expected an invoke call, not a direct call")
}
// Get the real defer struct type and cast to it.
valueTypes := []llvm.Type{b.uintptrType, llvm.PointerType(b.getLLVMRuntimeType("_defer"), 0), b.uintptrType, b.i8ptrType}
for _, arg := range callback.Args {
valueTypes = append(valueTypes, b.getLLVMType(arg.Type()))
}
deferFrameType := b.ctx.StructType(valueTypes, false)
deferFramePtr := b.CreateBitCast(deferData, llvm.PointerType(deferFrameType, 0), "deferFrame")
@@ -320,37 +263,21 @@ func (b *builder) createRunDefers() {
forwardParams = append(forwardParams, forwardParam)
}
var fnPtr llvm.Value
// Isolate the typecode.
typecode, forwardParams := forwardParams[0], forwardParams[1:]
if !callback.IsInvoke() {
// Isolate the func value.
funcValue := forwardParams[0]
forwardParams = forwardParams[1:]
//Get function pointer and context
fp, context := b.decodeFuncValue(funcValue, callback.Signature())
fnPtr = fp
//Pass context
forwardParams = append(forwardParams, context)
} else {
// Isolate the typecode.
typecode := forwardParams[0]
forwardParams = forwardParams[1:]
fnPtr = b.getInvokePtr(callback, typecode)
// Add the context parameter. An interface call cannot also be a
// closure but we have to supply the parameter anyway for platforms
// with a strict calling convention.
forwardParams = append(forwardParams, llvm.Undef(b.i8ptrType))
}
// Add the context parameter. An interface call cannot also be a
// closure but we have to supply the parameter anyway for platforms
// with a strict calling convention.
forwardParams = append(forwardParams, llvm.Undef(b.i8ptrType))
// Parent coroutine handle.
forwardParams = append(forwardParams, llvm.Undef(b.i8ptrType))
fnPtr := b.getInvokePtr(callback, typecode)
b.createCall(fnPtr, forwardParams, "")
case *ir.Function:
case *ssa.Function:
// Direct call.
// Get the real defer struct type and cast to it.
@@ -372,7 +299,7 @@ func (b *builder) createRunDefers() {
// Plain TinyGo functions add some extra parameters to implement async functionality and function recievers.
// These parameters should not be supplied when calling into an external C/ASM function.
if !callback.IsExported() {
if !b.getFunctionInfo(callback).exported {
// Add the context parameter. We know it is ignored by the receiving
// function, but we have to pass one anyway.
forwardParams = append(forwardParams, llvm.Undef(b.i8ptrType))
@@ -382,11 +309,11 @@ func (b *builder) createRunDefers() {
}
// Call real function.
b.createCall(callback.LLVMFn, forwardParams, "")
b.createCall(b.getFunction(callback), forwardParams, "")
case *ssa.MakeClosure:
// Get the real defer struct type and cast to it.
fn := b.ir.GetFunction(callback.Fn.(*ssa.Function))
fn := callback.Fn.(*ssa.Function)
valueTypes := []llvm.Type{b.uintptrType, llvm.PointerType(b.getLLVMRuntimeType("_defer"), 0)}
params := fn.Signature.Params()
for i := 0; i < params.Len(); i++ {
@@ -409,32 +336,8 @@ func (b *builder) createRunDefers() {
forwardParams = append(forwardParams, llvm.Undef(b.i8ptrType))
// Call deferred function.
b.createCall(fn.LLVMFn, forwardParams, "")
case *ssa.Builtin:
db := b.deferBuiltinFuncs[callback]
b.createCall(b.getFunction(fn), forwardParams, "")
//Get parameter types
valueTypes := []llvm.Type{b.uintptrType, llvm.PointerType(b.getLLVMRuntimeType("_defer"), 0)}
//Get signature from call results
params := callback.Type().Underlying().(*types.Signature).Params()
for i := 0; i < params.Len(); i++ {
valueTypes = append(valueTypes, b.getLLVMType(params.At(i).Type()))
}
deferFrameType := b.ctx.StructType(valueTypes, false)
deferFramePtr := b.CreateBitCast(deferData, llvm.PointerType(deferFrameType, 0), "deferFrame")
// Extract the params from the struct.
var forwardParams []llvm.Value
zero := llvm.ConstInt(b.ctx.Int32Type(), 0, false)
for i := 0; i < params.Len(); i++ {
gep := b.CreateInBoundsGEP(deferFramePtr, []llvm.Value{zero, llvm.ConstInt(b.ctx.Int32Type(), uint64(i+2), false)}, "gep")
forwardParam := b.CreateLoad(gep, "param")
forwardParams = append(forwardParams, forwardParam)
}
b.createRuntimeCall(db.funcName, forwardParams, "")
default:
panic("unknown deferred function type")
}
+1 -1
View File
@@ -14,7 +14,7 @@ import (
// makeError makes it easy to create an error from a token.Pos with a message.
func (c *compilerContext) makeError(pos token.Pos, msg string) types.Error {
return types.Error{
Fset: c.ir.Program.Fset,
Fset: c.program.Fset,
Pos: pos,
Msg: msg,
}
+12 -2
View File
@@ -5,6 +5,7 @@ package compiler
import (
"go/types"
"strings"
"github.com/tinygo-org/tinygo/compileopts"
"golang.org/x/tools/go/ssa"
@@ -149,7 +150,16 @@ func (b *builder) parseMakeClosure(expr *ssa.MakeClosure) (llvm.Value, error) {
if len(expr.Bindings) == 0 {
panic("unexpected: MakeClosure without bound variables")
}
f := b.ir.GetFunction(expr.Fn.(*ssa.Function))
f := expr.Fn.(*ssa.Function)
llvmFn := b.getFunction(f)
if strings.HasSuffix(f.Name(), "$bound") && llvmFn.IsDeclaration() {
// Hack: the ssa package does not expose bound methods so make sure
// they're built here when necessary.
irbuilder := b.ctx.NewBuilder()
defer irbuilder.Dispose()
b.createFunction(irbuilder, f, llvmFn)
}
// Collect all bound variables.
boundVars := make([]llvm.Value, len(expr.Bindings))
@@ -164,5 +174,5 @@ func (b *builder) parseMakeClosure(expr *ssa.MakeClosure) (llvm.Value, error) {
context := b.emitPointerPack(boundVars)
// Create the closure.
return b.createFuncValue(f.LLVMFn, context, f.Signature), nil
return b.createFuncValue(llvmFn, context, f.Signature), nil
}
+7 -21
View File
@@ -7,6 +7,7 @@ import (
"go/token"
"github.com/tinygo-org/tinygo/compiler/llvmutil"
"golang.org/x/tools/go/ssa"
"tinygo.org/x/go-llvm"
)
@@ -19,30 +20,17 @@ import (
// Because a go statement doesn't return anything, return undef.
func (b *builder) createGoInstruction(funcPtr llvm.Value, params []llvm.Value, prefix string, pos token.Pos) llvm.Value {
paramBundle := b.emitPointerPack(params)
var callee, stackSize llvm.Value
var callee llvm.Value
switch b.Scheduler() {
case "none", "tasks":
callee = b.createGoroutineStartWrapper(funcPtr, prefix, pos)
if b.AutomaticStackSize() {
// The stack size is not known until after linking. Call a dummy
// function that will be replaced with a load from a special ELF
// section that contains the stack size (and is modified after
// linking).
stackSize = b.createCall(b.mod.NamedFunction("internal/task.getGoroutineStackSize"), []llvm.Value{callee, llvm.Undef(b.i8ptrType), llvm.Undef(b.i8ptrType)}, "stacksize")
} else {
// The stack size is fixed at compile time. By emitting it here as a
// constant, it can be optimized.
stackSize = llvm.ConstInt(b.uintptrType, b.Target.DefaultStackSize, false)
}
case "coroutines":
callee = b.CreatePtrToInt(funcPtr, b.uintptrType, "")
// There is no goroutine stack size: coroutines are used instead of
// stacks.
stackSize = llvm.Undef(b.uintptrType)
default:
panic("unreachable")
}
b.createCall(b.mod.NamedFunction("internal/task.start"), []llvm.Value{callee, paramBundle, stackSize, llvm.Undef(b.i8ptrType), llvm.ConstPointerNull(b.i8ptrType)}, "")
start := b.getFunction(b.program.ImportedPackage("internal/task").Members["start"].(*ssa.Function))
b.createCall(start, []llvm.Value{callee, paramBundle, llvm.Undef(b.i8ptrType), llvm.ConstPointerNull(b.i8ptrType)}, "")
return llvm.Undef(funcPtr.Type().ElementType().ReturnType())
}
@@ -81,14 +69,13 @@ func (c *compilerContext) createGoroutineStartWrapper(fn llvm.Value, prefix stri
// Create the wrapper.
wrapperType := llvm.FunctionType(c.ctx.VoidType(), []llvm.Type{c.i8ptrType}, false)
wrapper = llvm.AddFunction(c.mod, name+"$gowrapper", wrapperType)
wrapper.SetLinkage(llvm.InternalLinkage)
wrapper.SetLinkage(llvm.PrivateLinkage)
wrapper.SetUnnamedAddr(true)
wrapper.AddAttributeAtIndex(-1, c.ctx.CreateStringAttribute("tinygo-gowrapper", name))
entry := c.ctx.AddBasicBlock(wrapper, "entry")
builder.SetInsertPointAtEnd(entry)
if c.Debug() {
pos := c.ir.Program.Fset.Position(pos)
pos := c.program.Fset.Position(pos)
diFuncType := c.dibuilder.CreateSubroutineType(llvm.DISubroutineType{
File: c.getDIFile(pos.Filename),
Parameters: nil, // do not show parameters in debugger
@@ -140,12 +127,11 @@ func (c *compilerContext) createGoroutineStartWrapper(fn llvm.Value, prefix stri
wrapper = llvm.AddFunction(c.mod, prefix+".gowrapper", wrapperType)
wrapper.SetLinkage(llvm.InternalLinkage)
wrapper.SetUnnamedAddr(true)
wrapper.AddAttributeAtIndex(-1, c.ctx.CreateStringAttribute("tinygo-gowrapper", ""))
entry := c.ctx.AddBasicBlock(wrapper, "entry")
builder.SetInsertPointAtEnd(entry)
if c.Debug() {
pos := c.ir.Program.Fset.Position(pos)
pos := c.program.Fset.Position(pos)
diFuncType := c.dibuilder.CreateSubroutineType(llvm.DISubroutineType{
File: c.getDIFile(pos.Filename),
Parameters: nil, // do not show parameters in debugger
+87 -21
View File
@@ -11,7 +11,6 @@ import (
"strconv"
"strings"
"github.com/tinygo-org/tinygo/ir"
"golang.org/x/tools/go/ssa"
"tinygo.org/x/go-llvm"
)
@@ -236,7 +235,7 @@ func (c *compilerContext) getTypeMethodSet(typ types.Type) llvm.Value {
return llvm.ConstGEP(global, []llvm.Value{zero, zero})
}
ms := c.ir.Program.MethodSets.MethodSet(typ)
ms := c.program.MethodSets.MethodSet(typ)
if ms.Len() == 0 {
// no methods, so can leave that one out
return llvm.ConstPointerNull(llvm.PointerType(c.getLLVMRuntimeType("interfaceMethodInfo"), 0))
@@ -247,15 +246,23 @@ func (c *compilerContext) getTypeMethodSet(typ types.Type) llvm.Value {
for i := 0; i < ms.Len(); i++ {
method := ms.At(i)
signatureGlobal := c.getMethodSignature(method.Obj().(*types.Func))
f := c.ir.GetFunction(c.ir.Program.MethodValue(method))
if f.LLVMFn.IsNil() {
fn := c.program.MethodValue(method)
llvmFn := c.getFunction(fn)
if llvmFn.IsNil() {
// compiler error, so panic
panic("cannot find function: " + f.LinkName())
panic("cannot find function: " + c.getFunctionInfo(fn).linkName)
}
fn := c.getInterfaceInvokeWrapper(f)
if isAnonymous(typ) && llvmFn.IsDeclaration() {
// Inline types may also have methods when they embed interface
// types with methods. Example: struct{ error }
irbuilder := c.ctx.NewBuilder()
defer irbuilder.Dispose()
c.createFunction(irbuilder, fn, llvmFn)
}
wrapper := c.getInterfaceInvokeWrapper(fn, llvmFn)
methodInfo := llvm.ConstNamedStruct(interfaceMethodInfoType, []llvm.Value{
signatureGlobal,
llvm.ConstPtrToInt(fn, c.uintptrType),
llvm.ConstPtrToInt(wrapper, c.uintptrType),
})
methods[i] = methodInfo
}
@@ -303,7 +310,7 @@ func (c *compilerContext) getInterfaceMethodSet(typ types.Type) llvm.Value {
// external *i8 indicating the indicating the signature of this method. It is
// used during the interface lowering pass.
func (c *compilerContext) getMethodSignature(method *types.Func) llvm.Value {
signature := ir.MethodSignature(method)
signature := methodSignature(method)
signatureGlobal := c.mod.NamedGlobal("func " + signature)
if signatureGlobal.IsNil() {
signatureGlobal = llvm.AddGlobal(c.mod, c.ctx.Int8Type(), "func "+signature)
@@ -357,8 +364,8 @@ func (b *builder) createTypeAssert(expr *ssa.TypeAssert) llvm.Value {
// value.
prevBlock := b.GetInsertBlock()
okBlock := b.ctx.AddBasicBlock(b.fn.LLVMFn, "typeassert.ok")
nextBlock := b.ctx.AddBasicBlock(b.fn.LLVMFn, "typeassert.next")
okBlock := b.ctx.AddBasicBlock(b.llvmFn, "typeassert.ok")
nextBlock := b.ctx.AddBasicBlock(b.llvmFn, "typeassert.next")
b.blockExits[b.currentBlock] = nextBlock // adjust outgoing block for phi nodes
b.CreateCondBr(commaOk, okBlock, nextBlock)
@@ -436,8 +443,8 @@ func (b *builder) getInvokeCall(instr *ssa.CallCommon) (llvm.Value, []llvm.Value
// value, dereferences or unpacks it if necessary, and calls the real method.
// If the method to wrap has a pointer receiver, no wrapping is necessary and
// the function is returned directly.
func (c *compilerContext) getInterfaceInvokeWrapper(f *ir.Function) llvm.Value {
wrapperName := f.LinkName() + "$invoke"
func (c *compilerContext) getInterfaceInvokeWrapper(fn *ssa.Function, llvmFn llvm.Value) llvm.Value {
wrapperName := llvmFn.Name() + "$invoke"
wrapper := c.mod.NamedFunction(wrapperName)
if !wrapper.IsNil() {
// Wrapper already created. Return it directly.
@@ -445,7 +452,7 @@ func (c *compilerContext) getInterfaceInvokeWrapper(f *ir.Function) llvm.Value {
}
// Get the expanded receiver type.
receiverType := c.getLLVMType(f.Params[0].Type())
receiverType := c.getLLVMType(fn.Params[0].Type())
var expandedReceiverType []llvm.Type
for _, info := range expandFormalParamType(receiverType, "", nil) {
expandedReceiverType = append(expandedReceiverType, info.llvmType)
@@ -457,15 +464,15 @@ func (c *compilerContext) getInterfaceInvokeWrapper(f *ir.Function) llvm.Value {
// Casting a function signature to a different signature and calling it
// with a receiver pointer bitcasted to *i8 (as done in calls on an
// interface) is hopefully a safe (defined) operation.
return f.LLVMFn
return llvmFn
}
// create wrapper function
fnType := f.LLVMFn.Type().ElementType()
fnType := llvmFn.Type().ElementType()
paramTypes := append([]llvm.Type{c.i8ptrType}, fnType.ParamTypes()[len(expandedReceiverType):]...)
wrapFnType := llvm.FunctionType(fnType.ReturnType(), paramTypes, false)
wrapper = llvm.AddFunction(c.mod, wrapperName, wrapFnType)
if f.LLVMFn.LastParam().Name() == "parentHandle" {
if llvmFn.LastParam().Name() == "parentHandle" {
wrapper.LastParam().SetName("parentHandle")
}
@@ -481,8 +488,8 @@ func (c *compilerContext) getInterfaceInvokeWrapper(f *ir.Function) llvm.Value {
// add debug info if needed
if c.Debug() {
pos := c.ir.Program.Fset.Position(f.Pos())
difunc := c.attachDebugInfoRaw(f, wrapper, "$invoke", pos.Filename, pos.Line)
pos := c.program.Fset.Position(fn.Pos())
difunc := c.attachDebugInfoRaw(fn, wrapper, "$invoke", pos.Filename, pos.Line)
b.SetCurrentDebugLocation(uint(pos.Line), uint(pos.Column), difunc, llvm.Metadata{})
}
@@ -492,13 +499,72 @@ func (c *compilerContext) getInterfaceInvokeWrapper(f *ir.Function) llvm.Value {
receiverValue := b.emitPointerUnpack(wrapper.Param(0), []llvm.Type{receiverType})[0]
params := append(b.expandFormalParam(receiverValue), wrapper.Params()[1:]...)
if f.LLVMFn.Type().ElementType().ReturnType().TypeKind() == llvm.VoidTypeKind {
b.CreateCall(f.LLVMFn, params, "")
if llvmFn.Type().ElementType().ReturnType().TypeKind() == llvm.VoidTypeKind {
b.CreateCall(llvmFn, params, "")
b.CreateRetVoid()
} else {
ret := b.CreateCall(f.LLVMFn, params, "ret")
ret := b.CreateCall(llvmFn, params, "ret")
b.CreateRet(ret)
}
return wrapper
}
// isAnonymous returns true if (and only if) this is an anonymous type: one that
// is created inline. It can have methods if it embeds a type with methods.
func isAnonymous(typ types.Type) bool {
if t, ok := typ.(*types.Pointer); ok {
typ = t.Elem()
}
if _, ok := typ.(*types.Named); !ok {
return true
}
return false
}
// methodSignature creates a readable version of a method signature (including
// the function name, excluding the receiver name). This string is used
// internally to match interfaces and to call the correct method on an
// interface. Examples:
//
// String() string
// Read([]byte) (int, error)
func methodSignature(method *types.Func) string {
return method.Name() + signature(method.Type().(*types.Signature))
}
// Make a readable version of a function (pointer) signature.
// Examples:
//
// () string
// (string, int) (int, error)
func signature(sig *types.Signature) string {
s := ""
if sig.Params().Len() == 0 {
s += "()"
} else {
s += "("
for i := 0; i < sig.Params().Len(); i++ {
if i > 0 {
s += ", "
}
s += sig.Params().At(i).Type().String()
}
s += ")"
}
if sig.Results().Len() == 0 {
// keep as-is
} else if sig.Results().Len() == 1 {
s += " " + sig.Results().At(0).Type().String()
} else {
s += " ("
for i := 0; i < sig.Results().Len(); i++ {
if i > 0 {
s += ", "
}
s += sig.Results().At(i).Type().String()
}
s += ")"
}
return s
}
+2 -2
View File
@@ -39,7 +39,7 @@ func (b *builder) createInterruptGlobal(instr *ssa.CallCommon) (llvm.Value, erro
// Create a new global of type runtime/interrupt.handle. Globals of this
// type are lowered in the interrupt lowering pass.
globalType := b.ir.Program.ImportedPackage("runtime/interrupt").Type("handle").Type()
globalType := b.program.ImportedPackage("runtime/interrupt").Type("handle").Type()
globalLLVMType := b.getLLVMType(globalType)
globalName := "runtime/interrupt.$interrupt" + strconv.FormatInt(id.Int64(), 10)
if global := b.mod.NamedGlobal(globalName); !global.IsNil() {
@@ -56,7 +56,7 @@ func (b *builder) createInterruptGlobal(instr *ssa.CallCommon) (llvm.Value, erro
// Add debug info to the interrupt global.
if b.Debug() {
pos := b.ir.Program.Fset.Position(instr.Pos())
pos := b.program.Fset.Position(instr.Pos())
diglobal := b.dibuilder.CreateGlobalVariableExpression(b.getDIFile(pos.Filename), llvm.DIGlobalVariableExpression{
Name: "interrupt" + strconv.FormatInt(id.Int64(), 10),
LinkageName: globalName,
+344
View File
@@ -0,0 +1,344 @@
// +build none
// This file generates runtimetypes.go from the AST of the TinyGo runtime
// package. This type information is necessary to avoid having to compile the
// runtime to compile any package.
package main
import (
"bytes"
"fmt"
"go/ast"
"go/format"
"go/token"
"io"
"io/ioutil"
"os"
"strings"
"golang.org/x/tools/go/packages"
)
// The list of runtime types known by the compiler.
var runtimeTypes = []string{
// panic, recover
"_defer",
// strings
"_string", "stringIterator",
// map
"hashmap", "hashmapBucket", "hashmapIterator",
// channel
"channel", "channelBlockedList", "chanSelectState",
// interface
"_interface", "interfaceMethodInfo", "typecodeID", "structField", "typeInInterface",
// func
"funcValue", "funcValueWithSignature",
}
// The list of runtime calls known to the compiler.
var runtimeCalls = []string{
// panic, recover
"_panic", "_recover",
"nilPanic", "lookupPanic", "slicePanic", "chanMakePanic", "negativeShiftPanic",
// string
"stringEqual", "stringLess",
"stringConcat",
"stringFromBytes", "stringToBytes",
"stringFromRunes", "stringToRunes",
"stringFromUnicode",
"stringNext",
// complex
"complex64div", "complex128div",
// slice
"sliceAppend", "sliceCopy",
// memory
"alloc", "trackPointer",
// print builtin
"printbool",
"printint8", "printuint8",
"printint16", "printuint16",
"printint32", "printuint32",
"printint64", "printuint64",
"printfloat32", "printfloat64",
"printcomplex64", "printcomplex128",
"printstring", "printspace", "printnl",
"printptr", "printmap", "printitf",
// hashmap
"hashmapMake", "hashmapLen", "hashmapNext",
"hashmapStringGet", "hashmapStringSet", "hashmapStringDelete",
"hashmapInterfaceGet", "hashmapInterfaceSet", "hashmapInterfaceDelete",
"hashmapBinaryGet", "hashmapBinarySet", "hashmapBinaryDelete",
// channel, concurrency
"tryChanSelect", "chanMake", "chanSend", "chanRecv", "chanClose", "chanSelect",
"deadlock",
// interface, reflect
"interfaceEqual", "interfaceImplements", "interfaceMethod",
"typeAssert", "interfaceTypeAssert",
// func
"getFuncPtr",
}
// makeDefs generates runtimetypes.go and writes it out after formatting it.
func makeDefs() error {
// Load the runtime package.
pkgs, err := packages.Load(&packages.Config{
Mode: packages.NeedSyntax,
BuildFlags: []string{"-tags=gc.extalloc"},
}, "../src/runtime")
if err != nil {
return err
}
if len(pkgs) != 1 {
return fmt.Errorf("expected 1 package, got %d", len(pkgs))
}
runtimePkg := pkgs[0]
if len(runtimePkg.Errors) != 0 {
return runtimePkg.Errors[0]
}
// Start creating the new Go file.
buf := &bytes.Buffer{}
buf.WriteString(`// Autogenerated by mkruntimetypes.go, DO NOT EDIT.
package compiler
// This file contains definitions for runtime types and functions, so that the
// runtime package can be compiled independently of other packages.
import (
"go/token"
"go/types"
"strconv"
)
// getRuntimeType constructs a new runtime type with the given name. The types
// constructed here must match the types in the runtime package.
func (c *compilerContext) getRuntimeType(name string) types.Type {
if c.program != nil {
return c.program.ImportedPackage("runtime").Type(name).Type()
}
if typ, ok := c.runtimeTypes[name]; ok {
// This type was already created.
return typ
}
typeName := types.NewTypeName(token.NoPos, c.runtimePkg, name, nil)
named := types.NewNamed(typeName, nil, nil)
// Make sure recursive types are only defined once.
c.runtimeTypes[name] = named
var fieldTypes []types.Type
switch name {
`)
err = makeTypeDefs(buf, runtimePkg)
if err != nil {
return err
}
buf.WriteString(` default:
panic("could not find runtime type: runtime." + name)
}
// Create the named struct type.
var fields []*types.Var
for i, t := range fieldTypes {
// Field name doesn't matter: this type is only used to create a LLVM
// struct type which don't have field names.
fields = append(fields, types.NewField(token.NoPos, nil, "field"+strconv.Itoa(i), t, false))
}
named.SetUnderlying(types.NewStruct(fields, nil))
return named
}
// getRuntimeFuncType constructs a new runtime function signature with the given
// name. The function signatures constructed here must match the functions in
// the runtime package.
func (c *compilerContext) getRuntimeFuncType(name string) *types.Signature {
var params []*types.Var
addParam := func(name string, typ types.Type) {
params = append(params, types.NewParam(token.NoPos, c.runtimePkg, name, typ))
}
var results []*types.Var
addResult := func(typ types.Type) {
results = append(results, types.NewParam(token.NoPos, c.runtimePkg, "", typ))
}
switch name {
`)
err = makeFuncDefs(buf, runtimePkg)
if err != nil {
return err
}
buf.WriteString(` default:
panic("unknown runtime call: runtime." + name)
}
return types.NewSignature(nil, types.NewTuple(params...), types.NewTuple(results...), false)
}
`)
source, err := format.Source(buf.Bytes())
if err != nil {
// Fallback (useful for investigating errors).
source = buf.Bytes()
}
err2 := ioutil.WriteFile("runtimetypes.go", source, 0666)
if err2 != nil {
return err2 // error from ioutil.WriteFile
}
return err // error from format.Source (if any)
}
// makeTypeDefs generates the switch body of the getRuntimeType function.
func makeTypeDefs(w io.Writer, runtimePkg *packages.Package) error {
typeSpecs := map[string]*ast.TypeSpec{}
for _, file := range runtimePkg.Syntax {
for _, decl := range file.Decls {
switch decl := decl.(type) {
case *ast.GenDecl:
if decl.Tok != token.TYPE {
continue
}
for _, spec := range decl.Specs {
typeSpec := spec.(*ast.TypeSpec)
typeSpecs[typeSpec.Name.Name] = typeSpec
}
}
}
}
for _, name := range runtimeTypes {
typeSpec := typeSpecs[name]
if typeSpec == nil {
return fmt.Errorf("could not find type: %s", name)
}
fmt.Fprintf(w, "\tcase %#v:\n", typeSpec.Name.Name)
if name == "channelBlockedList" {
fmt.Fprintf(w, "\t\ttaskType := types.NewNamed(types.NewTypeName(token.NoPos, c.taskPkg, \"Task\", nil), nil, nil)\n")
}
fmt.Fprintf(w, "\t\tfieldTypes = []types.Type{\n")
for _, field := range typeSpec.Type.(*ast.StructType).Fields.List {
fieldType := getTypeFromExpr(field.Type, typeSpec.Name.Name)
for _, ident := range field.Names {
fmt.Fprintf(w, "\t\t\t%s, // %s\n", fieldType, ident.Name)
}
}
fmt.Fprintf(w, "\t\t}\n")
}
return nil
}
// makeFuncDefs generates the switch body of the getRuntimeFuncType function.
func makeFuncDefs(w io.Writer, runtimePkg *packages.Package) error {
functions := map[string]*ast.FuncDecl{}
for _, file := range runtimePkg.Syntax {
for _, decl := range file.Decls {
switch decl := decl.(type) {
case *ast.FuncDecl:
functions[decl.Name.Name] = decl
default:
}
}
}
for _, name := range runtimeCalls {
decl := functions[name]
if decl == nil {
return fmt.Errorf("could not find function: %s", name)
}
fmt.Fprintf(w, "\tcase %#v:\n", decl.Name.Name)
for _, field := range decl.Type.Params.List {
typeString := getTypeFromExpr(field.Type, "")
for _, name := range field.Names {
fmt.Fprintf(w, "\t\taddParam(%#v, %s)\n", name.Name, typeString)
}
}
if decl.Type.Results != nil {
for _, field := range decl.Type.Results.List {
typeString := getTypeFromExpr(field.Type, "")
for range field.Names {
fmt.Fprintf(w, "\t\taddResult(%s)\n", typeString)
}
if len(field.Names) == 0 {
fmt.Fprintf(w, "\t\taddResult(%s)\n", typeString)
}
}
}
}
return nil
}
// getTypeFromExpr returns a string which is a piece of Go code that constructs
// the type (as given in ast.Expr) using the go/types package.
func getTypeFromExpr(typ ast.Expr, currentTypeName string) string {
switch typ := typ.(type) {
case *ast.Ident:
if typ.Name == currentTypeName {
// Assume a global named "named" which refers to the currently
// created named type.
return "named"
}
switch typ.Name {
case "bool", "int", "int8", "int16", "int32", "int64", "uint", "uint8", "uint16", "uint32", "uint64", "uintptr", "float32", "float64", "complex64", "complex128", "string", "byte", "rune":
// Built-in types.
return fmt.Sprintf("types.Typ[types.%s]", strings.Title(typ.Name))
case "chanState":
// runtime.chanState is a named type, but that doesn't matter when
// generating LLVM IR.
return fmt.Sprintf("types.Typ[types.Uint8]")
default:
// Assume that we can simply get the type recursively.
return fmt.Sprintf("c.getRuntimeType(%#v)", typ.Name)
}
case *ast.StarExpr:
return fmt.Sprintf("types.NewPointer(%s)", getTypeFromExpr(typ.X, currentTypeName))
case *ast.InterfaceType:
if len(typ.Methods.List) != 0 {
// Unimplemented: interfaces with methods.
return "interface{?}"
}
return "types.NewInterfaceType(nil, nil)"
case *ast.ArrayType:
// Slices and arrays. Assume the array length is a numeric constant and
// not a Go named constant for example.
elementType := getTypeFromExpr(typ.Elt, currentTypeName)
if typ.Len == nil {
return fmt.Sprintf("types.NewSlice(%s)", elementType)
}
length := typ.Len.(*ast.BasicLit).Value
return fmt.Sprintf("types.NewArray(%s, %s)", elementType, length)
case *ast.SelectorExpr:
s := typ.X.(*ast.Ident).Name + "." + typ.Sel.Name
switch s {
case "unsafe.Pointer":
return "types.Typ[types.UnsafePointer]"
case "task.Task":
// Assume there is a variable taskType which refers to the task.Task
// structure.
return "taskType"
default:
return fmt.Sprintf("<unknown %s>", s)
}
case *ast.StructType:
// Inline struct type.
var fields string
for _, field := range typ.Fields.List {
fieldType := getTypeFromExpr(field.Type, currentTypeName)
for _, ident := range field.Names {
fields += fmt.Sprintf("\t\t\ttypes.NewField(token.NoPos, nil, %#v, %s, false),\n", ident.Name, fieldType)
}
}
return fmt.Sprintf("types.NewStruct([]*types.Var{\n%s\t\t}, nil)", fields)
default:
// Dump the raw typ value, for debugging.
return fmt.Sprintf("%#v", typ)
}
}
func main() {
err := makeDefs()
if err != nil {
fmt.Fprintln(os.Stderr, "could not create defs:", err)
os.Exit(1)
}
}
+377
View File
@@ -0,0 +1,377 @@
// Autogenerated by mkruntimetypes.go, DO NOT EDIT.
package compiler
// This file contains definitions for runtime types and functions, so that the
// runtime package can be compiled independently of other packages.
import (
"go/token"
"go/types"
"strconv"
)
// getRuntimeType constructs a new runtime type with the given name. The types
// constructed here must match the types in the runtime package.
func (c *compilerContext) getRuntimeType(name string) types.Type {
if c.program != nil {
return c.program.ImportedPackage("runtime").Type(name).Type()
}
if typ, ok := c.runtimeTypes[name]; ok {
// This type was already created.
return typ
}
typeName := types.NewTypeName(token.NoPos, c.runtimePkg, name, nil)
named := types.NewNamed(typeName, nil, nil)
// Make sure recursive types are only defined once.
c.runtimeTypes[name] = named
var fieldTypes []types.Type
switch name {
case "_defer":
fieldTypes = []types.Type{
types.Typ[types.Uintptr], // callback
types.NewPointer(named), // next
}
case "_string":
fieldTypes = []types.Type{
types.NewPointer(types.Typ[types.Byte]), // ptr
types.Typ[types.Uintptr], // length
}
case "stringIterator":
fieldTypes = []types.Type{
types.Typ[types.Uintptr], // byteindex
}
case "hashmap":
fieldTypes = []types.Type{
types.NewPointer(named), // next
types.Typ[types.UnsafePointer], // buckets
types.Typ[types.Uintptr], // count
types.Typ[types.Uint8], // keySize
types.Typ[types.Uint8], // valueSize
types.Typ[types.Uint8], // bucketBits
}
case "hashmapBucket":
fieldTypes = []types.Type{
types.NewArray(types.Typ[types.Uint8], 8), // tophash
types.NewPointer(named), // next
}
case "hashmapIterator":
fieldTypes = []types.Type{
types.Typ[types.Uintptr], // bucketNumber
types.NewPointer(c.getRuntimeType("hashmapBucket")), // bucket
types.Typ[types.Uint8], // bucketIndex
}
case "channel":
fieldTypes = []types.Type{
types.Typ[types.Uintptr], // elementSize
types.Typ[types.Uintptr], // bufSize
types.Typ[types.Uint8], // state
types.NewPointer(c.getRuntimeType("channelBlockedList")), // blocked
types.Typ[types.Uintptr], // bufHead
types.Typ[types.Uintptr], // bufTail
types.Typ[types.Uintptr], // bufUsed
types.Typ[types.UnsafePointer], // buf
}
case "channelBlockedList":
taskType := types.NewNamed(types.NewTypeName(token.NoPos, c.taskPkg, "Task", nil), nil, nil)
fieldTypes = []types.Type{
types.NewPointer(named), // next
types.NewPointer(taskType), // t
types.NewPointer(c.getRuntimeType("chanSelectState")), // s
types.NewSlice(named), // allSelectOps
}
case "chanSelectState":
fieldTypes = []types.Type{
types.NewPointer(c.getRuntimeType("channel")), // ch
types.Typ[types.UnsafePointer], // value
}
case "_interface":
fieldTypes = []types.Type{
types.Typ[types.Uintptr], // typecode
types.Typ[types.UnsafePointer], // value
}
case "interfaceMethodInfo":
fieldTypes = []types.Type{
types.NewPointer(types.Typ[types.Uint8]), // signature
types.Typ[types.Uintptr], // funcptr
}
case "typecodeID":
fieldTypes = []types.Type{
types.NewPointer(named), // references
types.Typ[types.Uintptr], // length
}
case "structField":
fieldTypes = []types.Type{
types.NewPointer(c.getRuntimeType("typecodeID")), // typecode
types.NewPointer(types.Typ[types.Uint8]), // name
types.NewPointer(types.Typ[types.Uint8]), // tag
types.Typ[types.Bool], // embedded
}
case "typeInInterface":
fieldTypes = []types.Type{
types.NewPointer(c.getRuntimeType("typecodeID")), // typecode
types.NewPointer(c.getRuntimeType("interfaceMethodInfo")), // methodSet
}
case "funcValue":
fieldTypes = []types.Type{
types.Typ[types.UnsafePointer], // context
types.Typ[types.Uintptr], // id
}
case "funcValueWithSignature":
fieldTypes = []types.Type{
types.Typ[types.Uintptr], // funcPtr
types.NewPointer(c.getRuntimeType("typecodeID")), // signature
}
default:
panic("could not find runtime type: runtime." + name)
}
// Create the named struct type.
var fields []*types.Var
for i, t := range fieldTypes {
// Field name doesn't matter: this type is only used to create a LLVM
// struct type which don't have field names.
fields = append(fields, types.NewField(token.NoPos, nil, "field"+strconv.Itoa(i), t, false))
}
named.SetUnderlying(types.NewStruct(fields, nil))
return named
}
// getRuntimeFuncType constructs a new runtime function signature with the given
// name. The function signatures constructed here must match the functions in
// the runtime package.
func (c *compilerContext) getRuntimeFuncType(name string) *types.Signature {
var params []*types.Var
addParam := func(name string, typ types.Type) {
params = append(params, types.NewParam(token.NoPos, c.runtimePkg, name, typ))
}
var results []*types.Var
addResult := func(typ types.Type) {
results = append(results, types.NewParam(token.NoPos, c.runtimePkg, "", typ))
}
switch name {
case "_panic":
addParam("message", types.NewInterfaceType(nil, nil))
case "_recover":
addResult(types.NewInterfaceType(nil, nil))
case "nilPanic":
case "lookupPanic":
case "slicePanic":
case "chanMakePanic":
case "negativeShiftPanic":
case "stringEqual":
addParam("x", types.Typ[types.String])
addParam("y", types.Typ[types.String])
addResult(types.Typ[types.Bool])
case "stringLess":
addParam("x", types.Typ[types.String])
addParam("y", types.Typ[types.String])
addResult(types.Typ[types.Bool])
case "stringConcat":
addParam("x", c.getRuntimeType("_string"))
addParam("y", c.getRuntimeType("_string"))
addResult(c.getRuntimeType("_string"))
case "stringFromBytes":
addParam("x", types.NewStruct([]*types.Var{
types.NewField(token.NoPos, nil, "ptr", types.NewPointer(types.Typ[types.Byte]), false),
types.NewField(token.NoPos, nil, "len", types.Typ[types.Uintptr], false),
types.NewField(token.NoPos, nil, "cap", types.Typ[types.Uintptr], false),
}, nil))
addResult(c.getRuntimeType("_string"))
case "stringToBytes":
addParam("x", c.getRuntimeType("_string"))
addResult(types.NewStruct([]*types.Var{
types.NewField(token.NoPos, nil, "ptr", types.NewPointer(types.Typ[types.Byte]), false),
types.NewField(token.NoPos, nil, "len", types.Typ[types.Uintptr], false),
types.NewField(token.NoPos, nil, "cap", types.Typ[types.Uintptr], false),
}, nil))
case "stringFromRunes":
addParam("runeSlice", types.NewSlice(types.Typ[types.Rune]))
addResult(c.getRuntimeType("_string"))
case "stringToRunes":
addParam("s", types.Typ[types.String])
addResult(types.NewSlice(types.Typ[types.Rune]))
case "stringFromUnicode":
addParam("x", types.Typ[types.Rune])
addResult(c.getRuntimeType("_string"))
case "stringNext":
addParam("s", types.Typ[types.String])
addParam("it", types.NewPointer(c.getRuntimeType("stringIterator")))
addResult(types.Typ[types.Bool])
addResult(types.Typ[types.Int])
addResult(types.Typ[types.Rune])
case "complex64div":
addParam("n", types.Typ[types.Complex64])
addParam("m", types.Typ[types.Complex64])
addResult(types.Typ[types.Complex64])
case "complex128div":
addParam("n", types.Typ[types.Complex128])
addParam("m", types.Typ[types.Complex128])
addResult(types.Typ[types.Complex128])
case "sliceAppend":
addParam("srcBuf", types.Typ[types.UnsafePointer])
addParam("elemsBuf", types.Typ[types.UnsafePointer])
addParam("srcLen", types.Typ[types.Uintptr])
addParam("srcCap", types.Typ[types.Uintptr])
addParam("elemsLen", types.Typ[types.Uintptr])
addParam("elemSize", types.Typ[types.Uintptr])
addResult(types.Typ[types.UnsafePointer])
addResult(types.Typ[types.Uintptr])
addResult(types.Typ[types.Uintptr])
case "sliceCopy":
addParam("dst", types.Typ[types.UnsafePointer])
addParam("src", types.Typ[types.UnsafePointer])
addParam("dstLen", types.Typ[types.Uintptr])
addParam("srcLen", types.Typ[types.Uintptr])
addParam("elemSize", types.Typ[types.Uintptr])
addResult(types.Typ[types.Int])
case "alloc":
addParam("size", types.Typ[types.Uintptr])
addResult(types.Typ[types.UnsafePointer])
case "trackPointer":
addParam("ptr", types.Typ[types.UnsafePointer])
case "printbool":
addParam("b", types.Typ[types.Bool])
case "printint8":
addParam("n", types.Typ[types.Int8])
case "printuint8":
addParam("n", types.Typ[types.Uint8])
case "printint16":
addParam("n", types.Typ[types.Int16])
case "printuint16":
addParam("n", types.Typ[types.Uint16])
case "printint32":
addParam("n", types.Typ[types.Int32])
case "printuint32":
addParam("n", types.Typ[types.Uint32])
case "printint64":
addParam("n", types.Typ[types.Int64])
case "printuint64":
addParam("n", types.Typ[types.Uint64])
case "printfloat32":
addParam("v", types.Typ[types.Float32])
case "printfloat64":
addParam("v", types.Typ[types.Float64])
case "printcomplex64":
addParam("c", types.Typ[types.Complex64])
case "printcomplex128":
addParam("c", types.Typ[types.Complex128])
case "printstring":
addParam("s", types.Typ[types.String])
case "printspace":
case "printnl":
case "printptr":
addParam("ptr", types.Typ[types.Uintptr])
case "printmap":
addParam("m", types.NewPointer(c.getRuntimeType("hashmap")))
case "printitf":
addParam("msg", types.NewInterfaceType(nil, nil))
case "hashmapMake":
addParam("keySize", types.Typ[types.Uint8])
addParam("valueSize", types.Typ[types.Uint8])
addParam("sizeHint", types.Typ[types.Uintptr])
addResult(types.NewPointer(c.getRuntimeType("hashmap")))
case "hashmapLen":
addParam("m", types.NewPointer(c.getRuntimeType("hashmap")))
addResult(types.Typ[types.Int])
case "hashmapNext":
addParam("m", types.NewPointer(c.getRuntimeType("hashmap")))
addParam("it", types.NewPointer(c.getRuntimeType("hashmapIterator")))
addParam("key", types.Typ[types.UnsafePointer])
addParam("value", types.Typ[types.UnsafePointer])
addResult(types.Typ[types.Bool])
case "hashmapStringGet":
addParam("m", types.NewPointer(c.getRuntimeType("hashmap")))
addParam("key", types.Typ[types.String])
addParam("value", types.Typ[types.UnsafePointer])
addParam("valueSize", types.Typ[types.Uintptr])
addResult(types.Typ[types.Bool])
case "hashmapStringSet":
addParam("m", types.NewPointer(c.getRuntimeType("hashmap")))
addParam("key", types.Typ[types.String])
addParam("value", types.Typ[types.UnsafePointer])
case "hashmapStringDelete":
addParam("m", types.NewPointer(c.getRuntimeType("hashmap")))
addParam("key", types.Typ[types.String])
case "hashmapInterfaceGet":
addParam("m", types.NewPointer(c.getRuntimeType("hashmap")))
addParam("key", types.NewInterfaceType(nil, nil))
addParam("value", types.Typ[types.UnsafePointer])
addParam("valueSize", types.Typ[types.Uintptr])
addResult(types.Typ[types.Bool])
case "hashmapInterfaceSet":
addParam("m", types.NewPointer(c.getRuntimeType("hashmap")))
addParam("key", types.NewInterfaceType(nil, nil))
addParam("value", types.Typ[types.UnsafePointer])
case "hashmapInterfaceDelete":
addParam("m", types.NewPointer(c.getRuntimeType("hashmap")))
addParam("key", types.NewInterfaceType(nil, nil))
case "hashmapBinaryGet":
addParam("m", types.NewPointer(c.getRuntimeType("hashmap")))
addParam("key", types.Typ[types.UnsafePointer])
addParam("value", types.Typ[types.UnsafePointer])
addParam("valueSize", types.Typ[types.Uintptr])
addResult(types.Typ[types.Bool])
case "hashmapBinarySet":
addParam("m", types.NewPointer(c.getRuntimeType("hashmap")))
addParam("key", types.Typ[types.UnsafePointer])
addParam("value", types.Typ[types.UnsafePointer])
case "hashmapBinaryDelete":
addParam("m", types.NewPointer(c.getRuntimeType("hashmap")))
addParam("key", types.Typ[types.UnsafePointer])
case "tryChanSelect":
addParam("recvbuf", types.Typ[types.UnsafePointer])
addParam("states", types.NewSlice(c.getRuntimeType("chanSelectState")))
addResult(types.Typ[types.Uintptr])
addResult(types.Typ[types.Bool])
case "chanMake":
addParam("elementSize", types.Typ[types.Uintptr])
addParam("bufSize", types.Typ[types.Uintptr])
addResult(types.NewPointer(c.getRuntimeType("channel")))
case "chanSend":
addParam("ch", types.NewPointer(c.getRuntimeType("channel")))
addParam("value", types.Typ[types.UnsafePointer])
case "chanRecv":
addParam("ch", types.NewPointer(c.getRuntimeType("channel")))
addParam("value", types.Typ[types.UnsafePointer])
addResult(types.Typ[types.Bool])
case "chanClose":
addParam("ch", types.NewPointer(c.getRuntimeType("channel")))
case "chanSelect":
addParam("recvbuf", types.Typ[types.UnsafePointer])
addParam("states", types.NewSlice(c.getRuntimeType("chanSelectState")))
addParam("ops", types.NewSlice(c.getRuntimeType("channelBlockedList")))
addResult(types.Typ[types.Uintptr])
addResult(types.Typ[types.Bool])
case "deadlock":
case "interfaceEqual":
addParam("x", types.NewInterfaceType(nil, nil))
addParam("y", types.NewInterfaceType(nil, nil))
addResult(types.Typ[types.Bool])
case "interfaceImplements":
addParam("typecode", types.Typ[types.Uintptr])
addParam("interfaceMethodSet", types.NewPointer(types.NewPointer(types.Typ[types.Uint8])))
addResult(types.Typ[types.Bool])
case "interfaceMethod":
addParam("typecode", types.Typ[types.Uintptr])
addParam("interfaceMethodSet", types.NewPointer(types.NewPointer(types.Typ[types.Uint8])))
addParam("signature", types.NewPointer(types.Typ[types.Uint8]))
addResult(types.Typ[types.Uintptr])
case "typeAssert":
addParam("actualType", types.Typ[types.Uintptr])
addParam("assertedType", types.NewPointer(c.getRuntimeType("typecodeID")))
addResult(types.Typ[types.Bool])
case "interfaceTypeAssert":
addParam("ok", types.Typ[types.Bool])
case "getFuncPtr":
addParam("val", c.getRuntimeType("funcValue"))
addParam("signature", types.NewPointer(c.getRuntimeType("typecodeID")))
addResult(types.Typ[types.Uintptr])
default:
panic("unknown runtime call: runtime." + name)
}
return types.NewSignature(nil, types.NewTuple(params...), types.NewTuple(results...), false)
}
+223 -1
View File
@@ -15,6 +15,208 @@ import (
"tinygo.org/x/go-llvm"
)
type inlineType int
// How much to inline.
const (
// Default behavior. The compiler decides for itself whether any given
// function will be inlined. Whether any function is inlined depends on the
// optimization level.
inlineDefault inlineType = iota
// Inline hint, just like the C inline keyword (signalled using
// //go:inline). The compiler will be more likely to inline this function,
// but it is not a guarantee.
inlineHint
// Don't inline, just like the GCC noinline attribute. Signalled using
// //go:noinline.
inlineNone
)
// functionInfo contains some information about a function or method. In
// particular, it contains information obtained from pragmas.
//
// The linkName value contains a valid link name, even though //go:linkname is
// not present.
type functionInfo struct {
linkName string // go:linkname, go:export
module string // go:wasm-module
exported bool // go:export
nobounds bool // go:nobounds
inline inlineType // go:inline
}
// getFunction returns the LLVM function for the given *ssa.Function, creating
// it if needed. It can later be filled with compilerContext.createFunction().
func (c *compilerContext) getFunction(fn *ssa.Function) llvm.Value {
info := c.getFunctionInfo(fn)
return c.getFunctionRaw(fn.Signature, info)
}
func (c *compilerContext) getFunctionRaw(sig *types.Signature, info functionInfo) llvm.Value {
llvmFn := c.mod.NamedFunction(info.linkName)
if !llvmFn.IsNil() {
return llvmFn
}
var retType llvm.Type
if sig.Results() == nil {
retType = c.ctx.VoidType()
} else if sig.Results().Len() == 1 {
retType = c.getLLVMType(sig.Results().At(0).Type())
} else {
results := make([]llvm.Type, 0, sig.Results().Len())
for i := 0; i < sig.Results().Len(); i++ {
results = append(results, c.getLLVMType(sig.Results().At(i).Type()))
}
retType = c.ctx.StructType(results, false)
}
var paramInfos []paramInfo
params := []*types.Var{}
if sig.Recv() != nil {
params = append(params, sig.Recv())
}
for i := 0; i < sig.Params().Len(); i++ {
params = append(params, sig.Params().At(i))
}
for _, param := range params {
paramType := c.getLLVMType(param.Type())
paramFragmentInfos := expandFormalParamType(paramType, param.Name(), param.Type())
paramInfos = append(paramInfos, paramFragmentInfos...)
}
// Add an extra parameter as the function context. This context is used in
// closures and bound methods, but should be optimized away when not used.
if !info.exported {
paramInfos = append(paramInfos, paramInfo{llvmType: c.i8ptrType, name: "context", flags: 0})
paramInfos = append(paramInfos, paramInfo{llvmType: c.i8ptrType, name: "parentHandle", flags: 0})
}
var paramTypes []llvm.Type
for _, info := range paramInfos {
paramTypes = append(paramTypes, info.llvmType)
}
fnType := llvm.FunctionType(retType, paramTypes, false)
llvmFn = llvm.AddFunction(c.mod, info.linkName, fnType)
dereferenceableOrNullKind := llvm.AttributeKindID("dereferenceable_or_null")
for i, info := range paramInfos {
if info.flags&paramIsDeferenceableOrNull == 0 {
continue
}
if info.llvmType.TypeKind() == llvm.PointerTypeKind {
el := info.llvmType.ElementType()
size := c.targetData.TypeAllocSize(el)
if size == 0 {
// dereferenceable_or_null(0) appears to be illegal in LLVM.
continue
}
dereferenceableOrNull := c.ctx.CreateEnumAttribute(dereferenceableOrNullKind, size)
llvmFn.AddAttributeAtIndex(i+1, dereferenceableOrNull)
}
}
// External/exported functions may not retain pointer values.
// https://golang.org/cmd/cgo/#hdr-Passing_pointers
if info.exported {
// Set the wasm-import-module attribute if the function's module is set.
if info.module != "" {
wasmImportModuleAttr := c.ctx.CreateStringAttribute("wasm-import-module", info.module)
llvmFn.AddFunctionAttr(wasmImportModuleAttr)
}
nocaptureKind := llvm.AttributeKindID("nocapture")
nocapture := c.ctx.CreateEnumAttribute(nocaptureKind, 0)
for i, typ := range paramTypes {
if typ.TypeKind() == llvm.PointerTypeKind {
llvmFn.AddAttributeAtIndex(i+1, nocapture)
}
}
}
return llvmFn
}
// getFunctionInfo returns information about a function that is not directly
// present in *ssa.Function, such as the link name and whether it should be
// exported.
func (c *compilerContext) getFunctionInfo(f *ssa.Function) functionInfo {
info := functionInfo{}
if strings.HasPrefix(f.Name(), "C.") {
// Created by CGo: such a name cannot be created by regular C code.
info.linkName = f.Name()[2:]
info.exported = true
} else {
// Pick the default linkName.
info.linkName = f.RelString(nil)
// Check for //go: pragmas, which may change the link name (among
// others).
info.parsePragmas(f)
}
return info
}
// parsePragmas is used by getFunctionInfo to parse function pragmas such as
// //export or //go:noinline.
func (info *functionInfo) parsePragmas(f *ssa.Function) {
// Parse compiler directives in the preceding comments.
if f.Syntax() == nil {
return
}
if decl, ok := f.Syntax().(*ast.FuncDecl); ok && decl.Doc != nil {
for _, comment := range decl.Doc.List {
text := comment.Text
if strings.HasPrefix(text, "//export ") {
// Rewrite '//export' to '//go:export' for compatibility with
// gc.
text = "//go:" + text[2:]
}
if !strings.HasPrefix(text, "//go:") {
continue
}
parts := strings.Fields(text)
switch parts[0] {
case "//go:export":
if len(parts) != 2 {
continue
}
info.linkName = parts[1]
info.exported = true
case "//go:wasm-module":
// Alternative comment for setting the import module.
if len(parts) != 2 {
continue
}
info.module = parts[1]
case "//go:inline":
info.inline = inlineHint
case "//go:noinline":
info.inline = inlineNone
case "//go:linkname":
if len(parts) != 3 || parts[1] != f.Name() {
continue
}
// Only enable go:linkname when the package imports "unsafe".
// This is a slightly looser requirement than what gc uses: gc
// requires the file to import "unsafe", not the package as a
// whole.
if hasUnsafeImport(f.Pkg.Pkg) {
info.linkName = parts[2]
}
case "//go:nobounds":
// Skip bounds checking in this function. Useful for some
// runtime functions.
// This is somewhat dangerous and thus only imported in packages
// that import unsafe.
if hasUnsafeImport(f.Pkg.Pkg) {
info.nobounds = true
}
}
}
}
}
// globalInfo contains some information about a specific global. By default,
// linkName is equal to .RelString(nil) on a global and extern is false, but for
// some symbols this is different (due to //go:extern for example).
@@ -86,7 +288,7 @@ func (c *compilerContext) getGlobal(g *ssa.Global) llvm.Value {
// Add debug info.
// TODO: this should be done for every global in the program, not just
// the ones that are referenced from some code.
pos := c.ir.Program.Fset.Position(g.Pos())
pos := c.program.Fset.Position(g.Pos())
diglobal := c.dibuilder.CreateGlobalVariableExpression(c.difiles[pos.Filename], llvm.DIGlobalVariableExpression{
Name: g.RelString(nil),
LinkageName: info.linkName,
@@ -145,3 +347,23 @@ func (info *globalInfo) parsePragmas(doc *ast.CommentGroup) {
}
}
}
// Get all methods of a type.
func getAllMethods(prog *ssa.Program, typ types.Type) []*types.Selection {
ms := prog.MethodSets.MethodSet(typ)
methods := make([]*types.Selection, ms.Len())
for i := 0; i < ms.Len(); i++ {
methods[i] = ms.At(i)
}
return methods
}
// Return true if this package imports "unsafe", false otherwise.
func hasUnsafeImport(pkg *types.Package) bool {
for _, imp := range pkg.Imports() {
if imp == types.Unsafe {
return true
}
}
return false
}
+13
View File
@@ -0,0 +1,13 @@
package main
func add(x, y int) int {
return x + y
}
func stringEqual(s string) bool {
return s == "s"
}
func closeChan(ch chan int) {
close(ch)
}
+41
View File
@@ -0,0 +1,41 @@
target datalayout = "e-m:e-p:32:32-Fi8-i64:64-v128:64:128-a:0:32-n32-S64"
target triple = "armv7m-none-eabi"
%runtime.channel = type { i32, i32, i8, %runtime.channelBlockedList*, i32, i32, i32, i8* }
%runtime.channelBlockedList = type { %runtime.channelBlockedList*, %"internal/task.Task"*, %runtime.chanSelectState*, { %runtime.channelBlockedList*, i32, i32 } }
%"internal/task.Task" = type opaque
%runtime.chanSelectState = type { %runtime.channel*, i8* }
%runtime._string = type { i8*, i32 }
@"main.stringEqual$string" = internal unnamed_addr constant [1 x i8] c"s"
define internal i32 @main.add(i32 %x, i32 %y, i8* %context, i8* %parentHandle) unnamed_addr {
entry:
%0 = add i32 %x, %y
ret i32 %0
}
define internal void @main.closeChan(%runtime.channel* dereferenceable_or_null(32) %ch, i8* %context, i8* %parentHandle) unnamed_addr {
entry:
call void @runtime.chanClose(%runtime.channel* %ch, i8* undef, i8* null)
ret void
}
declare void @runtime.chanClose(%runtime.channel* dereferenceable_or_null(32), i8*, i8*)
define internal void @main.init(i8* %context, i8* %parentHandle) unnamed_addr {
entry:
ret void
}
define internal i1 @main.stringEqual(i8* %s.data, i32 %s.len, i8* %context, i8* %parentHandle) unnamed_addr {
entry:
%0 = insertvalue %runtime._string zeroinitializer, i8* %s.data, 0
%1 = insertvalue %runtime._string %0, i32 %s.len, 1
%2 = extractvalue %runtime._string %1, 0
%3 = extractvalue %runtime._string %1, 1
%4 = call i1 @runtime.stringEqual(i8* %2, i32 %3, i8* getelementptr inbounds ([1 x i8], [1 x i8]* @"main.stringEqual$string", i32 0, i32 0), i32 1, i8* undef, i8* null)
ret i1 %4
}
declare i1 @runtime.stringEqual(i8*, i32, i8*, i32, i8*, i8*)
+3 -4
View File
@@ -4,11 +4,10 @@ go 1.11
require (
github.com/blakesmith/ar v0.0.0-20150311145944-8bd4349a67f2
github.com/chromedp/cdproto v0.0.0-20200116234248-4da64dd111ac
github.com/chromedp/chromedp v0.5.3
github.com/google/shlex v0.0.0-20181106134648-c34317bd91bf
github.com/marcinbor85/gohex v0.0.0-20200531091804-343a4b548892
github.com/marcinbor85/gohex v0.0.0-20180128172054-7a43cd876e46
go.bug.st/serial v1.0.0
golang.org/x/tools v0.0.0-20200216192241-b320d3a0f5a2
tinygo.org/x/go-llvm v0.0.0-20200503225853-345b2947b59d
google.golang.org/appengine v1.4.0 // indirect
tinygo.org/x/go-llvm v0.0.0-20200401165421-8d120882fc7a
)
+29 -20
View File
@@ -1,26 +1,13 @@
github.com/blakesmith/ar v0.0.0-20150311145944-8bd4349a67f2 h1:oMCHnXa6CCCafdPDbMh/lWRhRByN0VFLvv+g+ayx1SI=
github.com/blakesmith/ar v0.0.0-20150311145944-8bd4349a67f2/go.mod h1:PkYb9DJNAwrSvRx5DYA+gUcOIgTGVMNkfSCbZM8cWpI=
github.com/chromedp/cdproto v0.0.0-20200116234248-4da64dd111ac h1:T7V5BXqnYd55Hj/g5uhDYumg9Fp3rMTS6bykYtTIFX4=
github.com/chromedp/cdproto v0.0.0-20200116234248-4da64dd111ac/go.mod h1:PfAWWKJqjlGFYJEidUM6aVIWPr0EpobeyVWEEmplX7g=
github.com/chromedp/chromedp v0.5.3 h1:F9LafxmYpsQhWQBdCs+6Sret1zzeeFyHS5LkRF//Ffg=
github.com/chromedp/chromedp v0.5.3/go.mod h1:YLdPtndaHQ4rCpSpBG+IPpy9JvX0VD+7aaLxYgYj28w=
github.com/creack/goselect v0.1.1 h1:tiSSgKE1eJtxs1h/VgGQWuXUP0YS4CDIFMp6vaI1ls0=
github.com/creack/goselect v0.1.1/go.mod h1:a/NhLweNvqIYMuxcMOuWY516Cimucms3DglDzQP3hKY=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/gobwas/httphead v0.0.0-20180130184737-2c6c146eadee h1:s+21KNqlpePfkah2I+gwHF8xmJWRjooY+5248k6m4A0=
github.com/gobwas/httphead v0.0.0-20180130184737-2c6c146eadee/go.mod h1:L0fX3K22YWvt/FAX9NnzrNzcI4wNYi9Yku4O0LKYflo=
github.com/gobwas/pool v0.2.0 h1:QEmUOlnSjWtnpRGHF3SauEiOsy82Cup83Vf2LcMlnc8=
github.com/gobwas/pool v0.2.0/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw=
github.com/gobwas/ws v1.0.2 h1:CoAavW/wd/kulfZmSIBt6p24n4j7tHgNVCjsfHVNUbo=
github.com/gobwas/ws v1.0.2/go.mod h1:szmBTxLgaFppYjEmNtny/v3w89xOydFnnZMcgRRu/EM=
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/knq/sysutil v0.0.0-20191005231841-15668db23d08 h1:V0an7KRw92wmJysvFvtqtKMAPmvS5O0jtB0nYo6t+gs=
github.com/knq/sysutil v0.0.0-20191005231841-15668db23d08/go.mod h1:dFWs1zEqDjFtnBXsd1vPOZaLsESovai349994nHx3e0=
github.com/mailru/easyjson v0.7.0 h1:aizVhC/NAAcKWb+5QsU1iNOZb4Yws5UO2I+aIprQITM=
github.com/mailru/easyjson v0.7.0/go.mod h1:KAzv3t3aY1NaHWoQz1+4F1ccyAH66Jk7yos7ldAVICs=
github.com/marcinbor85/gohex v0.0.0-20200531091804-343a4b548892 h1:6J+qramlHVLmiBOgRiBOnQkno8uprqG6YFFQTt6uYIw=
github.com/marcinbor85/gohex v0.0.0-20200531091804-343a4b548892/go.mod h1:Pb6XcsXyropB9LNHhnqaknG/vEwYztLkQzVCHv8sQ3M=
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/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
@@ -30,21 +17,43 @@ golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACk
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee h1:WG0RUwxtNT4qqaXX3DPA8zHFNm/D9xaBpxzHt1WcA/E=
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191128015809-6d18c012aee9 h1:ZBzSG/7F4eNKz2L3GE9o300RX0Az1Bw5HF7PDraD+qU=
golang.org/x/sys v0.0.0-20191128015809-6d18c012aee9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200116001909-b77594299b42 h1:vEOn+mP2zCOVzKckCZy6YsCtDblrpj/w7B9nxGNELpg=
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/tools v0.0.0-20190227180812-8dcc6e70cdef h1:ymc9FeDom3RIEA3coKokSllBB1hRcMT0tZ1W3Jf9Ids=
golang.org/x/tools v0.0.0-20190227180812-8dcc6e70cdef/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
golang.org/x/tools v0.0.0-20200216192241-b320d3a0f5a2 h1:0sfSpGSa544Fwnbot3Oxq/U6SXqjty6Jy/3wRhVS7ig=
golang.org/x/tools v0.0.0-20200216192241-b320d3a0f5a2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898 h1:/atklqdjdhuosWIl6AIbOeHJjicWYPqR9bpxqxYG2pA=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
tinygo.org/x/go-llvm v0.0.0-20200503225853-345b2947b59d h1:hcX7vpB067GWM/EH4sGGOti0PMgIx+0bbZwUXctOIvE=
tinygo.org/x/go-llvm v0.0.0-20200503225853-345b2947b59d/go.mod h1:fv1F0BSNpxMfCL0zF3M4OPFbgYHnhtB6ST0HvUtu/LE=
tinygo.org/x/go-llvm v0.0.0-20190224120431-7707ae5d1261 h1:rJS2Hga39YAnm7DE4qrPm6Dr/67EOojL0XPzvbEeBiw=
tinygo.org/x/go-llvm v0.0.0-20190224120431-7707ae5d1261/go.mod h1:fv1F0BSNpxMfCL0zF3M4OPFbgYHnhtB6ST0HvUtu/LE=
tinygo.org/x/go-llvm v0.0.0-20190818154551-95bc4ffe1add h1:dFjMH1sLhYADg8UQm7DB56B7e+TfvAmWmEZLhyv3r/w=
tinygo.org/x/go-llvm v0.0.0-20190818154551-95bc4ffe1add/go.mod h1:fv1F0BSNpxMfCL0zF3M4OPFbgYHnhtB6ST0HvUtu/LE=
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=
tinygo.org/x/go-llvm v0.0.0-20191215173731-ad71f3d24aae h1:s8J5EyxCkHxXB08UI3gk9W9IS/ekizRvSX+PfZxnAB0=
tinygo.org/x/go-llvm v0.0.0-20191215173731-ad71f3d24aae/go.mod h1:fv1F0BSNpxMfCL0zF3M4OPFbgYHnhtB6ST0HvUtu/LE=
tinygo.org/x/go-llvm v0.0.0-20200104190746-1ff21df33566 h1:a4y30bTf7U0zDA75v2PTL+XQ2OzJetj19gK8XwQpUNY=
tinygo.org/x/go-llvm v0.0.0-20200104190746-1ff21df33566/go.mod h1:fv1F0BSNpxMfCL0zF3M4OPFbgYHnhtB6ST0HvUtu/LE=
tinygo.org/x/go-llvm v0.0.0-20200226165415-53522ab6713d h1:mtgZh/e8a3wxneQFuLXoQYO//1mvlki02yZ1JCwMKp4=
tinygo.org/x/go-llvm v0.0.0-20200226165415-53522ab6713d/go.mod h1:fv1F0BSNpxMfCL0zF3M4OPFbgYHnhtB6ST0HvUtu/LE=
tinygo.org/x/go-llvm v0.0.0-20200401165421-8d120882fc7a h1:Ugje2Lxuv8CFncHzs5W+hWfJvPsM+W4K0zRvzFbLvoE=
tinygo.org/x/go-llvm v0.0.0-20200401165421-8d120882fc7a/go.mod h1:fv1F0BSNpxMfCL0zF3M4OPFbgYHnhtB6ST0HvUtu/LE=
-68
View File
@@ -1,68 +0,0 @@
package goenv
import (
"errors"
"fmt"
"io"
"io/ioutil"
"path/filepath"
"regexp"
"strings"
)
// Version of TinyGo.
// Update this value before release of new version of software.
const Version = "0.15.0"
// 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
}
}
-5
View File
@@ -1,5 +0,0 @@
# Hooks for Docker Hub
Files in this directory are custom commands to be run during the different Docker Hub build phases.
See https://docs.docker.com/docker-hub/builds/advanced/#custom-build-phase-hooks
-4
View File
@@ -1,4 +0,0 @@
#!/bin/bash
# Docker hub does a recursive clone, then checks the branch out,
# so when a PR adds a submodule (or updates it), it fails.
git submodule update --init
+1 -3
View File
@@ -47,12 +47,10 @@ func (e *Error) Error() string {
// 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, err error) *Error {
pos := getPosition(inst)
return &Error{
ImportPath: e.packagePath,
Pos: pos,
Pos: getPosition(inst),
Err: err,
Traceback: []ErrorLine{{pos, inst}},
}
}
+11 -38
View File
@@ -95,11 +95,7 @@ func (fr *frame) evalBasicBlock(bb, incoming llvm.BasicBlock, indent string) (re
if !operand.IsConstant() || inst.IsVolatile() || (!operand.Underlying.IsAConstantExpr().IsNil() && operand.Underlying.Opcode() == llvm.BitCast) {
value = fr.builder.CreateLoad(operand.Value(), inst.Name())
} else {
var err error
value, err = operand.Load()
if err != nil {
return nil, nil, fr.errorAt(inst, err)
}
value = operand.Load()
}
if value.Type() != inst.Type() {
return nil, nil, fr.errorAt(inst, errors.New("interp: load: type does not match"))
@@ -111,10 +107,7 @@ func (fr *frame) evalBasicBlock(bb, incoming llvm.BasicBlock, indent string) (re
if inst.IsVolatile() {
fr.builder.CreateStore(value.Value(), ptr.Value())
} else {
err := ptr.Store(value.Value())
if err != nil {
return nil, nil, fr.errorAt(inst, err)
}
ptr.Store(value.Value())
}
case !inst.IsAGetElementPtrInst().IsNil():
value := fr.getLocal(inst.Operand(0))
@@ -290,6 +283,7 @@ func (fr *frame) evalBasicBlock(bb, incoming llvm.BasicBlock, indent string) (re
PkgName: fr.packagePath,
KeySize: int(keySize),
ValueSize: int(valueSize),
MapType: inst.Type().ElementType(),
}
case callee.Name() == "runtime.hashmapStringSet":
// set a string key in the map
@@ -315,10 +309,7 @@ func (fr *frame) evalBasicBlock(bb, incoming llvm.BasicBlock, indent string) (re
}
// "key" is a Go string value, which in the TinyGo calling convention is split up
// into separate pointer and length parameters.
err := m.PutString(keyBuf, keyLen, valPtr)
if err != nil {
return nil, nil, fr.errorAt(inst, err)
}
m.PutString(keyBuf, keyLen, valPtr)
case callee.Name() == "runtime.hashmapBinarySet":
// set a binary (int etc.) key in the map
keyBuf := fr.getLocal(inst.Operand(1)).(*LocalValue)
@@ -339,24 +330,15 @@ func (fr *frame) evalBasicBlock(bb, incoming llvm.BasicBlock, indent string) (re
fr.builder.CreateCall(callee, llvmParams, "")
continue
}
err := m.PutBinary(keyBuf, valPtr)
if err != nil {
return nil, nil, fr.errorAt(inst, err)
}
m.PutBinary(keyBuf, valPtr)
case callee.Name() == "runtime.stringConcat":
// adding two strings together
buf1Ptr := fr.getLocal(inst.Operand(0))
buf1Len := fr.getLocal(inst.Operand(1))
buf2Ptr := fr.getLocal(inst.Operand(2))
buf2Len := fr.getLocal(inst.Operand(3))
buf1, err := getStringBytes(buf1Ptr, buf1Len.Value())
if err != nil {
return nil, nil, fr.errorAt(inst, err)
}
buf2, err := getStringBytes(buf2Ptr, buf2Len.Value())
if err != nil {
return nil, nil, fr.errorAt(inst, err)
}
buf1 := getStringBytes(buf1Ptr, buf1Len.Value())
buf2 := getStringBytes(buf2Ptr, buf2Len.Value())
result := []byte(string(buf1) + string(buf2))
vals := make([]llvm.Value, len(result))
for i := range vals {
@@ -369,7 +351,7 @@ func (fr *frame) evalBasicBlock(bb, incoming llvm.BasicBlock, indent string) (re
global.SetLinkage(llvm.InternalLinkage)
global.SetGlobalConstant(true)
global.SetUnnamedAddr(true)
stringType := fr.Mod.GetTypeByName("runtime._string")
stringType := inst.Type()
retPtr := llvm.ConstGEP(global, getLLVMIndices(fr.Mod.Context().Int32Type(), []uint32{0, 0}))
retLen := llvm.ConstInt(stringType.StructElementTypes()[1], uint64(len(result)), false)
ret := llvm.ConstNull(stringType)
@@ -420,15 +402,9 @@ func (fr *frame) evalBasicBlock(bb, incoming llvm.BasicBlock, indent string) (re
return nil, nil, fr.errorAt(inst, errors.New("interp: trying to copy a slice with negative length?"))
}
for i := int64(0); i < length; i++ {
var err error
// *dst = *src
val, err := srcArray.Load()
if err != nil {
return nil, nil, fr.errorAt(inst, err)
}
err = dstArray.Store(val)
if err != nil {
return nil, nil, fr.errorAt(inst, err)
}
dstArray.Store(srcArray.Load())
// dst++
dstArrayValue, err := dstArray.GetElementPtr([]uint32{1})
if err != nil {
@@ -446,10 +422,7 @@ func (fr *frame) evalBasicBlock(bb, incoming llvm.BasicBlock, indent string) (re
// convert a string to a []byte
bufPtr := fr.getLocal(inst.Operand(0))
bufLen := fr.getLocal(inst.Operand(1))
result, err := getStringBytes(bufPtr, bufLen.Value())
if err != nil {
return nil, nil, fr.errorAt(inst, err)
}
result := getStringBytes(bufPtr, bufLen.Value())
vals := make([]llvm.Value, len(result))
for i := range vals {
vals[i] = llvm.ConstInt(fr.Mod.Context().Int8Type(), uint64(result[i]), false)
-6
View File
@@ -62,10 +62,6 @@ func (e *evalPackage) hasSideEffects(fn llvm.Value) (*sideEffectResult, *Error)
return &sideEffectResult{severity: sideEffectNone}, nil
case name == "llvm.dbg.value":
return &sideEffectResult{severity: sideEffectNone}, nil
case name == "(*sync/atomic.Value).Load" || name == "(*sync/atomic.Value).Store":
// These functions do some unsafe pointer loading/storing but are
// otherwise safe.
return &sideEffectResult{severity: sideEffectLimited}, nil
case strings.HasPrefix(name, "llvm.lifetime."):
return &sideEffectResult{severity: sideEffectNone}, nil
}
@@ -126,8 +122,6 @@ func (e *evalPackage) hasSideEffects(fn llvm.Value) (*sideEffectResult, *Error)
// External function call. Assume only limited side effects
// (no affected globals, etc.).
switch child.Name() {
case "runtime.alloc":
continue
case "runtime.typeAssert":
continue // implemented in interp
case "runtime.interfaceImplements":
+5 -10
View File
@@ -1,8 +1,6 @@
package interp
import (
"errors"
"tinygo.org/x/go-llvm"
)
@@ -20,23 +18,20 @@ func getUses(value llvm.Value) []llvm.Value {
// getStringBytes loads the byte slice of a Go string represented as a
// {ptr, len} pair.
func getStringBytes(strPtr Value, strLen llvm.Value) ([]byte, error) {
func getStringBytes(strPtr Value, strLen llvm.Value) []byte {
if !strLen.IsConstant() {
return nil, errors.New("getStringBytes with a non-constant length")
panic("getStringBytes with a non-constant length")
}
buf := make([]byte, strLen.ZExtValue())
for i := range buf {
gep, err := strPtr.GetElementPtr([]uint32{uint32(i)})
if err != nil {
return nil, err
}
c, err := gep.Load()
if err != nil {
return nil, err
panic(err) // TODO
}
c := gep.Load()
buf[i] = byte(c.ZExtValue())
}
return buf, nil
return buf
}
// getLLVMIndices converts an []uint32 into an []llvm.Value, for use in
+86 -80
View File
@@ -15,8 +15,8 @@ type Value interface {
Value() llvm.Value // returns a LLVM value
Type() llvm.Type // equal to Value().Type()
IsConstant() bool // returns true if this value is a constant value
Load() (llvm.Value, error) // dereference a pointer
Store(llvm.Value) error // store to a pointer
Load() llvm.Value // dereference a pointer
Store(llvm.Value) // store to a pointer
GetElementPtr([]uint32) (Value, error) // returns an interior pointer
String() string // string representation, for debugging
}
@@ -44,32 +44,29 @@ func (v *LocalValue) IsConstant() bool {
}
// Load loads a constant value if this is a constant pointer.
func (v *LocalValue) Load() (llvm.Value, error) {
func (v *LocalValue) Load() llvm.Value {
if !v.Underlying.IsAGlobalVariable().IsNil() {
return v.Underlying.Initializer(), nil
return v.Underlying.Initializer()
}
switch v.Underlying.Opcode() {
case llvm.GetElementPtr:
indices := v.getConstGEPIndices()
if indices[0] != 0 {
return llvm.Value{}, errors.New("invalid GEP")
panic("invalid GEP")
}
global := v.Eval.getValue(v.Underlying.Operand(0))
agg, err := global.Load()
if err != nil {
return llvm.Value{}, err
}
return llvm.ConstExtractValue(agg, indices[1:]), nil
agg := global.Load()
return llvm.ConstExtractValue(agg, indices[1:])
case llvm.BitCast:
return llvm.Value{}, errors.New("interp: load from a bitcast")
panic("interp: load from a bitcast")
default:
return llvm.Value{}, errors.New("interp: load from a constant")
panic("interp: load from a constant")
}
}
// Store stores to the underlying value if the value type is a pointer type,
// otherwise it returns an error.
func (v *LocalValue) Store(value llvm.Value) error {
// otherwise it panics.
func (v *LocalValue) Store(value llvm.Value) {
if !v.Underlying.IsAGlobalVariable().IsNil() {
if !value.IsConstant() {
v.MarkDirty()
@@ -77,28 +74,26 @@ func (v *LocalValue) Store(value llvm.Value) error {
} else {
v.Underlying.SetInitializer(value)
}
return nil
return
}
if !value.IsConstant() {
v.MarkDirty()
v.Eval.builder.CreateStore(value, v.Underlying)
return nil
return
}
switch v.Underlying.Opcode() {
case llvm.GetElementPtr:
indices := v.getConstGEPIndices()
if indices[0] != 0 {
return errors.New("invalid GEP")
panic("invalid GEP")
}
global := &LocalValue{v.Eval, v.Underlying.Operand(0)}
agg, err := global.Load()
if err != nil {
return err
}
agg := global.Load()
agg = llvm.ConstInsertValue(agg, value, indices[1:])
return global.Store(agg)
global.Store(agg)
return
default:
return errors.New("interp: store on a constant")
panic("interp: store on a constant")
}
}
@@ -183,6 +178,8 @@ type MapValue struct {
ValueSize int
KeyType llvm.Type
ValueType llvm.Type
MapType llvm.Type // *%runtime.hashmap
keyVariant string
}
func (v *MapValue) newBucket() llvm.Value {
@@ -226,31 +223,32 @@ func (v *MapValue) Value() llvm.Value {
var keyBuf []byte
llvmKey := key.Value()
llvmValue := v.Values[i].Value()
if key.Type().TypeKind() == llvm.StructTypeKind && key.Type().StructName() == "runtime._string" {
switch v.keyVariant {
case "string":
keyPtr := llvm.ConstExtractValue(llvmKey, []uint32{0})
keyLen := llvm.ConstExtractValue(llvmKey, []uint32{1})
keyPtrVal := v.Eval.getValue(keyPtr)
var err error
keyBuf, err = getStringBytes(keyPtrVal, keyLen)
if err != nil {
panic(err) // TODO
keyBuf = getStringBytes(keyPtrVal, keyLen)
case "binary":
if key.Type().TypeKind() == llvm.IntegerTypeKind {
keyBuf = make([]byte, v.Eval.TargetData.TypeAllocSize(key.Type()))
n := key.Value().ZExtValue()
for i := range keyBuf {
keyBuf[i] = byte(n)
n >>= 8
}
} else if key.Type().TypeKind() == llvm.ArrayTypeKind &&
key.Type().ElementType().TypeKind() == llvm.IntegerTypeKind &&
key.Type().ElementType().IntTypeWidth() == 8 {
keyBuf = make([]byte, v.Eval.TargetData.TypeAllocSize(key.Type()))
for i := range keyBuf {
keyBuf[i] = byte(llvm.ConstExtractValue(llvmKey, []uint32{uint32(i)}).ZExtValue())
}
} else {
panic("interp: map key type not implemented: " + key.Type().String())
}
} else if key.Type().TypeKind() == llvm.IntegerTypeKind {
keyBuf = make([]byte, v.Eval.TargetData.TypeAllocSize(key.Type()))
n := key.Value().ZExtValue()
for i := range keyBuf {
keyBuf[i] = byte(n)
n >>= 8
}
} else if key.Type().TypeKind() == llvm.ArrayTypeKind &&
key.Type().ElementType().TypeKind() == llvm.IntegerTypeKind &&
key.Type().ElementType().IntTypeWidth() == 8 {
keyBuf = make([]byte, v.Eval.TargetData.TypeAllocSize(key.Type()))
for i := range keyBuf {
keyBuf[i] = byte(llvm.ConstExtractValue(llvmKey, []uint32{uint32(i)}).ZExtValue())
}
} else {
panic("interp: map key type not implemented: " + key.Type().String())
default:
panic("interp: map key variant: " + v.keyVariant)
}
hash := v.hash(keyBuf)
@@ -300,7 +298,7 @@ func (v *MapValue) Value() llvm.Value {
// Type returns type runtime.hashmap, which is the actual hashmap type.
func (v *MapValue) Type() llvm.Type {
return v.Eval.Mod.GetTypeByName("runtime.hashmap")
return v.MapType
}
func (v *MapValue) IsConstant() bool {
@@ -308,15 +306,13 @@ func (v *MapValue) IsConstant() bool {
}
// Load panics: maps are of reference type so cannot be dereferenced.
func (v *MapValue) Load() (llvm.Value, error) {
func (v *MapValue) Load() llvm.Value {
panic("interp: load from a map")
}
// Store returns an error: maps are of reference type so cannot be stored to.
func (v *MapValue) Store(value llvm.Value) error {
// This must be a bug, but it might be helpful to indicate the location
// anyway.
return errors.New("interp: store on a map")
// Store panics: maps are of reference type so cannot be stored to.
func (v *MapValue) Store(value llvm.Value) {
panic("interp: store on a map")
}
// GetElementPtr panics: maps are of reference type so their (interior)
@@ -325,65 +321,80 @@ func (v *MapValue) GetElementPtr(indices []uint32) (Value, error) {
return nil, errors.New("interp: GEP on a map")
}
// setKeyVariant sets the key variant as a result of storing to the hashmap
// (string, binary, or interface). The way that TinyGo is structured, the key
// variant is not known until there is a store to the hashmap.
// The key variant has to be known when lowering the hashmap to its final form,
// to correctly calculate the hash of a key (for example, a string key must
// calculate the hash over the string contents).
func (v *MapValue) setKeyVariant(keyVariant string) {
if v.keyVariant == "" {
v.keyVariant = keyVariant
return
}
if v.keyVariant != keyVariant {
// Valid IR will not cause this panic to occur.
panic("MapValue store with inconsistent key type")
}
}
// PutString does a map assign operation, assuming that the map is of type
// map[string]T.
func (v *MapValue) PutString(keyBuf, keyLen, valPtr *LocalValue) error {
func (v *MapValue) PutString(keyBuf, keyLen, valPtr *LocalValue) {
if !v.Underlying.IsNil() {
return errors.New("map already created")
panic("map already created")
}
v.setKeyVariant("string")
if valPtr.Underlying.Opcode() == llvm.BitCast {
valPtr = &LocalValue{v.Eval, valPtr.Underlying.Operand(0)}
}
value, err := valPtr.Load()
if err != nil {
return err
}
value := valPtr.Load()
if v.ValueType.IsNil() {
v.ValueType = value.Type()
if int(v.Eval.TargetData.TypeAllocSize(v.ValueType)) != v.ValueSize {
return errors.New("interp: map store value type has the wrong size")
panic("interp: map store value type has the wrong size")
}
} else {
if value.Type() != v.ValueType {
return errors.New("interp: map store value type is inconsistent")
panic("interp: map store value type is inconsistent")
}
}
keyType := v.Eval.Mod.GetTypeByName("runtime._string")
v.KeyType = keyType
key := llvm.ConstNull(keyType)
if v.KeyType.IsNil() {
v.KeyType = v.Eval.Mod.Context().StructType([]llvm.Type{
keyBuf.Type(),
keyLen.Type(),
}, false)
}
key := llvm.ConstNull(v.KeyType)
key = llvm.ConstInsertValue(key, keyBuf.Value(), []uint32{0})
key = llvm.ConstInsertValue(key, keyLen.Value(), []uint32{1})
// TODO: avoid duplicate keys
v.Keys = append(v.Keys, &LocalValue{v.Eval, key})
v.Values = append(v.Values, &LocalValue{v.Eval, value})
return nil
}
// PutBinary does a map assign operation.
func (v *MapValue) PutBinary(keyPtr, valPtr *LocalValue) error {
func (v *MapValue) PutBinary(keyPtr, valPtr *LocalValue) {
if !v.Underlying.IsNil() {
return errors.New("map already created")
panic("map already created")
}
v.setKeyVariant("binary")
if valPtr.Underlying.Opcode() == llvm.BitCast {
valPtr = &LocalValue{v.Eval, valPtr.Underlying.Operand(0)}
}
value, err := valPtr.Load()
if err != nil {
return err
}
value := valPtr.Load()
if v.ValueType.IsNil() {
v.ValueType = value.Type()
if int(v.Eval.TargetData.TypeAllocSize(v.ValueType)) != v.ValueSize {
return errors.New("interp: map store value type has the wrong size")
panic("interp: map store value type has the wrong size")
}
} else {
if value.Type() != v.ValueType {
return errors.New("interp: map store value type is inconsistent")
panic("interp: map store value type is inconsistent")
}
}
@@ -394,26 +405,21 @@ func (v *MapValue) PutBinary(keyPtr, valPtr *LocalValue) error {
keyPtr = &LocalValue{v.Eval, keyPtr.Underlying.Operand(0)}
}
}
key, err := keyPtr.Load()
if err != nil {
return err
}
key := keyPtr.Load()
if v.KeyType.IsNil() {
v.KeyType = key.Type()
if int(v.Eval.TargetData.TypeAllocSize(v.KeyType)) != v.KeySize {
return errors.New("interp: map store key type has the wrong size")
panic("interp: map store key type has the wrong size")
}
} else {
if key.Type() != v.KeyType {
return errors.New("interp: map store key type is inconsistent")
panic("interp: map store key type is inconsistent")
}
}
// TODO: avoid duplicate keys
v.Keys = append(v.Keys, &LocalValue{v.Eval, key})
v.Values = append(v.Values, &LocalValue{v.Eval, value})
return nil
}
// Get FNV-1a hash of this string.
-271
View File
@@ -1,271 +0,0 @@
package ir
import (
"go/ast"
"go/types"
"sort"
"strings"
"github.com/tinygo-org/tinygo/loader"
"golang.org/x/tools/go/ssa"
"tinygo.org/x/go-llvm"
)
// This file provides a wrapper around go/ssa values and adds extra
// functionality to them.
// View on all functions, types, and globals in a program, with analysis
// results.
type Program struct {
Program *ssa.Program
LoaderProgram *loader.Program
mainPkg *ssa.Package
Functions []*Function
functionMap map[*ssa.Function]*Function
}
// Function or method.
type Function struct {
*ssa.Function
LLVMFn llvm.Value
module string // go:wasm-module
linkName string // go:linkname, go:export
exported bool // go:export
nobounds bool // go:nobounds
flag bool // used by dead code elimination
inline InlineType // go:inline
}
// Interface type that is at some point used in a type assert (to check whether
// it implements another interface).
type Interface struct {
Num int
Type *types.Interface
}
type InlineType int
// How much to inline.
const (
// Default behavior. The compiler decides for itself whether any given
// function will be inlined. Whether any function is inlined depends on the
// optimization level.
InlineDefault InlineType = iota
// Inline hint, just like the C inline keyword (signalled using
// //go:inline). The compiler will be more likely to inline this function,
// but it is not a guarantee.
InlineHint
// Don't inline, just like the GCC noinline attribute. Signalled using
// //go:noinline.
InlineNone
)
// Create and initialize a new *Program from a *ssa.Program.
func NewProgram(lprogram *loader.Program) *Program {
program := lprogram.LoadSSA()
program.Build()
mainPkg := program.ImportedPackage(lprogram.MainPkg().ImportPath)
if mainPkg == nil {
panic("could not find main package")
}
p := &Program{
Program: program,
LoaderProgram: lprogram,
mainPkg: mainPkg,
functionMap: make(map[*ssa.Function]*Function),
}
for _, pkg := range lprogram.Sorted() {
p.AddPackage(program.ImportedPackage(pkg.ImportPath))
}
return p
}
// Add a package to this Program. All packages need to be added first before any
// analysis is done for correct results.
func (p *Program) AddPackage(pkg *ssa.Package) {
memberNames := make([]string, 0)
for name := range pkg.Members {
memberNames = append(memberNames, name)
}
sort.Strings(memberNames)
for _, name := range memberNames {
member := pkg.Members[name]
switch member := member.(type) {
case *ssa.Function:
p.addFunction(member)
case *ssa.Type:
methods := getAllMethods(pkg.Prog, member.Type())
if !types.IsInterface(member.Type()) {
// named type
for _, method := range methods {
p.addFunction(pkg.Prog.MethodValue(method))
}
}
case *ssa.Global:
// Ignore. Globals are not handled here.
case *ssa.NamedConst:
// Ignore: these are already resolved.
default:
panic("unknown member type: " + member.String())
}
}
}
func (p *Program) addFunction(ssaFn *ssa.Function) {
if _, ok := p.functionMap[ssaFn]; ok {
return
}
f := &Function{Function: ssaFn}
f.parsePragmas()
p.Functions = append(p.Functions, f)
p.functionMap[ssaFn] = f
for _, anon := range ssaFn.AnonFuncs {
p.addFunction(anon)
}
}
// Return true if this package imports "unsafe", false otherwise.
func hasUnsafeImport(pkg *types.Package) bool {
for _, imp := range pkg.Imports() {
if imp == types.Unsafe {
return true
}
}
return false
}
func (p *Program) GetFunction(ssaFn *ssa.Function) *Function {
return p.functionMap[ssaFn]
}
func (p *Program) MainPkg() *ssa.Package {
return p.mainPkg
}
// Parse compiler directives in the preceding comments.
func (f *Function) parsePragmas() {
if f.Syntax() == nil {
return
}
if decl, ok := f.Syntax().(*ast.FuncDecl); ok && decl.Doc != nil {
for _, comment := range decl.Doc.List {
text := comment.Text
if strings.HasPrefix(text, "//export ") {
// Rewrite '//export' to '//go:export' for compatibility with
// gc.
text = "//go:" + text[2:]
}
if !strings.HasPrefix(text, "//go:") {
continue
}
parts := strings.Fields(text)
switch parts[0] {
case "//go:export":
if len(parts) != 2 {
continue
}
f.linkName = parts[1]
f.exported = true
case "//go:wasm-module":
// Alternative comment for setting the import module.
if len(parts) != 2 {
continue
}
f.module = parts[1]
case "//go:inline":
f.inline = InlineHint
case "//go:noinline":
f.inline = InlineNone
case "//go:linkname":
if len(parts) != 3 || parts[1] != f.Name() {
continue
}
// Only enable go:linkname when the package imports "unsafe".
// This is a slightly looser requirement than what gc uses: gc
// requires the file to import "unsafe", not the package as a
// whole.
if hasUnsafeImport(f.Pkg.Pkg) {
f.linkName = parts[2]
}
case "//go:nobounds":
// Skip bounds checking in this function. Useful for some
// runtime functions.
// This is somewhat dangerous and thus only imported in packages
// that import unsafe.
if hasUnsafeImport(f.Pkg.Pkg) {
f.nobounds = true
}
}
}
}
}
func (f *Function) IsNoBounds() bool {
return f.nobounds
}
// Return true iff this function is externally visible.
func (f *Function) IsExported() bool {
return f.exported || f.CName() != ""
}
// Return the inline directive of this function.
func (f *Function) Inline() InlineType {
return f.inline
}
// Return the module name if not the default.
func (f *Function) Module() string {
return f.module
}
// Return the link name for this function.
func (f *Function) LinkName() string {
if f.linkName != "" {
return f.linkName
}
if f.Signature.Recv() != nil {
// Method on a defined type (which may be a pointer).
return f.RelString(nil)
} else {
// Bare function.
if name := f.CName(); name != "" {
// Name CGo functions directly.
return name
} else {
return f.RelString(nil)
}
}
}
// Return the name of the C function if this is a CGo wrapper. Otherwise, return
// a zero-length string.
func (f *Function) CName() string {
name := f.Name()
if strings.HasPrefix(name, "_Cfunc_") {
// emitted by `go tool cgo`
return name[len("_Cfunc_"):]
}
if strings.HasPrefix(name, "C.") {
// created by ../loader/cgo.go
return name[2:]
}
return ""
}
// Get all methods of a type.
func getAllMethods(prog *ssa.Program, typ types.Type) []*types.Selection {
ms := prog.MethodSets.MethodSet(typ)
methods := make([]*types.Selection, ms.Len())
for i := 0; i < ms.Len(); i++ {
methods[i] = ms.At(i)
}
return methods
}
-149
View File
@@ -1,149 +0,0 @@
package ir
import (
"errors"
"go/types"
"golang.org/x/tools/go/ssa"
)
// This file implements several optimization passes (analysis + transform) to
// optimize code in SSA form before it is compiled to LLVM IR. It is based on
// the IR defined in ir.go.
// Make a readable version of a method signature (including the function name,
// excluding the receiver name). This string is used internally to match
// interfaces and to call the correct method on an interface. Examples:
//
// String() string
// Read([]byte) (int, error)
func MethodSignature(method *types.Func) string {
return method.Name() + signature(method.Type().(*types.Signature))
}
// Make a readable version of a function (pointer) signature.
// Examples:
//
// () string
// (string, int) (int, error)
func signature(sig *types.Signature) string {
s := ""
if sig.Params().Len() == 0 {
s += "()"
} else {
s += "("
for i := 0; i < sig.Params().Len(); i++ {
if i > 0 {
s += ", "
}
s += sig.Params().At(i).Type().String()
}
s += ")"
}
if sig.Results().Len() == 0 {
// keep as-is
} else if sig.Results().Len() == 1 {
s += " " + sig.Results().At(0).Type().String()
} else {
s += " ("
for i := 0; i < sig.Results().Len(); i++ {
if i > 0 {
s += ", "
}
s += sig.Results().At(i).Type().String()
}
s += ")"
}
return s
}
// Simple pass that removes dead code. This pass makes later analysis passes
// more useful.
func (p *Program) SimpleDCE() error {
// Unmark all functions.
for _, f := range p.Functions {
f.flag = false
}
// Initial set of live functions. Include main.main, *.init and runtime.*
// functions.
main, ok := p.mainPkg.Members["main"].(*ssa.Function)
if !ok {
if p.mainPkg.Members["main"] == nil {
return errors.New("function main is undeclared in the main package")
} else {
return errors.New("cannot declare main - must be func")
}
}
runtimePkg := p.Program.ImportedPackage("runtime")
mathPkg := p.Program.ImportedPackage("math")
taskPkg := p.Program.ImportedPackage("internal/task")
p.GetFunction(main).flag = true
worklist := []*ssa.Function{main}
for _, f := range p.Functions {
if f.exported || f.Synthetic == "package initializer" || f.Pkg == runtimePkg || f.Pkg == taskPkg || (f.Pkg == mathPkg && f.Pkg != nil) {
if f.flag {
continue
}
f.flag = true
worklist = append(worklist, f.Function)
}
}
// Mark all called functions recursively.
for len(worklist) != 0 {
f := worklist[len(worklist)-1]
worklist = worklist[:len(worklist)-1]
for _, block := range f.Blocks {
for _, instr := range block.Instrs {
if instr, ok := instr.(*ssa.MakeInterface); ok {
for _, sel := range getAllMethods(p.Program, instr.X.Type()) {
fn := p.Program.MethodValue(sel)
callee := p.GetFunction(fn)
if callee == nil {
// TODO: why is this necessary?
p.addFunction(fn)
callee = p.GetFunction(fn)
}
if !callee.flag {
callee.flag = true
worklist = append(worklist, callee.Function)
}
}
}
for _, operand := range instr.Operands(nil) {
if operand == nil || *operand == nil {
continue
}
switch operand := (*operand).(type) {
case *ssa.Function:
f := p.GetFunction(operand)
if f == nil {
// FIXME HACK: this function should have been
// discovered already. It is not for bound methods.
p.addFunction(operand)
f = p.GetFunction(operand)
}
if !f.flag {
f.flag = true
worklist = append(worklist, operand)
}
}
}
}
}
}
// Remove unmarked functions.
livefunctions := []*Function{}
for _, f := range p.Functions {
if f.flag {
livefunctions = append(livefunctions, f)
} else {
delete(p.functionMap, f.Function)
}
}
p.Functions = livefunctions
return nil
}
+22 -8
View File
@@ -1,6 +1,9 @@
package loader
import "go/scanner"
import (
"go/token"
"strings"
)
// Errors contains a list of parser errors or a list of typechecker errors for
// the given package.
@@ -13,13 +16,24 @@ func (e Errors) Error() string {
return "could not compile: " + e.Errs[0].Error()
}
// Error is a regular error but with an added import stack. This is especially
// useful for debugging import cycle errors.
type Error struct {
ImportStack []string
Err scanner.Error
// ImportCycleErrors is returned when encountering an import cycle. The list of
// packages is a list from the root package to the leaf package that imports one
// of the packages in the list.
type ImportCycleError struct {
Packages []string
ImportPositions []token.Position
}
func (e Error) Error() string {
return e.Err.Error()
func (e *ImportCycleError) Error() string {
var msg strings.Builder
msg.WriteString("import cycle:\n\t")
msg.WriteString(strings.Join(e.Packages, "\n\t"))
msg.WriteString("\n at ")
for i, pos := range e.ImportPositions {
if i > 0 {
msg.WriteString(", ")
}
msg.WriteString(pos.String())
}
return msg.String()
}
-281
View File
@@ -1,281 +0,0 @@
package loader
// This file constructs a new temporary GOROOT directory by merging both the
// standard Go GOROOT and the GOROOT from TinyGo using symlinks.
import (
"crypto/sha512"
"encoding/hex"
"errors"
"fmt"
"io"
"io/ioutil"
"os"
"os/exec"
"path"
"path/filepath"
"runtime"
"sync"
"github.com/tinygo-org/tinygo/compileopts"
"github.com/tinygo-org/tinygo/goenv"
)
var gorootCreateMutex sync.Mutex
// GetCachedGoroot creates a new GOROOT by merging both the standard GOROOT and
// the GOROOT from TinyGo using lots of symbolic links.
func GetCachedGoroot(config *compileopts.Config) (string, error) {
goroot := goenv.Get("GOROOT")
if goroot == "" {
return "", errors.New("could not determine GOROOT")
}
tinygoroot := goenv.Get("TINYGOROOT")
if tinygoroot == "" {
return "", errors.New("could not determine TINYGOROOT")
}
// Determine the location of the cached GOROOT.
version, err := goenv.GorootVersionString(goroot)
if err != nil {
return "", err
}
// This hash is really a cache key, that contains (hopefully) enough
// information to make collisions unlikely during development.
// By including the Go version and TinyGo version, cache collisions should
// not happen outside of development.
hash := sha512.New512_256()
fmt.Fprintln(hash, goroot)
fmt.Fprintln(hash, version)
fmt.Fprintln(hash, goenv.Version)
fmt.Fprintln(hash, tinygoroot)
gorootsHash := hash.Sum(nil)
gorootsHashHex := hex.EncodeToString(gorootsHash[:])
cachedgorootName := "goroot-" + version + "-" + gorootsHashHex
cachedgoroot := filepath.Join(goenv.Get("GOCACHE"), cachedgorootName)
if needsSyscallPackage(config.BuildTags()) {
cachedgoroot += "-syscall"
}
// Do not try to create the cached GOROOT in parallel, that's only a waste
// of I/O bandwidth and thus speed. Instead, use a mutex to make sure only
// one goroutine does it at a time.
// This is not a way to ensure atomicity (a different TinyGo invocation
// could be creating the same directory), but instead a way to avoid
// creating it many times in parallel when running tests in parallel.
gorootCreateMutex.Lock()
defer gorootCreateMutex.Unlock()
if _, err := os.Stat(cachedgoroot); err == nil {
return cachedgoroot, nil
}
err = os.MkdirAll(goenv.Get("GOCACHE"), 0777)
if err != nil {
return "", err
}
tmpgoroot, err := ioutil.TempDir(goenv.Get("GOCACHE"), cachedgorootName+".tmp")
if err != nil {
return "", err
}
// Remove the temporary directory if it wasn't moved to the right place
// (for example, when there was an error).
defer os.RemoveAll(tmpgoroot)
for _, name := range []string{"bin", "lib", "pkg"} {
err = symlink(filepath.Join(goroot, name), filepath.Join(tmpgoroot, name))
if err != nil {
return "", err
}
}
err = mergeDirectory(goroot, tinygoroot, tmpgoroot, "", pathsToOverride(needsSyscallPackage(config.BuildTags())))
if err != nil {
return "", err
}
err = os.Rename(tmpgoroot, cachedgoroot)
if err != nil {
if os.IsExist(err) {
// Another invocation of TinyGo also seems to have created a GOROOT.
// Use that one instead. Our new GOROOT will be automatically
// deleted by the defer above.
return cachedgoroot, nil
}
if runtime.GOOS == "windows" && os.IsPermission(err) {
// On Windows, a rename with a destination directory that already
// exists does not result in an IsExist error, but rather in an
// access denied error. To be sure, check for this case by checking
// whether the target directory exists.
if _, err := os.Stat(cachedgoroot); err == nil {
return cachedgoroot, nil
}
}
return "", err
}
return cachedgoroot, nil
}
// mergeDirectory merges two roots recursively. The tmpgoroot is the directory
// that will be created by this call by either symlinking the directory from
// goroot or tinygoroot, or by creating the directory and merging the contents.
func mergeDirectory(goroot, tinygoroot, tmpgoroot, importPath string, overrides map[string]bool) error {
if mergeSubdirs, ok := overrides[importPath+"/"]; ok {
if !mergeSubdirs {
// This directory and all subdirectories should come from the TinyGo
// root, so simply make a symlink.
newname := filepath.Join(tmpgoroot, "src", importPath)
oldname := filepath.Join(tinygoroot, "src", importPath)
return symlink(oldname, newname)
}
// Merge subdirectories. Start by making the directory to merge.
err := os.Mkdir(filepath.Join(tmpgoroot, "src", importPath), 0777)
if err != nil {
return err
}
// Symlink all files from TinyGo, and symlink directories from TinyGo
// that need to be overridden.
tinygoEntries, err := ioutil.ReadDir(filepath.Join(tinygoroot, "src", importPath))
if err != nil {
return err
}
for _, e := range tinygoEntries {
if e.IsDir() {
// A directory, so merge this thing.
err := mergeDirectory(goroot, tinygoroot, tmpgoroot, path.Join(importPath, e.Name()), overrides)
if err != nil {
return err
}
} else {
// A file, so symlink this.
newname := filepath.Join(tmpgoroot, "src", importPath, e.Name())
oldname := filepath.Join(tinygoroot, "src", importPath, e.Name())
err := symlink(oldname, newname)
if err != nil {
return err
}
}
}
// Symlink all directories from $GOROOT that are not part of the TinyGo
// overrides.
gorootEntries, err := ioutil.ReadDir(filepath.Join(goroot, "src", importPath))
if err != nil {
return err
}
for _, e := range gorootEntries {
if !e.IsDir() {
// Don't merge in files from Go. Otherwise we'd end up with a
// weird syscall package with files from both roots.
continue
}
if _, ok := overrides[path.Join(importPath, e.Name())+"/"]; ok {
// Already included above, so don't bother trying to create this
// symlink.
continue
}
newname := filepath.Join(tmpgoroot, "src", importPath, e.Name())
oldname := filepath.Join(goroot, "src", importPath, e.Name())
err := symlink(oldname, newname)
if err != nil {
return err
}
}
}
return nil
}
// needsSyscallPackage returns whether the syscall package should be overriden
// with the TinyGo version. This is the case on some targets.
func needsSyscallPackage(buildTags []string) bool {
for _, tag := range buildTags {
if tag == "baremetal" || tag == "darwin" || tag == "nintendoswitch" {
return true
}
}
return false
}
// The boolean indicates whether to merge the subdirs. True means merge, false
// means use the TinyGo version.
func pathsToOverride(needsSyscallPackage bool) map[string]bool {
paths := map[string]bool{
"/": true,
"device/": false,
"examples/": false,
"internal/": true,
"internal/bytealg/": false,
"internal/reflectlite/": false,
"internal/task/": false,
"machine/": false,
"os/": true,
"reflect/": false,
"runtime/": false,
"sync/": true,
"testing/": true,
}
if needsSyscallPackage {
paths["syscall/"] = true // include syscall/js
}
return paths
}
// symlink creates a symlink or something similar. On Unix-like systems, it
// always creates a symlink. On Windows, it tries to create a symlink and if
// that fails, creates a hardlink or directory junction instead.
//
// Note that while Windows 10 does support symlinks and allows them to be
// created using os.Symlink, it requires developer mode to be enabled.
// Therefore provide a fallback for when symlinking is not possible.
// Unfortunately this fallback only works when TinyGo is installed on the same
// filesystem as the TinyGo cache and the Go installation (which is usually the
// C drive).
func symlink(oldname, newname string) error {
symlinkErr := os.Symlink(oldname, newname)
if runtime.GOOS == "windows" && symlinkErr != nil {
// Fallback for when developer mode is disabled.
// Note that we return the symlink error even if something else fails
// later on. This is because symlinks are the easiest to support
// (they're also used on Linux and MacOS) and enabling them is easy:
// just enable developer mode.
st, err := os.Stat(oldname)
if err != nil {
return symlinkErr
}
if st.IsDir() {
// Make a directory junction. There may be a way to do this
// programmatically, but it involves a lot of magic. Use the mklink
// command built into cmd instead (mklink is a builtin, not an
// external command).
err := exec.Command("cmd", "/k", "mklink", "/J", newname, oldname).Run()
if err != nil {
return symlinkErr
}
} else {
// Try making a hard link.
err := os.Link(oldname, newname)
if err != nil {
// Making a hardlink failed. Try copying the file as a last
// fallback.
inf, err := os.Open(oldname)
if err != nil {
return err
}
defer inf.Close()
outf, err := os.Create(newname)
if err != nil {
return err
}
defer outf.Close()
_, err = io.Copy(outf, inf)
if err != nil {
os.Remove(newname)
return err
}
// File was copied.
}
}
return nil // success
}
return symlinkErr
}
-30
View File
@@ -1,30 +0,0 @@
package loader
import (
"os"
"os/exec"
"strings"
"github.com/tinygo-org/tinygo/compileopts"
)
// List returns a ready-to-run *exec.Cmd for running the `go list` command with
// the configuration used for TinyGo.
func List(config *compileopts.Config, extraArgs, pkgs []string) (*exec.Cmd, error) {
goroot, err := GetCachedGoroot(config)
if err != nil {
return nil, err
}
args := append([]string{"list"}, extraArgs...)
if len(config.BuildTags()) != 0 {
args = append(args, "-tags", strings.Join(config.BuildTags(), " "))
}
args = append(args, pkgs...)
cgoEnabled := "0"
if config.CgoEnabled() {
cgoEnabled = "1"
}
cmd := exec.Command("go", args...)
cmd.Env = append(os.Environ(), "GOROOT="+goroot, "GOOS="+config.GOOS(), "GOARCH="+config.GOARCH(), "CGO_ENABLED="+cgoEnabled)
return cmd, nil
}
+345 -223
View File
@@ -2,239 +2,226 @@ package loader
import (
"bytes"
"encoding/json"
"errors"
"fmt"
"go/ast"
"go/build"
"go/parser"
"go/scanner"
"go/token"
"go/types"
"io"
"os"
"os/exec"
"path/filepath"
"strconv"
"sort"
"strings"
"syscall"
"text/template"
"github.com/tinygo-org/tinygo/cgo"
"github.com/tinygo-org/tinygo/compileopts"
"github.com/tinygo-org/tinygo/goenv"
)
// Program holds all packages and some metadata about the program as a whole.
type Program struct {
config *compileopts.Config
clangHeaders string
typeChecker types.Config
goroot string // synthetic GOROOT
workingDir string
Packages map[string]*Package
sorted []*Package
fset *token.FileSet
// Information obtained during parsing.
LDFlags []string
}
// PackageJSON is a subset of the JSON struct returned from `go list`.
type PackageJSON struct {
Dir string
ImportPath string
ForTest string
// Source files
GoFiles []string
CgoFiles []string
CFiles []string
// Dependency information
Imports []string
ImportMap map[string]string
// Error information
Error *struct {
ImportStack []string
Pos string
Err string
}
mainPkg string
Build *build.Context
OverlayBuild *build.Context
OverlayPath func(path string) string
Packages map[string]*Package
sorted []*Package
fset *token.FileSet
TypeChecker types.Config
Dir string // current working directory (for error reporting)
TINYGOROOT string // root of the TinyGo installation or root of the source code
CFlags []string
ClangHeaders string
}
// Package holds a loaded package, its imports, and its parsed files.
type Package struct {
PackageJSON
program *Program
Files []*ast.File
Pkg *types.Package
info types.Info
*Program
*build.Package
Imports map[string]*Package
Importing bool
Files []*ast.File
Pkg *types.Package
types.Info
}
// Load loads the given package with all dependencies (including the runtime
// package). Call .Parse() afterwards to parse all Go files (including CGo
// processing, if necessary).
func Load(config *compileopts.Config, inputPkgs []string, clangHeaders string, typeChecker types.Config) (*Program, error) {
goroot, err := GetCachedGoroot(config)
if err != nil {
return nil, err
}
wd, err := os.Getwd()
if err != nil {
return nil, err
}
p := &Program{
config: config,
clangHeaders: clangHeaders,
typeChecker: typeChecker,
goroot: goroot,
workingDir: wd,
Packages: make(map[string]*Package),
fset: token.NewFileSet(),
// Import loads the given package relative to srcDir (for the vendor directory).
// It only loads the current package without recursion.
func (p *Program) Import(path, srcDir string, pos token.Position) (*Package, error) {
if p.Packages == nil {
p.Packages = make(map[string]*Package)
}
// List the dependencies of this package, in raw JSON format.
extraArgs := []string{"-json", "-deps"}
if config.TestConfig.CompileTestBinary {
extraArgs = append(extraArgs, "-test")
// Load this package.
ctx := p.Build
if newPath := p.OverlayPath(path); newPath != "" {
ctx = p.OverlayBuild
path = newPath
}
cmd, err := List(config, extraArgs, inputPkgs)
buildPkg, err := ctx.Import(path, srcDir, build.ImportComment)
if err != nil {
return nil, err
}
buf := &bytes.Buffer{}
cmd.Stdout = buf
cmd.Stderr = os.Stderr
err = cmd.Run()
if err != nil {
if exitErr, ok := err.(*exec.ExitError); ok {
if status, ok := exitErr.Sys().(syscall.WaitStatus); ok {
os.Exit(status.ExitStatus())
}
os.Exit(1)
return nil, scanner.Error{
Pos: pos,
Msg: err.Error(), // TODO: define a new error type that will wrap the inner error
}
return nil, fmt.Errorf("failed to run `go list`: %s", err)
}
if existingPkg, ok := p.Packages[buildPkg.ImportPath]; ok {
// Already imported, or at least started the import.
return existingPkg, nil
}
p.sorted = nil // invalidate the sorted order of packages
pkg := p.newPackage(buildPkg)
p.Packages[buildPkg.ImportPath] = pkg
if p.mainPkg == "" {
p.mainPkg = buildPkg.ImportPath
}
// Parse the returned json from `go list`.
decoder := json.NewDecoder(buf)
for {
pkg := &Package{
program: p,
info: types.Info{
Types: make(map[ast.Expr]types.TypeAndValue),
Defs: make(map[*ast.Ident]types.Object),
Uses: make(map[*ast.Ident]types.Object),
Implicits: make(map[ast.Node]types.Object),
Scopes: make(map[ast.Node]*types.Scope),
Selections: make(map[*ast.SelectorExpr]*types.Selection),
},
}
err := decoder.Decode(&pkg.PackageJSON)
if err != nil {
if err == io.EOF {
break
}
return nil, err
}
if pkg.Error != nil {
// There was an error while importing (for example, a circular
// dependency).
pos := token.Position{}
fields := strings.Split(pkg.Error.Pos, ":")
if len(fields) >= 2 {
// There is some file/line/column information.
if n, err := strconv.Atoi(fields[len(fields)-2]); err == nil {
// Format: filename.go:line:colum
pos.Filename = strings.Join(fields[:len(fields)-2], ":")
pos.Line = n
pos.Column, _ = strconv.Atoi(fields[len(fields)-1])
} else {
// Format: filename.go:line
pos.Filename = strings.Join(fields[:len(fields)-1], ":")
pos.Line, _ = strconv.Atoi(fields[len(fields)-1])
}
pos.Filename = p.getOriginalPath(pos.Filename)
}
err := scanner.Error{
Pos: pos,
Msg: pkg.Error.Err,
}
if len(pkg.Error.ImportStack) != 0 {
return nil, Error{
ImportStack: pkg.Error.ImportStack,
Err: err,
}
}
return nil, err
}
p.sorted = append(p.sorted, pkg)
p.Packages[pkg.ImportPath] = pkg
}
return p, nil
return pkg, nil
}
// getOriginalPath looks whether this path is in the generated GOROOT and if so,
// replaces the path with the original path (in GOROOT or TINYGOROOT). Otherwise
// the input path is returned.
func (p *Program) getOriginalPath(path string) string {
originalPath := path
if strings.HasPrefix(path, p.goroot+string(filepath.Separator)) {
// If this file is part of the synthetic GOROOT, try to infer the
// original path.
relpath := path[len(filepath.Join(p.goroot, "src"))+1:]
realgorootPath := filepath.Join(goenv.Get("GOROOT"), "src", relpath)
if _, err := os.Stat(realgorootPath); err == nil {
originalPath = realgorootPath
}
maybeInTinyGoRoot := false
for prefix := range pathsToOverride(needsSyscallPackage(p.config.BuildTags())) {
if !strings.HasPrefix(relpath, prefix) {
continue
}
maybeInTinyGoRoot = true
}
if maybeInTinyGoRoot {
tinygoPath := filepath.Join(goenv.Get("TINYGOROOT"), "src", relpath)
if _, err := os.Stat(tinygoPath); err == nil {
originalPath = tinygoPath
}
}
// ImportFile loads and parses the import statements in the given path and
// creates a pseudo-package out of it.
func (p *Program) ImportFile(path string) (*Package, error) {
if p.Packages == nil {
p.Packages = make(map[string]*Package)
}
if _, ok := p.Packages[path]; ok {
// unlikely
return nil, errors.New("loader: cannot import file that is already imported as package: " + path)
}
file, err := p.parseFile(path, parser.ImportsOnly)
if err != nil {
return nil, err
}
buildPkg := &build.Package{
Dir: filepath.Dir(path),
ImportPath: path,
GoFiles: []string{filepath.Base(path)},
}
for _, importSpec := range file.Imports {
buildPkg.Imports = append(buildPkg.Imports, importSpec.Path.Value[1:len(importSpec.Path.Value)-1])
}
p.sorted = nil // invalidate the sorted order of packages
pkg := p.newPackage(buildPkg)
p.Packages[buildPkg.ImportPath] = pkg
if p.mainPkg == "" {
p.mainPkg = buildPkg.ImportPath
}
return pkg, nil
}
// newPackage instantiates a new *Package object with initialized members.
func (p *Program) newPackage(pkg *build.Package) *Package {
return &Package{
Program: p,
Package: pkg,
Imports: make(map[string]*Package, len(pkg.Imports)),
Info: types.Info{
Types: make(map[ast.Expr]types.TypeAndValue),
Defs: make(map[*ast.Ident]types.Object),
Uses: make(map[*ast.Ident]types.Object),
Implicits: make(map[ast.Node]types.Object),
Scopes: make(map[ast.Node]*types.Scope),
Selections: make(map[*ast.SelectorExpr]*types.Selection),
},
}
return originalPath
}
// Sorted returns a list of all packages, sorted in a way that no packages come
// before the packages they depend upon.
func (p *Program) Sorted() []*Package {
if p.sorted == nil {
p.sort()
}
return p.sorted
}
// MainPkg returns the last package in the Sorted() slice. This is the main
// package of the program.
func (p *Program) MainPkg() *Package {
return p.sorted[len(p.sorted)-1]
func (p *Program) sort() {
p.sorted = nil
packageList := make([]*Package, 0, len(p.Packages))
packageSet := make(map[string]struct{}, len(p.Packages))
worklist := make([]string, 0, len(p.Packages))
for path := range p.Packages {
worklist = append(worklist, path)
}
sort.Strings(worklist)
for len(worklist) != 0 {
pkgPath := worklist[0]
pkg := p.Packages[pkgPath]
if _, ok := packageSet[pkgPath]; ok {
// Package already in the final package list.
worklist = worklist[1:]
continue
}
unsatisfiedImports := make([]string, 0)
for _, pkg := range pkg.Imports {
if _, ok := packageSet[pkg.ImportPath]; ok {
continue
}
unsatisfiedImports = append(unsatisfiedImports, pkg.ImportPath)
}
sort.Strings(unsatisfiedImports)
if len(unsatisfiedImports) == 0 {
// All dependencies of this package are satisfied, so add this
// package to the list.
packageList = append(packageList, pkg)
packageSet[pkgPath] = struct{}{}
worklist = worklist[1:]
} else {
// Prepend all dependencies to the worklist and reconsider this
// package (by not removing it from the worklist). At that point, it
// must be possible to add it to packageList.
worklist = append(unsatisfiedImports, worklist...)
}
}
p.sorted = packageList
}
// Parse parses all packages and typechecks them.
// Parse recursively imports all packages, parses them, and typechecks them.
//
// The returned error may be an Errors error, which contains a list of errors.
//
// Idempotent.
func (p *Program) Parse() error {
func (p *Program) Parse(compileTestBinary bool) error {
includeTests := compileTestBinary
// Load all imports
for _, pkg := range p.Sorted() {
err := pkg.importRecursively(includeTests)
if err != nil {
if err, ok := err.(*ImportCycleError); ok {
if pkg.ImportPath != err.Packages[0] {
err.Packages = append([]string{pkg.ImportPath}, err.Packages...)
}
}
return err
}
}
// Parse all packages.
// TODO: do this in parallel.
for _, pkg := range p.sorted {
err := pkg.Parse()
for _, pkg := range p.Sorted() {
err := pkg.Parse(includeTests)
if err != nil {
return err
}
}
if compileTestBinary {
err := p.SwapTestMain()
if err != nil {
return err
}
}
// Typecheck all packages.
for _, pkg := range p.sorted {
for _, pkg := range p.Sorted() {
err := pkg.Check()
if err != nil {
return err
@@ -244,6 +231,83 @@ func (p *Program) Parse() error {
return nil
}
func (p *Program) SwapTestMain() error {
var tests []string
isTestFunc := func(f *ast.FuncDecl) bool {
// TODO: improve signature check
if strings.HasPrefix(f.Name.Name, "Test") && f.Name.Name != "TestMain" {
return true
}
return false
}
mainPkg := p.Packages[p.mainPkg]
for _, f := range mainPkg.Files {
for i, d := range f.Decls {
switch v := d.(type) {
case *ast.FuncDecl:
if isTestFunc(v) {
tests = append(tests, v.Name.Name)
}
if v.Name.Name == "main" {
// Remove main
if len(f.Decls) == 1 {
f.Decls = make([]ast.Decl, 0)
} else {
f.Decls[i] = f.Decls[len(f.Decls)-1]
f.Decls = f.Decls[:len(f.Decls)-1]
}
}
}
}
}
// TODO: Check if they defined a TestMain and call it instead of testing.TestMain
const mainBody = `package main
import (
"testing"
)
func main () {
m := &testing.M{
Tests: []testing.TestToCall{
{{range .TestFunctions}}
{Name: "{{.}}", Func: {{.}}},
{{end}}
},
}
testing.TestMain(m)
}
`
tmpl := template.Must(template.New("testmain").Parse(mainBody))
b := bytes.Buffer{}
tmplData := struct {
TestFunctions []string
}{
TestFunctions: tests,
}
err := tmpl.Execute(&b, tmplData)
if err != nil {
return err
}
path := filepath.Join(p.mainPkg, "$testmain.go")
if p.fset == nil {
p.fset = token.NewFileSet()
}
newMain, err := parser.ParseFile(p.fset, path, b.Bytes(), parser.AllErrors)
if err != nil {
return err
}
mainPkg.Files = append(mainPkg.Files, newMain)
return nil
}
// parseFile is a wrapper around parser.ParseFile.
func (p *Program) parseFile(path string, mode parser.Mode) (*ast.File, error) {
if p.fset == nil {
@@ -255,26 +319,34 @@ func (p *Program) parseFile(path string, mode parser.Mode) (*ast.File, error) {
return nil, err
}
defer rd.Close()
return parser.ParseFile(p.fset, p.getOriginalPath(path), rd, mode)
relpath := path
if filepath.IsAbs(path) {
rp, err := filepath.Rel(p.Dir, path)
if err == nil {
relpath = rp
}
}
return parser.ParseFile(p.fset, relpath, rd, mode)
}
// Parse parses and typechecks this package.
//
// Idempotent.
func (p *Package) Parse() error {
func (p *Package) Parse(includeTests bool) error {
if len(p.Files) != 0 {
return nil // nothing to do (?)
return nil
}
// Load the AST.
// TODO: do this in parallel.
if p.ImportPath == "unsafe" {
// Special case for the unsafe package, which is defined internally by
// the types package.
// Special case for the unsafe package. Don't even bother loading
// the files.
p.Pkg = types.Unsafe
return nil
}
files, err := p.parseFiles()
files, err := p.parseFiles(includeTests)
if err != nil {
return err
}
@@ -289,11 +361,11 @@ func (p *Package) Parse() error {
// Idempotent.
func (p *Package) Check() error {
if p.Pkg != nil {
return nil // already typechecked
return nil
}
var typeErrors []error
checker := p.program.typeChecker // make a copy, because it will be modified
checker := p.TypeChecker
checker.Error = func(err error) {
typeErrors = append(typeErrors, err)
}
@@ -301,7 +373,7 @@ func (p *Package) Check() error {
// Do typechecking of the package.
checker.Importer = p
typesPkg, err := checker.Check(p.ImportPath, p.program.fset, p.Files, &p.info)
typesPkg, err := checker.Check(p.ImportPath, p.fset, p.Files, &p.Info)
if err != nil {
if err, ok := err.(Errors); ok {
return err
@@ -313,47 +385,52 @@ func (p *Package) Check() error {
}
// parseFiles parses the loaded list of files and returns this list.
func (p *Package) parseFiles() ([]*ast.File, error) {
func (p *Package) parseFiles(includeTests bool) ([]*ast.File, error) {
// TODO: do this concurrently.
var files []*ast.File
var fileErrs []error
// Parse all files (incuding CgoFiles).
parseFile := func(file string) {
if !filepath.IsAbs(file) {
file = filepath.Join(p.Dir, file)
}
f, err := p.program.parseFile(file, parser.ParseComments)
var gofiles []string
if includeTests {
gofiles = make([]string, 0, len(p.GoFiles)+len(p.TestGoFiles))
gofiles = append(gofiles, p.GoFiles...)
gofiles = append(gofiles, p.TestGoFiles...)
} else {
gofiles = p.GoFiles
}
for _, file := range gofiles {
f, err := p.parseFile(filepath.Join(p.Package.Dir, file), parser.ParseComments)
if err != nil {
fileErrs = append(fileErrs, err)
return
continue
}
if err != nil {
fileErrs = append(fileErrs, err)
continue
}
files = append(files, f)
}
for _, file := range p.GoFiles {
parseFile(file)
}
for _, file := range p.CgoFiles {
parseFile(file)
}
// Do CGo processing.
if len(p.CgoFiles) != 0 {
var cflags []string
cflags = append(cflags, p.program.config.CFlags()...)
cflags = append(cflags, "-I"+p.Dir)
if p.program.clangHeaders != "" {
cflags = append(cflags, "-Xclang", "-internal-isystem", "-Xclang", p.program.clangHeaders)
path := filepath.Join(p.Package.Dir, file)
f, err := p.parseFile(path, parser.ParseComments)
if err != nil {
fileErrs = append(fileErrs, err)
continue
}
generated, ldflags, errs := cgo.Process(files, p.program.workingDir, p.program.fset, cflags)
files = append(files, f)
}
if len(p.CgoFiles) != 0 {
cflags := append(p.CFlags, "-I"+p.Package.Dir)
if p.ClangHeaders != "" {
cflags = append(cflags, "-Xclang", "-internal-isystem", "-Xclang", p.ClangHeaders)
}
generated, errs := cgo.Process(files, p.Program.Dir, p.fset, cflags)
if errs != nil {
fileErrs = append(fileErrs, errs...)
}
files = append(files, generated)
p.program.LDFlags = append(p.program.LDFlags, ldflags...)
}
// Only return an error after CGo processing, so that errors in parsing and
// CGo can be reported together.
if len(fileErrs) != 0 {
return nil, Errors{p, fileErrs}
}
@@ -367,14 +444,59 @@ func (p *Package) Import(to string) (*types.Package, error) {
if to == "unsafe" {
return types.Unsafe, nil
}
if replace, ok := p.ImportMap[to]; ok {
// This import path should be replaced by another import path, according
// to `go list`.
to = replace
}
if imported, ok := p.program.Packages[to]; ok {
return imported.Pkg, nil
if _, ok := p.Imports[to]; ok {
return p.Imports[to].Pkg, nil
} else {
return nil, errors.New("package not imported: " + to)
}
}
// importRecursively calls Program.Import() on all imported packages, and calls
// importRecursively() on the imported packages as well.
//
// Idempotent.
func (p *Package) importRecursively(includeTests bool) error {
p.Importing = true
imports := p.Package.Imports
if includeTests {
imports = append(imports, p.Package.TestImports...)
}
for _, to := range imports {
if to == "C" {
// Do CGo processing in a later stage.
continue
}
if _, ok := p.Imports[to]; ok {
continue
}
// Find error location.
var pos token.Position
if len(p.Package.ImportPos[to]) > 0 {
pos = p.Package.ImportPos[to][0]
} else {
pos = token.Position{Filename: p.Package.ImportPath}
}
importedPkg, err := p.Program.Import(to, p.Package.Dir, pos)
if err != nil {
if err, ok := err.(*ImportCycleError); ok {
err.Packages = append([]string{p.ImportPath}, err.Packages...)
}
return err
}
if importedPkg.Importing {
return &ImportCycleError{[]string{p.ImportPath, importedPkg.ImportPath}, p.ImportPos[to]}
}
err = importedPkg.importRecursively(false)
if err != nil {
if err, ok := err.(*ImportCycleError); ok {
err.Packages = append([]string{p.ImportPath}, err.Packages...)
}
return err
}
p.Imports[to] = importedPkg
}
p.Importing = false
return nil
}
+2 -2
View File
@@ -10,8 +10,8 @@ import (
func (p *Program) LoadSSA() *ssa.Program {
prog := ssa.NewProgram(p.fset, ssa.SanityCheckFunctions|ssa.BareInits|ssa.GlobalDebug)
for _, pkg := range p.sorted {
prog.CreatePackage(pkg.Pkg, pkg.Files, &pkg.info, true)
for _, pkg := range p.Sorted() {
prog.CreatePackage(pkg.Pkg, pkg.Files, &pkg.Info, true)
}
return prog
+100 -223
View File
@@ -8,7 +8,6 @@ import (
"go/scanner"
"go/types"
"io"
"io/ioutil"
"os"
"os/exec"
"os/signal"
@@ -29,12 +28,6 @@ import (
"go.bug.st/serial"
)
var (
// This variable is set at build time using -ldflags parameters.
// See: https://stackoverflow.com/a/11355611
gitSha1 string
)
// 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 {
@@ -65,23 +58,33 @@ func moveFile(src, dst string) error {
return os.Remove(src)
}
// copyFile copies the given file from src to dst. It can copy over
// a possibly already existing file at the destination.
// copyFile copies the given file from src to dst. It copies first to a .tmp
// file which is then moved over a possibly already existing file at the
// destination.
func copyFile(src, dst string) error {
source, err := os.Open(src)
inf, err := os.Open(src)
if err != nil {
return err
}
defer source.Close()
destination, err := os.Create(dst)
defer inf.Close()
outpath := dst + ".tmp"
outf, err := os.Create(outpath)
if err != nil {
return err
}
defer destination.Close()
_, err = io.Copy(destination, source)
return err
_, err = io.Copy(outf, inf)
if err != nil {
os.Remove(outpath)
return err
}
err = outf.Close()
if err != nil {
return err
}
return os.Rename(dst+".tmp", dst)
}
// Build compiles and links the given package and writes it to outpath.
@@ -91,10 +94,10 @@ func Build(pkgName, outpath string, options *compileopts.Options) error {
return err
}
return builder.Build(pkgName, outpath, config, func(result builder.BuildResult) error {
if err := os.Rename(result.Binary, outpath); err != nil {
return builder.Build(pkgName, outpath, config, func(tmppath string) error {
if err := os.Rename(tmppath, outpath); err != nil {
// Moving failed. Do a file copy.
inf, err := os.Open(result.Binary)
inf, err := os.Open(tmppath)
if err != nil {
return err
}
@@ -121,17 +124,22 @@ func Build(pkgName, outpath string, options *compileopts.Options) error {
// Test runs the tests in the given package.
func Test(pkgName string, options *compileopts.Options) error {
options.TestConfig.CompileTestBinary = true
config, err := builder.NewConfig(options)
if err != nil {
return err
}
return builder.Build(pkgName, ".elf", config, func(result builder.BuildResult) error {
cmd := exec.Command(result.Binary)
// Add test build tag. This is incorrect: `go test` only looks at the
// _test.go file suffix but does not add the test build tag in the process.
// 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.Stdout = os.Stdout
cmd.Stderr = os.Stderr
cmd.Dir = result.MainDir
err := cmd.Run()
if err != nil {
// Propagate the exit code
@@ -141,7 +149,7 @@ func Test(pkgName string, options *compileopts.Options) error {
}
os.Exit(1)
}
return &commandError{"failed to run compiled binary", result.Binary, err}
return &commandError{"failed to run compiled binary", tmppath, err}
}
return nil
})
@@ -185,9 +193,9 @@ func Flash(pkgName, port string, options *compileopts.Options) error {
return errors.New("unknown flash method: " + flashMethod)
}
return builder.Build(pkgName, fileExt, config, func(result builder.BuildResult) error {
return builder.Build(pkgName, fileExt, config, func(tmppath string) error {
// do we need port reset to put MCU into bootloader mode?
if config.Target.PortReset == "true" && flashMethod != "openocd" {
if config.Target.PortReset == "true" {
if port == "" {
var err error
port, err = getDefaultPort()
@@ -198,7 +206,7 @@ func Flash(pkgName, port string, options *compileopts.Options) error {
err := touchSerialPortAt1200bps(port)
if err != nil {
return &commandError{"failed to reset port", result.Binary, err}
return &commandError{"failed to reset port", tmppath, err}
}
// give the target MCU a chance to restart into bootloader
time.Sleep(3 * time.Second)
@@ -210,7 +218,7 @@ func Flash(pkgName, port string, options *compileopts.Options) error {
// Create the command.
flashCmd := config.Target.FlashCommand
fileToken := "{" + fileExt[1:] + "}"
flashCmd = strings.Replace(flashCmd, fileToken, result.Binary, -1)
flashCmd = strings.Replace(flashCmd, fileToken, tmppath, -1)
if port == "" && strings.Contains(flashCmd, "{port}") {
var err error
@@ -240,21 +248,21 @@ func Flash(pkgName, port string, options *compileopts.Options) error {
cmd.Dir = goenv.Get("TINYGOROOT")
err := cmd.Run()
if err != nil {
return &commandError{"failed to flash", result.Binary, err}
return &commandError{"failed to flash", tmppath, err}
}
return nil
case "msd":
switch fileExt {
case ".uf2":
err := flashUF2UsingMSD(config.Target.FlashVolume, result.Binary)
err := flashUF2UsingMSD(config.Target.FlashVolume, tmppath)
if err != nil {
return &commandError{"failed to flash", result.Binary, err}
return &commandError{"failed to flash", tmppath, err}
}
return nil
case ".hex":
err := flashHexUsingMSD(config.Target.FlashVolume, result.Binary)
err := flashHexUsingMSD(config.Target.FlashVolume, tmppath)
if err != nil {
return &commandError{"failed to flash", result.Binary, err}
return &commandError{"failed to flash", tmppath, err}
}
return nil
default:
@@ -265,13 +273,13 @@ func Flash(pkgName, port string, options *compileopts.Options) error {
if err != nil {
return err
}
args = append(args, "-c", "program "+filepath.ToSlash(result.Binary)+" reset exit")
args = append(args, "-c", "program "+tmppath+" reset exit")
cmd := exec.Command("openocd", args...)
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
err = cmd.Run()
if err != nil {
return &commandError{"failed to flash", result.Binary, err}
return &commandError{"failed to flash", tmppath, err}
}
return nil
default:
@@ -296,7 +304,7 @@ func FlashGDB(pkgName string, ocdOutput bool, options *compileopts.Options) erro
return errors.New("gdb not configured in the target specification")
}
return builder.Build(pkgName, "", config, func(result builder.BuildResult) error {
return builder.Build(pkgName, "", config, func(tmppath string) error {
// Find a good way to run GDB.
gdbInterface, openocdInterface := config.Programmer()
switch gdbInterface {
@@ -359,7 +367,7 @@ func FlashGDB(pkgName string, ocdOutput bool, options *compileopts.Options) erro
gdbCommands = append(gdbCommands, "target remote :1234")
// Run in an emulator.
args := append(config.Target.Emulator[1:], result.Binary, "-s", "-S")
args := append(config.Target.Emulator[1:], tmppath, "-s", "-S")
daemon = exec.Command(config.Target.Emulator[0], args...)
daemon.Stdout = os.Stdout
daemon.Stderr = os.Stderr
@@ -367,7 +375,7 @@ func FlashGDB(pkgName string, ocdOutput bool, options *compileopts.Options) erro
gdbCommands = append(gdbCommands, "target remote :2345")
// Run in an emulator.
args := append(config.Target.Emulator[1:], result.Binary, "-g")
args := append(config.Target.Emulator[1:], tmppath, "-g")
daemon = exec.Command(config.Target.Emulator[0], args...)
daemon.Stdout = os.Stdout
daemon.Stderr = os.Stderr
@@ -405,7 +413,7 @@ func FlashGDB(pkgName string, ocdOutput bool, options *compileopts.Options) erro
// Construct and execute a gdb command.
// By default: gdb -ex run <binary>
// Exit GDB with Ctrl-D.
params := []string{result.Binary}
params := []string{tmppath}
for _, cmd := range gdbCommands {
params = append(params, "-ex", cmd)
}
@@ -415,7 +423,7 @@ func FlashGDB(pkgName string, ocdOutput bool, options *compileopts.Options) erro
cmd.Stderr = os.Stderr
err := cmd.Run()
if err != nil {
return &commandError{"failed to run gdb with", result.Binary, err}
return &commandError{"failed to run gdb with", tmppath, err}
}
return nil
})
@@ -431,10 +439,10 @@ func Run(pkgName string, options *compileopts.Options) error {
return err
}
return builder.Build(pkgName, ".elf", config, func(result builder.BuildResult) error {
return builder.Build(pkgName, ".elf", config, func(tmppath string) error {
if len(config.Target.Emulator) == 0 {
// Run directly.
cmd := exec.Command(result.Binary)
cmd := exec.Command(tmppath)
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
err := cmd.Run()
@@ -443,12 +451,12 @@ func Run(pkgName string, options *compileopts.Options) error {
// Workaround for QEMU which always exits with an error.
return nil
}
return &commandError{"failed to run compiled binary", result.Binary, err}
return &commandError{"failed to run compiled binary", tmppath, err}
}
return nil
} else {
// Run in an emulator.
args := append(config.Target.Emulator[1:], result.Binary)
args := append(config.Target.Emulator[1:], tmppath)
cmd := exec.Command(config.Target.Emulator[0], args...)
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
@@ -458,7 +466,7 @@ func Run(pkgName string, options *compileopts.Options) error {
// Workaround for QEMU which always exits with an error.
return nil
}
return &commandError{"failed to run emulator with", result.Binary, err}
return &commandError{"failed to run emulator with", tmppath, err}
}
return nil
}
@@ -471,13 +479,6 @@ func touchSerialPortAt1200bps(port string) (err error) {
// Open port
p, e := serial.Open(port, &serial.Mode{BaudRate: 1200})
if e != nil {
if runtime.GOOS == `windows` {
se, ok := e.(*serial.PortError)
if ok && se.Code() == serial.InvalidSerialPort {
// InvalidSerialPort error occurs when transitioning to boot
return nil
}
}
time.Sleep(1 * time.Second)
err = e
continue
@@ -490,8 +491,6 @@ func touchSerialPortAt1200bps(port string) (err error) {
return fmt.Errorf("opening port: %s", err)
}
const maxMSDRetries = 10
func flashUF2UsingMSD(volume, tmppath string) error {
// find standard UF2 info path
var infoPath string
@@ -508,12 +507,15 @@ func flashUF2UsingMSD(volume, tmppath string) error {
infoPath = path + "/INFO_UF2.TXT"
}
d, err := locateDevice(volume, infoPath)
d, err := filepath.Glob(infoPath)
if err != nil {
return err
}
if d == nil {
return errors.New("unable to locate UF2 device: " + volume)
}
return moveFile(tmppath, filepath.Dir(d)+"/flash.uf2")
return moveFile(tmppath, filepath.Dir(d[0])+"/flash.uf2")
}
func flashHexUsingMSD(volume, tmppath string) error {
@@ -532,31 +534,15 @@ func flashHexUsingMSD(volume, tmppath string) error {
destPath = path + "/"
}
d, err := locateDevice(volume, destPath)
d, err := filepath.Glob(destPath)
if err != nil {
return err
}
return moveFile(tmppath, d+"/flash.hex")
}
func locateDevice(volume, path string) (string, error) {
var d []string
var err error
for i := 0; i < maxMSDRetries; i++ {
d, err = filepath.Glob(path)
if err != nil {
return "", err
}
if d != nil {
break
}
time.Sleep(500 * time.Millisecond)
}
if d == nil {
return "", errors.New("unable to locate device: " + volume)
return errors.New("unable to locate device: " + volume)
}
return d[0], nil
return moveFile(tmppath, d[0]+"/flash.hex")
}
func windowsFindUSBDrive(volume string) (string, error) {
@@ -617,18 +603,29 @@ func getDefaultPort() (port string, err error) {
case "freebsd":
portPath = "/dev/cuaU*"
case "windows":
ports, err := serial.GetPortsList()
cmd := exec.Command("wmic",
"PATH", "Win32_SerialPort", "WHERE", "Caption LIKE 'USB Serial%'", "GET", "DeviceID")
var out bytes.Buffer
cmd.Stdout = &out
err := cmd.Run()
if err != nil {
return "", err
}
if len(ports) == 0 {
if out.String() == "No Instance(s) Available." {
return "", errors.New("no serial ports available")
} else if len(ports) > 1 {
return "", errors.New("multiple serial ports available - use -port flag")
}
return ports[0], nil
for _, line := range strings.Split(out.String(), "\n") {
words := strings.Fields(line)
if len(words) == 1 {
if strings.Contains(words[0], "COM") {
return words[0], nil
}
}
}
return "", errors.New("unable to locate a serial port")
default:
return "", errors.New("unable to search for a default USB device to be flashed on this OS")
}
@@ -646,7 +643,7 @@ func getDefaultPort() (port string, err error) {
func usage() {
fmt.Fprintln(os.Stderr, "TinyGo is a Go compiler for small places.")
fmt.Fprintln(os.Stderr, "version:", goenv.Version)
fmt.Fprintln(os.Stderr, "version:", version)
fmt.Fprintf(os.Stderr, "usage: %s command [-printir] [-target=<target>] -o <output> <input>\n", os.Args[0])
fmt.Fprintln(os.Stderr, "\ncommands:")
fmt.Fprintln(os.Stderr, " build: compile packages and dependencies")
@@ -655,27 +652,12 @@ func usage() {
fmt.Fprintln(os.Stderr, " flash: compile and flash to the device")
fmt.Fprintln(os.Stderr, " gdb: run/flash and immediately enter GDB")
fmt.Fprintln(os.Stderr, " env: list environment variables used during build")
fmt.Fprintln(os.Stderr, " list: run go list using the TinyGo root")
fmt.Fprintln(os.Stderr, " clean: empty cache directory ("+goenv.Get("GOCACHE")+")")
fmt.Fprintln(os.Stderr, " help: print this help text")
fmt.Fprintln(os.Stderr, "\nflags:")
flag.PrintDefaults()
}
// try to make the path relative to the current working directory. If any error
// occurs, this error is ignored and the absolute path is returned instead.
func tryToMakePathRelative(dir string) string {
wd, err := os.Getwd()
if err != nil {
return dir
}
relpath, err := filepath.Rel(wd, dir)
if err != nil {
return dir
}
return relpath
}
// printCompilerError prints compiler errors using the provided logger function
// (similar to fmt.Println).
//
@@ -683,24 +665,8 @@ func tryToMakePathRelative(dir string) string {
// to limitations in the LLVM bindings.
func printCompilerError(logln func(...interface{}), err error) {
switch err := err.(type) {
case types.Error:
printCompilerError(logln, scanner.Error{
Pos: err.Fset.Position(err.Pos),
Msg: err.Msg,
})
case scanner.Error:
if !strings.HasPrefix(err.Pos.Filename, filepath.Join(goenv.Get("GOROOT"), "src")) && !strings.HasPrefix(err.Pos.Filename, filepath.Join(goenv.Get("TINYGOROOT"), "src")) {
// This file is not from the standard library (either the GOROOT or
// the TINYGOROOT). Make the path relative, for easier reading.
// Ignore any errors in the process (falling back to the absolute
// path).
err.Pos.Filename = tryToMakePathRelative(err.Pos.Filename)
}
case types.Error, scanner.Error:
logln(err)
case scanner.ErrorList:
for _, scannerErr := range err {
printCompilerError(logln, *scannerErr)
}
case *interp.Error:
logln("#", err.ImportPath)
logln(err.Error())
@@ -719,17 +685,11 @@ func printCompilerError(logln func(...interface{}), err error) {
case loader.Errors:
logln("#", err.Pkg.ImportPath)
for _, err := range err.Errs {
printCompilerError(logln, err)
}
case loader.Error:
logln(err.Err.Error())
logln("package", err.ImportStack[0])
for _, pkgPath := range err.ImportStack[1:] {
logln("\timports", pkgPath)
logln(err)
}
case *builder.MultiError:
for _, err := range err.Errs {
printCompilerError(logln, err)
logln(err)
}
default:
logln("error:", err)
@@ -746,25 +706,17 @@ func handleCompilerError(err error) {
}
func main() {
if len(os.Args) < 2 {
fmt.Fprintln(os.Stderr, "No command-line arguments supplied.")
usage()
os.Exit(1)
}
command := os.Args[1]
outpath := flag.String("o", "", "output filename")
opt := flag.String("opt", "z", "optimization level: 0, 1, 2, s, z")
gc := flag.String("gc", "", "garbage collector to use (none, leaking, extalloc, conservative)")
panicStrategy := flag.String("panic", "print", "panic strategy (print, trap)")
scheduler := flag.String("scheduler", "", "which scheduler to use (none, coroutines, tasks)")
scheduler := flag.String("scheduler", "", "which scheduler to use (coroutines, tasks)")
printIR := flag.Bool("printir", false, "print LLVM IR")
dumpSSA := flag.Bool("dumpssa", false, "dump internal Go SSA")
verifyIR := flag.Bool("verifyir", false, "run extra verification steps on LLVM IR")
tags := flag.String("tags", "", "a space-separated list of extra build tags")
target := flag.String("target", "", "LLVM target | .json file with TargetSpec")
printSize := flag.String("size", "", "print sizes (none, short, full)")
printStacks := flag.Bool("print-stacks", false, "print stack sizes of goroutines")
nodebug := flag.Bool("no-debug", false, "disable DWARF debug symbol generation")
ocdOutput := flag.Bool("ocd-output", false, "print OCD daemon output during debug")
port := flag.String("port", "", "flash port")
@@ -774,11 +726,12 @@ func main() {
wasmAbi := flag.String("wasm-abi", "js", "WebAssembly ABI conventions: js (no i64 params) or generic")
heapSize := flag.String("heap-size", "1M", "default heap size in bytes (only supported by WebAssembly)")
var flagJSON, flagDeps *bool
if command == "list" {
flagJSON = flag.Bool("json", false, "print data in JSON format")
flagDeps = flag.Bool("deps", false, "")
if len(os.Args) < 2 {
fmt.Fprintln(os.Stderr, "No command-line arguments supplied.")
usage()
os.Exit(1)
}
command := os.Args[1]
// Early command processing, before commands are interpreted by the Go flag
// library.
@@ -804,7 +757,6 @@ func main() {
VerifyIR: *verifyIR,
Debug: !*nodebug,
PrintSizes: *printSize,
PrintStacks: *printStacks,
Tags: *tags,
WasmAbi: *wasmAbi,
Programmer: *programmer,
@@ -818,6 +770,12 @@ func main() {
options.LDFlags = strings.Split(*ldFlags, " ")
}
if *panicStrategy != "print" && *panicStrategy != "trap" {
fmt.Fprintln(os.Stderr, "Panic strategy must be either print or trap.")
usage()
os.Exit(1)
}
var err error
if options.HeapSize, err = parseSize(*heapSize); err != nil {
fmt.Fprintln(os.Stderr, "Could not read heap size:", *heapSize)
@@ -827,13 +785,6 @@ func main() {
os.Setenv("CC", "clang -target="+*target)
err = options.Verify()
if err != nil {
fmt.Fprintln(os.Stderr, err.Error())
usage()
os.Exit(1)
}
switch command {
case "build":
if *outpath == "" {
@@ -843,7 +794,7 @@ func main() {
}
pkgName := "."
if flag.NArg() == 1 {
pkgName = filepath.ToSlash(flag.Arg(0))
pkgName = flag.Arg(0)
} else if flag.NArg() > 1 {
fmt.Fprintln(os.Stderr, "build only accepts a single positional argument: package name, but multiple were specified")
usage()
@@ -852,7 +803,6 @@ func main() {
if options.Target == "" && filepath.Ext(*outpath) == ".wasm" {
options.Target = "wasm"
}
err := Build(pkgName, *outpath, options)
handleCompilerError(err)
case "build-library":
@@ -889,9 +839,8 @@ func main() {
usage()
os.Exit(1)
}
pkgName := filepath.ToSlash(flag.Arg(0))
if command == "flash" {
err := Flash(pkgName, *port, options)
err := Flash(flag.Arg(0), *port, options)
handleCompilerError(err)
} else {
if !options.Debug {
@@ -899,7 +848,7 @@ func main() {
usage()
os.Exit(1)
}
err := FlashGDB(pkgName, *ocdOutput, options)
err := FlashGDB(flag.Arg(0), *ocdOutput, options)
handleCompilerError(err)
}
case "run":
@@ -908,13 +857,12 @@ func main() {
usage()
os.Exit(1)
}
pkgName := filepath.ToSlash(flag.Arg(0))
err := Run(pkgName, options)
err := Run(flag.Arg(0), options)
handleCompilerError(err)
case "test":
pkgName := "."
if flag.NArg() == 1 {
pkgName = filepath.ToSlash(flag.Arg(0))
pkgName = flag.Arg(0)
} else if flag.NArg() > 1 {
fmt.Fprintln(os.Stderr, "test only accepts a single positional argument: package name, but multiple were specified")
usage()
@@ -922,35 +870,6 @@ func main() {
}
err := Test(pkgName, options)
handleCompilerError(err)
case "targets":
dir := filepath.Join(goenv.Get("TINYGOROOT"), "targets")
entries, err := ioutil.ReadDir(dir)
if err != nil {
fmt.Fprintln(os.Stderr, "could not list targets:", err)
os.Exit(1)
return
}
for _, entry := range entries {
if !entry.Mode().IsRegular() || !strings.HasSuffix(entry.Name(), ".json") {
// Only inspect JSON files.
continue
}
path := filepath.Join(dir, entry.Name())
spec, err := compileopts.LoadTarget(path)
if err != nil {
fmt.Fprintln(os.Stderr, "could not list target:", err)
os.Exit(1)
return
}
if spec.FlashMethod == "" && spec.FlashCommand == "" && spec.Emulator == nil {
// This doesn't look like a regular target file, but rather like
// a parent target (such as targets/cortex-m.json).
continue
}
name := entry.Name()
name = name[:len(name)-5]
fmt.Println(name)
}
case "info":
if flag.NArg() == 1 {
options.Target = flag.Arg(0)
@@ -970,50 +889,12 @@ func main() {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
cachedGOROOT, err := loader.GetCachedGoroot(config)
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())
fmt.Printf("cached GOROOT: %s\n", cachedGOROOT)
case "list":
config, err := builder.NewConfig(options)
if err != nil {
fmt.Fprintln(os.Stderr, err)
usage()
os.Exit(1)
}
var extraArgs []string
if *flagJSON {
extraArgs = append(extraArgs, "-json")
}
if *flagDeps {
extraArgs = append(extraArgs, "-deps")
}
cmd, err := loader.List(config, extraArgs, flag.Args())
if err != nil {
fmt.Fprintln(os.Stderr, "failed to run `go list`:", err)
os.Exit(1)
}
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
err = cmd.Run()
if err != nil {
if exitErr, ok := err.(*exec.ExitError); ok {
if status, ok := exitErr.Sys().(syscall.WaitStatus); ok {
os.Exit(status.ExitStatus())
}
os.Exit(1)
}
fmt.Fprintln(os.Stderr, "failed to run `go list`:", err)
os.Exit(1)
}
case "clean":
// remove cache directory
err := os.RemoveAll(goenv.Get("GOCACHE"))
@@ -1025,13 +906,9 @@ func main() {
usage()
case "version":
goversion := "<unknown>"
if s, err := goenv.GorootVersionString(goenv.Get("GOROOT")); err == nil {
if s, err := builder.GorootVersionString(goenv.Get("GOROOT")); err == nil {
goversion = s
}
version := goenv.Version
if strings.HasSuffix(goenv.Version, "-dev") && gitSha1 != "" {
version += "-" + gitSha1
}
fmt.Printf("tinygo version %s %s/%s (using go version %s and LLVM version %s)\n", version, runtime.GOOS, runtime.GOARCH, goversion, llvm.Version)
case "env":
if flag.NArg() == 0 {
+2 -13
View File
@@ -6,7 +6,6 @@ package main
import (
"bufio"
"bytes"
"flag"
"fmt"
"io/ioutil"
"os"
@@ -26,8 +25,6 @@ import (
const TESTDATA = "testdata"
var testTarget = flag.String("target", "", "override test target")
func TestCompiler(t *testing.T) {
matches, err := filepath.Glob(filepath.Join(TESTDATA, "*.go"))
if err != nil {
@@ -47,14 +44,6 @@ func TestCompiler(t *testing.T) {
sort.Strings(matches)
if *testTarget != "" {
// This makes it possible to run one specific test (instead of all),
// which is especially useful to quickly check whether some changes
// affect a particular target architecture.
runPlatTests(*testTarget, matches, t)
return
}
if runtime.GOOS != "windows" {
t.Run("Host", func(t *testing.T) {
runPlatTests("", matches, t)
@@ -84,7 +73,7 @@ func TestCompiler(t *testing.T) {
t.Run("ARM64Linux", func(t *testing.T) {
runPlatTests("aarch64--linux-gnu", matches, t)
})
goVersion, err := goenv.GorootVersionString(goenv.Get("GOROOT"))
goVersion, err := builder.GorootVersionString(goenv.Get("GOROOT"))
if err != nil {
t.Error("could not get Go version:", err)
return
@@ -157,7 +146,7 @@ func runTest(path, target string, t *testing.T) {
PrintIR: false,
DumpSSA: false,
VerifyIR: true,
Debug: true,
Debug: false,
PrintSizes: "",
WasmAbi: "js",
}
+1 -6
View File
@@ -174,11 +174,6 @@ func EnableIRQ(irq uint32) {
NVIC.ISER[irq>>5].Set(1 << (irq & 0x1F))
}
// Disable the given interrupt number.
func DisableIRQ(irq uint32) {
NVIC.ICER[irq>>5].Set(1 << (irq & 0x1F))
}
// Set the priority of the given interrupt number.
// Note that the priority is given as a 0-255 number, where some of the lower
// bits are not implemented by the hardware. For example, to set a low interrupt
@@ -201,7 +196,7 @@ func SetPriority(irq uint32, priority uint32) {
func DisableInterrupts() uintptr {
return AsmFull(`
mrs {}, PRIMASK
cpsid i
cpsid if
`, nil)
}
-7
View File
@@ -1,11 +1,9 @@
.syntax unified
.cfi_sections .debug_frame
.section .text.HardFault_Handler
.global HardFault_Handler
.type HardFault_Handler, %function
HardFault_Handler:
.cfi_startproc
// Put the old stack pointer in the first argument, for easy debugging. This
// is especially useful on Cortex-M0, which supports far fewer debug
// facilities.
@@ -21,8 +19,6 @@ HardFault_Handler:
// Continue handling this error in Go.
bl handleHardFault
.cfi_endproc
.size HardFault_Handler, .-HardFault_Handler
// This is a convenience function for semihosting support.
// At some point, this should be replaced by inline assembly.
@@ -30,8 +26,5 @@ HardFault_Handler:
.global SemihostingCall
.type SemihostingCall, %function
SemihostingCall:
.cfi_startproc
bkpt 0xab
bx lr
.cfi_endproc
.size SemihostingCall, .-SemihostingCall
-21
View File
@@ -1,21 +0,0 @@
package device
// Run the given assembly code. The code will be marked as having side effects,
// as it doesn't produce output and thus would normally be eliminated by the
// optimizer.
func Asm(asm string)
// Run the given inline assembly. The code will be marked as having side
// effects, as it would otherwise be optimized away. The inline assembly string
// recognizes template values in the form {name}, like so:
//
// arm.AsmFull(
// "str {value}, {result}",
// map[string]interface{}{
// "value": 1
// "result": &dest,
// })
//
// You can use {} in the asm string (which expands to a register) to set the
// return value.
func AsmFull(asm string, regs map[string]interface{}) uintptr
-52
View File
@@ -1,52 +0,0 @@
// The following definitions were copied from:
// esp-idf/components/xtensa/include/xtensa/corebits.h
#define PS_WOE_MASK 0x00040000
#define PS_OWB_MASK 0x00000F00
#define PS_CALLINC_MASK 0x00030000
#define PS_WOE PS_WOE_MASK
// Only calling it call_start_cpu0 for consistency with ESP-IDF.
.section .text.call_start_cpu0
1:
.long _stack_top
.global call_start_cpu0
call_start_cpu0:
// We need to set the stack pointer to a different value. This is somewhat
// complicated in the Xtensa architecture. The code below is a modified
// version of the following code:
// https://github.com/espressif/esp-idf/blob/c77c4ccf/components/xtensa/include/xt_instr_macros.h#L47
// Disable WOE.
rsr.ps a2
movi a3, ~(PS_WOE_MASK)
and a2, a2, a3
wsr.ps a2
rsync
// Set WINDOWBASE to 1 << WINDOWSTART.
rsr.windowbase a2
ssl a2
movi a2, 1
sll a2, a2
wsr.windowstart a2
rsync
// Load new stack pointer.
l32r sp, 1b
// Re-enable WOE.
rsr.ps a2
movi a3, PS_WOE
or a2, a2, a3
wsr.ps a2
rsync
// Jump to the runtime start function written in Go.
j main
.section .text.tinygo_scanCurrentStack
.global tinygo_scanCurrentStack
tinygo_scanCurrentStack:
// TODO: save callee saved registers on the stack
j tinygo_scanstack
-6
View File
@@ -1,6 +0,0 @@
.section .text.tinygo_scanCurrentStack
.global tinygo_scanCurrentStack
tinygo_scanCurrentStack:
// TODO: save callee saved registers on the stack
j tinygo_scanstack
-130
View File
@@ -1,130 +0,0 @@
#ifdef __riscv_flen
#define NREG 48
#define LFREG flw
#define SFREG fsw
#else
#define NREG 16
#endif
#if __riscv_xlen==64
#define REGSIZE 8
#define SREG sd
#define LREG ld
#else
#define REGSIZE 4
#define SREG sw
#define LREG lw
#endif
.section .text.handleInterruptASM
.global handleInterruptASM
.type handleInterruptASM,@function
handleInterruptASM:
// Save and restore all registers, because the hardware only saves/restores
// the pc.
// Note: we have to do this in assembly because the "interrupt"="machine"
// attribute is broken in LLVM: https://bugs.llvm.org/show_bug.cgi?id=42984
addi sp, sp, -NREG*REGSIZE
SREG ra, 0*REGSIZE(sp)
SREG t0, 1*REGSIZE(sp)
SREG t1, 2*REGSIZE(sp)
SREG t2, 3*REGSIZE(sp)
SREG a0, 4*REGSIZE(sp)
SREG a1, 5*REGSIZE(sp)
SREG a2, 6*REGSIZE(sp)
SREG a3, 7*REGSIZE(sp)
SREG a4, 8*REGSIZE(sp)
SREG a5, 9*REGSIZE(sp)
SREG a6, 10*REGSIZE(sp)
SREG a7, 11*REGSIZE(sp)
SREG t3, 12*REGSIZE(sp)
SREG t4, 13*REGSIZE(sp)
SREG t5, 14*REGSIZE(sp)
SREG t6, 15*REGSIZE(sp)
#ifdef __riscv_flen
SFREG f0, (0 + 16)*REGSIZE(sp)
SFREG f1, (1 + 16)*REGSIZE(sp)
SFREG f2, (2 + 16)*REGSIZE(sp)
SFREG f3, (3 + 16)*REGSIZE(sp)
SFREG f4, (4 + 16)*REGSIZE(sp)
SFREG f5, (5 + 16)*REGSIZE(sp)
SFREG f6, (6 + 16)*REGSIZE(sp)
SFREG f7, (7 + 16)*REGSIZE(sp)
SFREG f8, (8 + 16)*REGSIZE(sp)
SFREG f9, (9 + 16)*REGSIZE(sp)
SFREG f10,(10 + 16)*REGSIZE(sp)
SFREG f11,(11 + 16)*REGSIZE(sp)
SFREG f12,(12 + 16)*REGSIZE(sp)
SFREG f13,(13 + 16)*REGSIZE(sp)
SFREG f14,(14 + 16)*REGSIZE(sp)
SFREG f15,(15 + 16)*REGSIZE(sp)
SFREG f16,(16 + 16)*REGSIZE(sp)
SFREG f17,(17 + 16)*REGSIZE(sp)
SFREG f18,(18 + 16)*REGSIZE(sp)
SFREG f19,(19 + 16)*REGSIZE(sp)
SFREG f20,(20 + 16)*REGSIZE(sp)
SFREG f21,(21 + 16)*REGSIZE(sp)
SFREG f22,(22 + 16)*REGSIZE(sp)
SFREG f23,(23 + 16)*REGSIZE(sp)
SFREG f24,(24 + 16)*REGSIZE(sp)
SFREG f25,(25 + 16)*REGSIZE(sp)
SFREG f26,(26 + 16)*REGSIZE(sp)
SFREG f27,(27 + 16)*REGSIZE(sp)
SFREG f28,(28 + 16)*REGSIZE(sp)
SFREG f29,(29 + 16)*REGSIZE(sp)
SFREG f30,(30 + 16)*REGSIZE(sp)
SFREG f31,(31 + 16)*REGSIZE(sp)
#endif
call handleInterrupt
#ifdef __riscv_flen
LFREG f0, (31 + 16)*REGSIZE(sp)
LFREG f1, (30 + 16)*REGSIZE(sp)
LFREG f2, (29 + 16)*REGSIZE(sp)
LFREG f3, (28 + 16)*REGSIZE(sp)
LFREG f4, (27 + 16)*REGSIZE(sp)
LFREG f5, (26 + 16)*REGSIZE(sp)
LFREG f6, (25 + 16)*REGSIZE(sp)
LFREG f7, (24 + 16)*REGSIZE(sp)
LFREG f8, (23 + 16)*REGSIZE(sp)
LFREG f9, (22 + 16)*REGSIZE(sp)
LFREG f10,(21 + 16)*REGSIZE(sp)
LFREG f11,(20 + 16)*REGSIZE(sp)
LFREG f12,(19 + 16)*REGSIZE(sp)
LFREG f13,(18 + 16)*REGSIZE(sp)
LFREG f14,(17 + 16)*REGSIZE(sp)
LFREG f15,(16 + 16)*REGSIZE(sp)
LFREG f16,(15 + 16)*REGSIZE(sp)
LFREG f17,(14 + 16)*REGSIZE(sp)
LFREG f18,(13 + 16)*REGSIZE(sp)
LFREG f19,(12 + 16)*REGSIZE(sp)
LFREG f20,(11 + 16)*REGSIZE(sp)
LFREG f21,(10 + 16)*REGSIZE(sp)
LFREG f22,(9 + 16)*REGSIZE(sp)
LFREG f23,(8 + 16)*REGSIZE(sp)
LFREG f24,(7 + 16)*REGSIZE(sp)
LFREG f25,(6 + 16)*REGSIZE(sp)
LFREG f26,(5 + 16)*REGSIZE(sp)
LFREG f27,(4 + 16)*REGSIZE(sp)
LFREG f28,(3 + 16)*REGSIZE(sp)
LFREG f29,(2 + 16)*REGSIZE(sp)
LFREG f30,(1 + 16)*REGSIZE(sp)
LFREG f31,(0 + 16)*REGSIZE(sp)
#endif
LREG t6, 15*REGSIZE(sp)
LREG t5, 14*REGSIZE(sp)
LREG t4, 13*REGSIZE(sp)
LREG t3, 12*REGSIZE(sp)
LREG a7, 11*REGSIZE(sp)
LREG a6, 10*REGSIZE(sp)
LREG a5, 9*REGSIZE(sp)
LREG a4, 8*REGSIZE(sp)
LREG a3, 7*REGSIZE(sp)
LREG a2, 6*REGSIZE(sp)
LREG a1, 5*REGSIZE(sp)
LREG a0, 4*REGSIZE(sp)
LREG t2, 3*REGSIZE(sp)
LREG t1, 2*REGSIZE(sp)
LREG t0, 1*REGSIZE(sp)
LREG ra, 0*REGSIZE(sp)
addi sp, sp, NREG*REGSIZE
mret
-16
View File
@@ -19,19 +19,3 @@ func Asm(asm string)
// You can use {} in the asm string (which expands to a register) to set the
// return value.
func AsmFull(asm string, regs map[string]interface{}) uintptr
// DisableInterrupts disables all interrupts, and returns the old interrupt
// state.
func DisableInterrupts() uintptr {
// Note: this can be optimized with a CSRRW instruction, which atomically
// swaps the value and returns the old value.
mask := MIE.Get()
MIE.Set(0)
return mask
}
// EnableInterrupts enables all interrupts again. The value passed in must be
// the mask returned by DisableInterrupts.
func EnableInterrupts(mask uintptr) {
MIE.Set(mask)
}
+45 -5
View File
@@ -9,12 +9,52 @@ _start:
// Load the globals pointer. The program will load pointers relative to this
// register, so it must be set to the right value on startup.
// See: https://gnu-mcu-eclipse.github.io/arch/riscv/programmer/#the-gp-global-pointer-register
// Linker relaxations must be disabled to avoid the initialization beign
// relaxed with an uninitialized global pointer: mv gp, gp
.option push
.option norelax
la gp, __global_pointer$
.option pop
// Jump to runtime.main
call main
.section .text.handleInterruptASM
.global handleInterruptASM
.type handleInterruptASM,@function
handleInterruptASM:
// Save and restore all registers, because the hardware only saves/restores
// the pc.
// Note: we have to do this in assembly because the "interrupt"="machine"
// attribute is broken in LLVM: https://bugs.llvm.org/show_bug.cgi?id=42984
addi sp, sp, -64
sw ra, 60(sp)
sw t0, 56(sp)
sw t1, 52(sp)
sw t2, 48(sp)
sw a0, 44(sp)
sw a1, 40(sp)
sw a2, 36(sp)
sw a3, 32(sp)
sw a4, 28(sp)
sw a5, 24(sp)
sw a6, 20(sp)
sw a7, 16(sp)
sw t3, 12(sp)
sw t4, 8(sp)
sw t5, 4(sp)
sw t6, 0(sp)
call handleInterrupt
lw t6, 0(sp)
lw t5, 4(sp)
lw t4, 8(sp)
lw t3, 12(sp)
lw a7, 16(sp)
lw a6, 20(sp)
lw a5, 24(sp)
lw a4, 28(sp)
lw a3, 32(sp)
lw a2, 36(sp)
lw a1, 40(sp)
lw a0, 44(sp)
lw t2, 48(sp)
lw t1, 52(sp)
lw t0, 56(sp)
lw ra, 60(sp)
addi sp, sp, 64
mret
-71
View File
@@ -1,71 +0,0 @@
// Hand created file. DO NOT DELETE.
// atsamd51x bitfield definitions that are not auto-generated by gen-device-svd.go
// +build sam,atsamd51
// These are the supported pchctrl function numberings on the atsamd51x
// See http://ww1.microchip.com/downloads/en/DeviceDoc/SAM_D5xE5x_Family_Data_Sheet_DS60001507F.pdf
// table 14-9
package sam
const (
PCHCTRL_GCLK_OSCCTRL_DFLL48 = 0 // DFLL48 input clock source
PCHCTRL_GCLK_OSCCTRL_FDPLL0 = 1 // Reference clock for FDPLL0
PCHCTRL_GCLK_OSCCTRL_FDPLL1 = 2 // Reference clock for FDPLL1
PCHCTRL_GCLK_OSCCTRL_FDPLL0_32K = 3 // FDPLL0 = 3 // 32KHz clock for internal lock timer
PCHCTRL_GCLK_OSCCTRL_FDPLL1_32K = 3 // FDPLL1 = 3 // 32KHz clock for internal lock timer
PCHCTRL_GCLK_SDHC0_SLOW = 3 // SDHC0 = 3 // Slow
PCHCTRL_GCLK_SDHC1_SLOW = 3 // SDHC1 = 3 // Slow
PCHCTRL_GCLK_SERCOMX_SLOW = 3 // GCLK_SERCOM[0..7]_SLOW = 3
PCHCTRL_GCLK_EIC = 4
PCHCTRL_GCLK_FREQM_MSR = 5 // FREQM Measure
PCHCTRL_GCLK_FREQM_REF = 6 // FREQM Reference
PCHCTRL_GCLK_SERCOM0_CORE = 7 // SERCOM0 Core
PCHCTRL_GCLK_SERCOM1_CORE = 8 // SERCOM1 Core
PCHCTRL_GCLK_TC0 = 9
PCHCTRL_GCLK_TC1 = 9 // TC0, TC1
PCHCTRL_GCLK_USB = 10 // USB
PCHCTRL_GCLK_EVSYS0 = 11
PCHCTRL_GCLK_EVSYS1 = 12
PCHCTRL_GCLK_EVSYS2 = 13
PCHCTRL_GCLK_EVSYS3 = 14
PCHCTRL_GCLK_EVSYS4 = 15
PCHCTRL_GCLK_EVSYS5 = 16
PCHCTRL_GCLK_EVSYS6 = 17
PCHCTRL_GCLK_EVSYS7 = 18
PCHCTRL_GCLK_EVSYS8 = 19
PCHCTRL_GCLK_EVSYS9 = 20
PCHCTRL_GCLK_EVSYS10 = 21
PCHCTRL_GCLK_EVSYS11 = 22
PCHCTRL_GCLK_SERCOM2_CORE = 23 // SERCOM2 Core
PCHCTRL_GCLK_SERCOM3_CORE = 24 // SERCOM3 Core
PCHCTRL_GCLK_TCC0 = 25
PCHCTRL_GCLK_TCC1 = 25 // TCC0, TCC1
PCHCTRL_GCLK_TC2 = 26
PCHCTRL_GCLK_TC3 = 26 // TC2, TC3
PCHCTRL_GCLK_CAN0 = 27 // CAN0
PCHCTRL_GCLK_CAN1 = 28 // CAN1
PCHCTRL_GCLK_TCC2 = 29
PCHCTRL_GCLK_TCC3 = 29 // TCC2, TCC3
PCHCTRL_GCLK_TC4 = 30
PCHCTRL_GCLK_TC5 = 30 // TC4, TC5
PCHCTRL_GCLK_PDEC = 31 // PDEC
PCHCTRL_GCLK_AC = 32 // AC
PCHCTRL_GCLK_CCL = 33 // CCL
PCHCTRL_GCLK_SERCOM4_CORE = 34 // SERCOM4 Core
PCHCTRL_GCLK_SERCOM5_CORE = 35 // SERCOM5 Core
PCHCTRL_GCLK_SERCOM6_CORE = 36 // SERCOM6 Core
PCHCTRL_GCLK_SERCOM7_CORE = 37 // SERCOM7 Core
PCHCTRL_GCLK_TCC4 = 38 // TCC4
PCHCTRL_GCLK_TC6 = 39
PCHCTRL_GCLK_TC7 = 39 // TC6, TC7
PCHCTRL_GCLK_ADC0 = 40 // ADC0
PCHCTRL_GCLK_ADC1 = 41 // ADC1
PCHCTRL_GCLK_DAC = 42 // DAC
PCHCTRL_GCLK_I2S0 = 43
PCHCTRL_GCLK_I2S1 = 44
PCHCTRL_GCLK_SDHC0 = 45 // SDHC0
PCHCTRL_GCLK_SDHC1 = 46 // SDHC1
PCHCTRL_GCLK_CM4_TRACE = 47 // CM4 Trace
)
@@ -1,9 +1,7 @@
// Hand created file. DO NOT DELETE.
// STM32FXXX (except stm32f1xx) bitfield definitions that are not
// auto-generated by gen-device-svd.go
// +build stm32f4
// These are the supported alternate function numberings on the stm32f407
// +build stm32,stm32f407
// Alternate function settings on the stm32f4 series
// Alternate function settings on the stm32f4xx series
package stm32
+4 -2
View File
@@ -5,14 +5,16 @@ import (
"time"
)
// This example assumes that the button is connected to pin 8. Change the value
// below to use a different pin.
const (
led = machine.LED
button = machine.BUTTON
button = machine.Pin(8)
)
func main() {
led.Configure(machine.PinConfig{Mode: machine.PinOutput})
button.Configure(machine.PinConfig{Mode: machine.PinInputPullup})
button.Configure(machine.PinConfig{Mode: machine.PinInput})
for {
if button.Get() {
-13
View File
@@ -1,13 +0,0 @@
// +build circuitplay_express
package main
import (
"machine"
)
func init() {
enable := machine.PA30
enable.Configure(machine.PinConfig{Mode: machine.PinOutput})
enable.Set(true)
}
-36
View File
@@ -1,36 +0,0 @@
// Simplistic example using the DAC on the Circuit Playground Express.
//
// To actually use the DAC for producing complex waveforms or samples requires a DMA
// timer-based playback mechanism which is beyond the scope of this example.
package main
import (
"machine"
"time"
)
func main() {
speaker := machine.A0
speaker.Configure(machine.PinConfig{Mode: machine.PinOutput})
machine.DAC0.Configure(machine.DACConfig{})
data := []uint16{0xFFFF, 0x8000, 0x4000, 0x2000, 0x1000, 0x0000}
for {
for _, val := range data {
play(val)
time.Sleep(500 * time.Millisecond)
}
}
}
func play(val uint16) {
for i := 0; i < 100; i++ {
machine.DAC0.Set(val)
time.Sleep(2 * time.Millisecond)
machine.DAC0.Set(0)
time.Sleep(2 * time.Millisecond)
}
}
-13
View File
@@ -1,13 +0,0 @@
// +build pyportal
package main
import (
"machine"
)
func init() {
enable := machine.SPK_SD
enable.Configure(machine.PinConfig{Mode: machine.PinOutput})
enable.Set(true)
}
@@ -1,10 +0,0 @@
// +build circuitplay_express
package main
import "machine"
const (
buttonMode = machine.PinInputPulldown
buttonPinChange = machine.PinFalling
)
-10
View File
@@ -1,10 +0,0 @@
// +build pca10040
package main
import "machine"
const (
buttonMode = machine.PinInputPullup
buttonPinChange = machine.PinRising
)
-52
View File
@@ -1,52 +0,0 @@
package main
// This example demonstrates how to use pin change interrupts.
//
// This is only an example and should not be copied directly in any serious
// circuit, because it lacks an important feature: debouncing.
// See: https://en.wikipedia.org/wiki/Switch#Contact_bounce
import (
"machine"
"runtime/volatile"
"time"
)
const (
button = machine.BUTTON
led = machine.LED
)
func main() {
var lightLed volatile.Register8
lightLed.Set(0)
// Configure the LED, defaulting to on (usually setting the pin to low will
// turn the LED on).
led.Configure(machine.PinConfig{Mode: machine.PinOutput})
led.Low()
// Make sure the pin is configured as a pullup to avoid floating inputs.
// Pullup works for most buttons, as most buttons short to ground when
// pressed.
button.Configure(machine.PinConfig{Mode: buttonMode})
// Set an interrupt on this pin.
err := button.SetInterrupt(buttonPinChange, func(machine.Pin) {
if lightLed.Get() != 0 {
lightLed.Set(0)
led.Low()
} else {
lightLed.Set(1)
led.High()
}
})
if err != nil {
println("could not configure pin interrupt:", err.Error())
}
// Make sure the program won't exit.
for {
time.Sleep(time.Hour)
}
}
-10
View File
@@ -1,10 +0,0 @@
// +build wioterminal
package main
import "machine"
const (
buttonMode = machine.PinInput
buttonPinChange = machine.PinFalling
)
+6 -16
View File
@@ -8,9 +8,9 @@ import (
// This example assumes that an RGB LED is connected to pins 3, 5 and 6 on an Arduino.
// Change the values below to use different pins.
const (
redPin = machine.D4
greenPin = machine.D5
bluePin = machine.D6
redPin = 3
greenPin = 5
bluePin = 6
)
// cycleColor is just a placeholder until math/rand or some equivalent is working.
@@ -28,16 +28,13 @@ func main() {
machine.InitPWM()
red := machine.PWM{redPin}
err := red.Configure()
checkError(err, "failed to configure red pin")
red.Configure()
green := machine.PWM{greenPin}
err = green.Configure()
checkError(err, "failed to configure green pin")
green.Configure()
blue := machine.PWM{bluePin}
err = blue.Configure()
checkError(err, "failed to configure blue pin")
blue.Configure()
var rc uint8
var gc uint8 = 20
@@ -55,10 +52,3 @@ func main() {
time.Sleep(time.Millisecond * 500)
}
}
func checkError(err error, msg string) {
if err != nil {
print(msg, ": ", err.Error())
println()
}
}
+7 -10
View File
@@ -5,8 +5,6 @@ import (
"machine"
)
var timerCh = make(chan struct{}, 1)
func main() {
machine.LED.Configure(machine.PinConfig{Mode: machine.PinOutput})
@@ -14,18 +12,17 @@ func main() {
arm.SetupSystemTimer(machine.CPUFrequency() / 10)
for {
machine.LED.Low()
<-timerCh
machine.LED.High()
<-timerCh
}
}
var led_state bool
//export SysTick_Handler
func timer_isr() {
select {
case timerCh <- struct{}{}:
default:
// The consumer is running behind.
if led_state {
machine.LED.Low()
} else {
machine.LED.High()
}
led_state = !led_state
}
-253
View File
@@ -1,253 +0,0 @@
package bytealg
const (
// Index can search any valid length of string.
MaxLen = int(-1) >> 31
MaxBruteForce = MaxLen
)
// Compare two byte slices.
// Returns -1 if the first differing byte is lower in a, or 1 if the first differing byte is greater in b.
// If the byte slices are equal, returns 0.
// If the lengths are different and there are no differing bytes, compares based on length.
func Compare(a, b []byte) int {
// Compare for differing bytes.
for i := 0; i < len(a) && i < len(b); i++ {
switch {
case a[0] < b[0]:
return -1
case a[0] > b[0]:
return 1
}
}
// Compare lengths.
switch {
case len(a) > len(b):
return 1
case len(a) < len(b):
return -1
default:
return 0
}
}
// Count the number of instances of a byte in a slice.
func Count(b []byte, c byte) int {
// Use a simple implementation, as there is no intrinsic that does this like we want.
n := 0
for _, v := range b {
if v == c {
n++
}
}
return n
}
// Count the number of instances of a byte in a string.
func CountString(s string, c byte) int {
// Use a simple implementation, as there is no intrinsic that does this like we want.
// Currently, the compiler does not generate zero-copy byte-string conversions, so this needs to be seperate from Count.
n := 0
for i := 0; i < len(s); i++ {
if s[i] == c {
n++
}
}
return n
}
// Cutover is not reachable in TinyGo, but must exist as it is referenced.
func Cutover(n int) int {
// Setting MaxLen and MaxBruteForce should force a different path to be taken.
// This should never be called.
panic("cutover is unreachable")
}
// Equal checks if two byte slices are equal.
// It is equivalent to bytes.Equal.
func Equal(a, b []byte) bool {
if len(a) != len(b) {
return false
}
for i, v := range a {
if v != b[i] {
return false
}
}
return true
}
// Index finds the base index of the first instance of the byte sequence b in a.
// If a does not contain b, this returns -1.
func Index(a, b []byte) int {
for i := 0; i <= len(a)-len(b); i++ {
if Equal(a[i:i+len(b)], b) {
return i
}
}
return -1
}
// Index finds the index of the first instance of the specified byte in the slice.
// If the byte is not found, this returns -1.
func IndexByte(b []byte, c byte) int {
for i, v := range b {
if v == c {
return i
}
}
return -1
}
// Index finds the index of the first instance of the specified byte in the string.
// If the byte is not found, this returns -1.
func IndexByteString(s string, c byte) int {
for i := 0; i < len(s); i++ {
if s[i] == c {
return i
}
}
return -1
}
// Index finds the base index of the first instance of a substring in a string.
// If the substring is not found, this returns -1.
func IndexString(str, sub string) int {
for i := 0; i <= len(str)-len(sub); i++ {
if str[i:i+len(sub)] == sub {
return i
}
}
return -1
}
// Copyright 2020 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.
// The following code has been copied from the Go 1.15 release tree.
// PrimeRK is the prime base used in Rabin-Karp algorithm.
const PrimeRK = 16777619
// HashStrBytes returns the hash and the appropriate multiplicative
// factor for use in Rabin-Karp algorithm.
func HashStrBytes(sep []byte) (uint32, uint32) {
hash := uint32(0)
for i := 0; i < len(sep); i++ {
hash = hash*PrimeRK + uint32(sep[i])
}
var pow, sq uint32 = 1, PrimeRK
for i := len(sep); i > 0; i >>= 1 {
if i&1 != 0 {
pow *= sq
}
sq *= sq
}
return hash, pow
}
// HashStr returns the hash and the appropriate multiplicative
// factor for use in Rabin-Karp algorithm.
func HashStr(sep string) (uint32, uint32) {
hash := uint32(0)
for i := 0; i < len(sep); i++ {
hash = hash*PrimeRK + uint32(sep[i])
}
var pow, sq uint32 = 1, PrimeRK
for i := len(sep); i > 0; i >>= 1 {
if i&1 != 0 {
pow *= sq
}
sq *= sq
}
return hash, pow
}
// HashStrRevBytes returns the hash of the reverse of sep and the
// appropriate multiplicative factor for use in Rabin-Karp algorithm.
func HashStrRevBytes(sep []byte) (uint32, uint32) {
hash := uint32(0)
for i := len(sep) - 1; i >= 0; i-- {
hash = hash*PrimeRK + uint32(sep[i])
}
var pow, sq uint32 = 1, PrimeRK
for i := len(sep); i > 0; i >>= 1 {
if i&1 != 0 {
pow *= sq
}
sq *= sq
}
return hash, pow
}
// HashStrRev returns the hash of the reverse of sep and the
// appropriate multiplicative factor for use in Rabin-Karp algorithm.
func HashStrRev(sep string) (uint32, uint32) {
hash := uint32(0)
for i := len(sep) - 1; i >= 0; i-- {
hash = hash*PrimeRK + uint32(sep[i])
}
var pow, sq uint32 = 1, PrimeRK
for i := len(sep); i > 0; i >>= 1 {
if i&1 != 0 {
pow *= sq
}
sq *= sq
}
return hash, pow
}
// IndexRabinKarpBytes uses the Rabin-Karp search algorithm to return the index of the
// first occurence of substr in s, or -1 if not present.
func IndexRabinKarpBytes(s, sep []byte) int {
// Rabin-Karp search
hashsep, pow := HashStrBytes(sep)
n := len(sep)
var h uint32
for i := 0; i < n; i++ {
h = h*PrimeRK + uint32(s[i])
}
if h == hashsep && Equal(s[:n], sep) {
return 0
}
for i := n; i < len(s); {
h *= PrimeRK
h += uint32(s[i])
h -= pow * uint32(s[i-n])
i++
if h == hashsep && Equal(s[i-n:i], sep) {
return i - n
}
}
return -1
}
// IndexRabinKarp uses the Rabin-Karp search algorithm to return the index of the
// first occurence of substr in s, or -1 if not present.
func IndexRabinKarp(s, substr string) int {
// Rabin-Karp search
hashss, pow := HashStr(substr)
n := len(substr)
var h uint32
for i := 0; i < n; i++ {
h = h*PrimeRK + uint32(s[i])
}
if h == hashss && s[:n] == substr {
return 0
}
for i := n; i < len(s); {
h *= PrimeRK
h += uint32(s[i])
h -= pow * uint32(s[i-n])
i++
if h == hashss && s[i-n:i] == substr {
return i - n
}
}
return -1
}
+2 -28
View File
@@ -1,7 +1,5 @@
package task
import "runtime/interrupt"
const asserts = false
// Queue is a FIFO container of tasks.
@@ -12,9 +10,7 @@ type Queue struct {
// Push a task onto the queue.
func (q *Queue) Push(t *Task) {
i := interrupt.Disable()
if asserts && t.Next != nil {
interrupt.Restore(i)
panic("runtime: pushing a task to a queue with a non-nil Next pointer")
}
if q.tail != nil {
@@ -25,15 +21,12 @@ func (q *Queue) Push(t *Task) {
if q.head == nil {
q.head = t
}
interrupt.Restore(i)
}
// Pop a task off of the queue.
func (q *Queue) Pop() *Task {
i := interrupt.Disable()
t := q.head
if t == nil {
interrupt.Restore(i)
return nil
}
q.head = t.Next
@@ -41,13 +34,11 @@ func (q *Queue) Pop() *Task {
q.tail = nil
}
t.Next = nil
interrupt.Restore(i)
return t
}
// Append pops the contents of another queue and pushes them onto the end of this queue.
func (q *Queue) Append(other *Queue) {
i := interrupt.Disable()
if q.head == nil {
q.head = other.head
} else {
@@ -55,15 +46,6 @@ func (q *Queue) Append(other *Queue) {
}
q.tail = other.tail
other.head, other.tail = nil, nil
interrupt.Restore(i)
}
// Empty checks if the queue is empty.
func (q *Queue) Empty() bool {
i := interrupt.Disable()
empty := q.head == nil
interrupt.Restore(i)
return empty
}
// Stack is a LIFO container of tasks.
@@ -75,24 +57,19 @@ type Stack struct {
// Push a task onto the stack.
func (s *Stack) Push(t *Task) {
i := interrupt.Disable()
if asserts && t.Next != nil {
interrupt.Restore(i)
panic("runtime: pushing a task to a stack with a non-nil Next pointer")
}
s.top, t.Next = t, s.top
interrupt.Restore(i)
}
// Pop a task off of the stack.
func (s *Stack) Pop() *Task {
i := interrupt.Disable()
t := s.top
if t != nil {
s.top = t.Next
t.Next = nil
}
interrupt.Restore(i)
t.Next = nil
return t
}
@@ -112,13 +89,10 @@ func (t *Task) tail() *Task {
// Queue moves the contents of the stack into a queue.
// Elements can be popped from the queue in the same order that they would be popped from the stack.
func (s *Stack) Queue() Queue {
i := interrupt.Disable()
head := s.top
s.top = nil
q := Queue{
return Queue{
head: head,
tail: head.tail(),
}
interrupt.Restore(i)
return q
}
-5
View File
@@ -18,8 +18,3 @@ type Task struct {
// state is the underlying running state of the task.
state state
}
// getGoroutineStackSize is a compiler intrinsic that returns the stack size for
// the given function and falls back to the default stack size. It is replaced
// with a load from a special section just before codegen.
func getGoroutineStackSize(fn uintptr) uintptr
+1 -6
View File
@@ -67,7 +67,7 @@ func createTask() *Task {
// start invokes a function in a new goroutine. Calls to this are inserted by the compiler.
// The created goroutine starts running immediately.
// This is implemented inside the compiler.
func start(fn uintptr, args unsafe.Pointer, stackSize uintptr)
func start(fn uintptr, args unsafe.Pointer)
// Current returns the current active task.
// This is implemented inside the compiler.
@@ -85,14 +85,9 @@ type taskHolder interface {
getReturnPtr() unsafe.Pointer
}
// If there are no direct references to the task methods, they will not be discovered by the compiler, and this will trigger a compiler error.
// Instantiating this interface forces discovery of these methods.
var _ = taskHolder((*Task)(nil))
func fake() {
// Hack to ensure intrinsics are discovered.
Current()
go func() {}()
Pause()
}
+1 -1
View File
@@ -17,7 +17,7 @@ func Current() *Task {
}
//go:noinline
func start(fn uintptr, args unsafe.Pointer, stackSize uintptr) {
func start(fn uintptr, args unsafe.Pointer) {
// The compiler will error if this is reachable.
runtimePanic("scheduler is disabled")
}
+3 -3
View File
@@ -54,7 +54,7 @@ func (t *Task) Resume() {
}
// initialize the state and prepare to call the specified function with the specified argument bundle.
func (s *state) initialize(fn uintptr, args unsafe.Pointer, stackSize uintptr) {
func (s *state) initialize(fn uintptr, args unsafe.Pointer) {
// Create a stack.
stack := make([]uintptr, stackSize/unsafe.Sizeof(uintptr(0)))
@@ -67,9 +67,9 @@ func runqueuePushBack(*Task)
// start creates and starts a new goroutine with the given function and arguments.
// The new goroutine is scheduled to run later.
func start(fn uintptr, args unsafe.Pointer, stackSize uintptr) {
func start(fn uintptr, args unsafe.Pointer) {
t := &Task{}
t.state.initialize(fn, args, stackSize)
t.state.initialize(fn, args)
runqueuePushBack(t)
}
+2
View File
@@ -4,6 +4,8 @@ package task
import "unsafe"
const stackSize = 256
// calleeSavedRegs is the list of registers that must be saved and restored when
// switching between tasks. Also see scheduler_avr.S that relies on the
// exact layout of this struct.
+2
View File
@@ -4,6 +4,8 @@ package task
import "unsafe"
const stackSize = 1024
// calleeSavedRegs is the list of registers that must be saved and restored when
// switching between tasks. Also see scheduler_cortexm.S that relies on the
// exact layout of this struct.
+9 -27
View File
@@ -7,39 +7,21 @@ func CPUFrequency() uint32 {
return 16000000
}
// Digital pins, marked as plain numbers on the board.
const (
D0 = PD0 // RX
D1 = PD1 // TX
D2 = PD2
D3 = PD3
D4 = PD4
D5 = PD5
D6 = PD6
D7 = PD7
D8 = PB0
D9 = PB1
D10 = PB2
D11 = PB3
D12 = PB4
D13 = PB5
)
// LED on the Arduino
const LED Pin = D13
const LED Pin = 13
// ADC on the Arduino
const (
ADC0 Pin = PC0
ADC1 Pin = PC1
ADC2 Pin = PC2
ADC3 Pin = PC3
ADC4 Pin = PC4 // Used by TWI for SDA
ADC5 Pin = PC5 // Used by TWI for SCL
ADC0 Pin = 0
ADC1 Pin = 1
ADC2 Pin = 2
ADC3 Pin = 3
ADC4 Pin = 4 // Used by TWI for SDA
ADC5 Pin = 5 // Used by TWI for SCL
)
// UART pins
const (
UART_TX_PIN Pin = PD1
UART_RX_PIN Pin = PD0
UART_TX_PIN Pin = 1
UART_RX_PIN Pin = 0
)
+1 -1
View File
@@ -49,7 +49,7 @@ const (
// Digital pins
D0 Pin = PE0
D1 Pin = PE1
D2 Pin = PE4
D2 Pin = PE6
D3 Pin = PE5
D4 Pin = PG5
D5 Pin = PE3
+9 -27
View File
@@ -7,39 +7,21 @@ func CPUFrequency() uint32 {
return 16000000
}
// Digital pins.
const (
D0 = PD0 // RX0
D1 = PD1 // TX1
D2 = PD2
D3 = PD3
D4 = PD4
D5 = PD5
D6 = PD6
D7 = PD7
D8 = PB0
D9 = PB1
D10 = PB2
D11 = PB3
D12 = PB4
D13 = PB5
)
// LED on the Arduino
const LED Pin = D13
const LED Pin = 13
// ADC on the Arduino
const (
ADC0 Pin = PC0
ADC1 Pin = PC1
ADC2 Pin = PC2
ADC3 Pin = PC3
ADC4 Pin = PC4 // Used by TWI for SDA
ADC5 Pin = PC5 // Used by TWI for SCL
ADC0 Pin = 0
ADC1 Pin = 1
ADC2 Pin = 2
ADC3 Pin = 3
ADC4 Pin = 4 // Used by TWI for SDA
ADC5 Pin = 5 // Used by TWI for SCL
)
// UART pins
const (
UART_TX_PIN Pin = PD1
UART_RX_PIN Pin = PD0
UART_TX_PIN Pin = 1
UART_RX_PIN Pin = 0
)
+5 -5
View File
@@ -66,15 +66,15 @@ const (
// SPI pins
const (
SPI0_SCK_PIN Pin = D13 // SCK: SERCOM1/PAD[1]
SPI0_SDO_PIN Pin = D11 // SDO: SERCOM1/PAD[0]
SPI0_SDI_PIN Pin = D12 // SDI: SERCOM1/PAD[3]
SPI0_SCK_PIN Pin = D13 // SCK: SERCOM1/PAD[1]
SPI0_MOSI_PIN Pin = D11 // MOSI: SERCOM1/PAD[0]
SPI0_MISO_PIN Pin = D12 // MISO: SERCOM1/PAD[3]
)
// NINA-W102 Pins
const (
NINA_SDO Pin = PA12
NINA_SDI Pin = PA13
NINA_MOSI Pin = PA12
NINA_MISO Pin = PA13
NINA_CS Pin = PA14
NINA_SCK Pin = PA15
NINA_GPIO0 Pin = PA27
-101
View File
@@ -1,101 +0,0 @@
// +build sam,atsamd21,arduino_zero
package machine
// used to reset into bootloader
const RESET_MAGIC_VALUE = 0x07738135
// GPIO Pins - Digital Low
const (
D0 = PA11 // RX
D1 = PA10 // TX
D2 = PA14
D3 = PA09 // PWM available
D4 = PA08 // PWM available
D5 = PA15 // PWM available
D6 = PA20 // PWM available
D7 = PA21
)
// GPIO Pins - Digital High
const (
D8 = PA06 // PWM available
D9 = PA07 // PWM available
D10 = PA18 // PWM available
D11 = PA16 // PWM available
D12 = PA19 // PWM available
D13 = PA17 // PWM available
)
// LEDs on the Arduino Zero
const (
LED = LED1
LED1 Pin = D13
LED2 Pin = PA27 // TX LED
LED3 Pin = PB03 // RX LED
)
// ADC pins
const (
AREF Pin = PA03
ADC0 Pin = PA02
ADC1 Pin = PB08
ADC2 Pin = PB09
ADC3 Pin = PA04
ADC4 Pin = PA05
ADC5 Pin = PB02
)
// SPI pins - EDBG connected
const (
SPI0_SDO_PIN Pin = PA16 // MOSI: SERCOM1/PAD[0]
SPI0_SDI_PIN Pin = PA19 // MISO: SERCOM1/PAD[2]
SPI0_SCK_PIN Pin = PA17 // SCK: SERCOM1/PAD[3]
)
// SPI pins (Legacy ICSP)
const (
SPI1_SDO_PIN Pin = PB10 // MOSI: SERCOM4/PAD[2] - Pin 4
SPI1_SDI_PIN Pin = PA12 // MISO: SERCOM4/PAD[0] - Pin 1
SPI1_SCK_PIN Pin = PB11 // SCK: SERCOM4/PAD[3] - Pin 3
)
// I2C pins - EDBG connected
const (
SDA_PIN Pin = PA22 // SDA: SERCOM3/PAD[0] - Pin 20
SCL_PIN Pin = PA23 // SCL: SERCOM3/PAD[1] - Pin 21
)
// I2S pins - might not be exposed
const (
I2S_SCK_PIN Pin = PA10
I2S_SD_PIN Pin = PA07
I2S_WS_PIN Pin = PA11
)
// UART0 pins - EDBG connected
const (
UART_RX_PIN Pin = D0
UART_TX_PIN Pin = D1
)
// 'native' USB port pins
const (
USBCDC_DM_PIN Pin = PA24
USBCDC_DP_PIN Pin = PA25
)
// USB CDC identifiers
const (
usb_STRING_PRODUCT = "Arduino Zero"
usb_STRING_MANUFACTURER = "Arduino LLC"
usb_VID uint16 = 0x2341
usb_PID uint16 = 0x804d
)
// 32.768 KHz Crystal
const (
XIN32 Pin = PA00
XOUT32 Pin = PA01
)
-37
View File
@@ -1,37 +0,0 @@
// +build avr,atmega328p arduino arduino_nano
package machine
const (
// Note: start at port B because there is no port A.
portB Pin = iota * 8
portC
portD
)
const (
PB0 = portB + 0
PB1 = portB + 1
PB2 = portB + 2
PB3 = portB + 3
PB4 = portB + 4
PB5 = portB + 5
PB6 = portB + 6
PB7 = portB + 7
PC0 = portC + 0
PC1 = portC + 1
PC2 = portC + 2
PC3 = portC + 3
PC4 = portC + 4
PC5 = portC + 5
PC6 = portC + 6
PC7 = portC + 7
PD0 = portD + 0
PD1 = portD + 1
PD2 = portD + 2
PD3 = portD + 3
PD4 = portD + 4
PD5 = portD + 5
PD6 = portD + 6
PD7 = portD + 7
)
+4 -8
View File
@@ -65,22 +65,18 @@ var (
Buffer: NewRingBuffer(),
Bus: stm32.USART1,
}
UART1 = UART{
Buffer: NewRingBuffer(),
Bus: stm32.USART2,
}
UART1 = &UART0
)
func init() {
UART0.Interrupt = interrupt.New(stm32.IRQ_USART1, UART0.handleInterrupt)
UART1.Interrupt = interrupt.New(stm32.IRQ_USART2, UART1.handleInterrupt)
}
// SPI pins
const (
SPI0_SCK_PIN = PA5
SPI0_SDO_PIN = PA7
SPI0_SDI_PIN = PA6
SPI0_SCK_PIN = PA5
SPI0_MOSI_PIN = PA7
SPI0_MISO_PIN = PA6
)
// I2C pins
+4 -4
View File
@@ -2,7 +2,7 @@
package machine
const HasLowFrequencyCrystal = false
const HasLowFrequencyCrystal = true
// GPIO Pins
const (
@@ -72,9 +72,9 @@ const (
// SPI pins (internal flash)
const (
SPI0_SCK_PIN = P0_19 // SCK
SPI0_SDO_PIN = P0_21 // SDO
SPI0_SDI_PIN = P0_23 // SDI
SPI0_SCK_PIN = P0_19 // SCK
SPI0_MOSI_PIN = P0_21 // MOSI
SPI0_MISO_PIN = P0_23 // MISO
)
// USB CDC identifiers

Some files were not shown because too many files have changed in this diff Show More