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 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 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 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: llvm-source-linux:
steps: steps:
- restore_cache: - restore_cache:
keys: keys:
- llvm-source-10-v1 - llvm-source-10-v0
- run: - run:
name: "Fetch LLVM source" name: "Fetch LLVM source"
command: make llvm-source command: make llvm-source
- save_cache: - save_cache:
key: llvm-source-10-v1 key: llvm-source-10-v0
paths: paths:
- llvm-project - llvm-project
build-wasi-libc: build-wasi-libc:
steps: steps:
- restore_cache: - restore_cache:
keys: keys:
- wasi-libc-sysroot-v3 - wasi-libc-sysroot-v2
- run: - run:
name: "Build wasi-libc" name: "Build wasi-libc"
command: make wasi-libc command: make wasi-libc
- save_cache: - save_cache:
key: wasi-libc-sysroot-v3 key: wasi-libc-sysroot-v2
paths: paths:
- lib/wasi-libc/sysroot - lib/wasi-libc/sysroot
test-linux: test-linux:
@@ -90,7 +71,6 @@ commands:
- apt-dependencies: - apt-dependencies:
llvm: "<<parameters.llvm>>" llvm: "<<parameters.llvm>>"
- install-node - install-node
- install-chrome
- restore_cache: - restore_cache:
keys: keys:
- go-cache-v2-{{ checksum "go.mod" }}-{{ .Environment.CIRCLE_PREVIOUS_BUILD_NUM }} - go-cache-v2-{{ checksum "go.mod" }}-{{ .Environment.CIRCLE_PREVIOUS_BUILD_NUM }}
@@ -99,21 +79,20 @@ commands:
- run: go install -tags=llvm<<parameters.llvm>> . - run: go install -tags=llvm<<parameters.llvm>> .
- restore_cache: - restore_cache:
keys: keys:
- wasi-libc-sysroot-systemclang-v2 - wasi-libc-sysroot-systemclang-v1
- run: make wasi-libc - run: make wasi-libc
- save_cache: - save_cache:
key: wasi-libc-sysroot-systemclang-v2 key: wasi-libc-sysroot-systemclang-v1
paths: paths:
- lib/wasi-libc/sysroot - 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 gen-device -j4
- run: make smoketest XTENSA=0 - run: make smoketest
- run: make tinygo-test
- run: make wasmtest
- save_cache: - save_cache:
key: go-cache-v2-{{ checksum "go.mod" }}-{{ .Environment.CIRCLE_BUILD_NUM }} key: go-cache-v2-{{ checksum "go.mod" }}-{{ .Environment.CIRCLE_BUILD_NUM }}
paths: paths:
- ~/.cache/go-build - ~/.cache/go-build
- ~/.cache/tinygo
- /go/pkg/mod - /go/pkg/mod
- run: make fmt-check - run: make fmt-check
assert-test-linux: assert-test-linux:
@@ -134,8 +113,6 @@ commands:
gcc-avr \ gcc-avr \
avr-libc avr-libc
- install-node - install-node
- install-xtensa-toolchain:
variant: "linux-amd64"
- restore_cache: - restore_cache:
keys: keys:
- go-cache-v2-{{ checksum "go.mod" }}-{{ .Environment.CIRCLE_PREVIOUS_BUILD_NUM }} - go-cache-v2-{{ checksum "go.mod" }}-{{ .Environment.CIRCLE_PREVIOUS_BUILD_NUM }}
@@ -143,7 +120,7 @@ commands:
- llvm-source-linux - llvm-source-linux
- restore_cache: - restore_cache:
keys: keys:
- llvm-build-10-linux-v1-assert - llvm-build-10-linux-v0-assert
- run: - run:
name: "Build LLVM" name: "Build LLVM"
command: | command: |
@@ -161,7 +138,7 @@ commands:
make ASSERT=1 llvm-build make ASSERT=1 llvm-build
fi fi
- save_cache: - save_cache:
key: llvm-build-10-linux-v1-assert key: llvm-build-10-linux-v0-assert
paths: paths:
llvm-build llvm-build
- run: make ASSERT=1 - run: make ASSERT=1
@@ -173,6 +150,7 @@ commands:
key: go-cache-v2-{{ checksum "go.mod" }}-{{ .Environment.CIRCLE_BUILD_NUM }} key: go-cache-v2-{{ checksum "go.mod" }}-{{ .Environment.CIRCLE_BUILD_NUM }}
paths: paths:
- ~/.cache/go-build - ~/.cache/go-build
- ~/.cache/tinygo
- /go/pkg/mod - /go/pkg/mod
- run: make gen-device -j4 - run: make gen-device -j4
- run: make smoketest TINYGO=build/tinygo - run: make smoketest TINYGO=build/tinygo
@@ -194,8 +172,6 @@ commands:
gcc-avr \ gcc-avr \
avr-libc avr-libc
- install-node - install-node
- install-xtensa-toolchain:
variant: "linux-amd64"
- restore_cache: - restore_cache:
keys: keys:
- go-cache-v2-{{ checksum "go.mod" }}-{{ .Environment.CIRCLE_PREVIOUS_BUILD_NUM }} - go-cache-v2-{{ checksum "go.mod" }}-{{ .Environment.CIRCLE_PREVIOUS_BUILD_NUM }}
@@ -203,7 +179,7 @@ commands:
- llvm-source-linux - llvm-source-linux
- restore_cache: - restore_cache:
keys: keys:
- llvm-build-10-linux-v1 - llvm-build-10-linux-v0
- run: - run:
name: "Build LLVM" name: "Build LLVM"
command: | command: |
@@ -221,32 +197,25 @@ commands:
make llvm-build make llvm-build
fi fi
- save_cache: - save_cache:
key: llvm-build-10-linux-v1 key: llvm-build-10-linux-v0
paths: paths:
llvm-build llvm-build
- build-wasi-libc - build-wasi-libc
- run: - run:
name: "Test TinyGo" name: "Test TinyGo"
command: make test command: make test
- run:
name: "Install fpm"
command: |
sudo apt-get install ruby ruby-dev
sudo gem install --no-document fpm
- run: - run:
name: "Build TinyGo release" name: "Build TinyGo release"
command: | command: |
make release deb -j3 make release -j3
cp -p build/release.tar.gz /tmp/tinygo.linux-amd64.tar.gz cp -p build/release.tar.gz /tmp/tinygo.linux-amd64.tar.gz
cp -p build/release.deb /tmp/tinygo_amd64.deb
- store_artifacts: - store_artifacts:
path: /tmp/tinygo.linux-amd64.tar.gz path: /tmp/tinygo.linux-amd64.tar.gz
- store_artifacts:
path: /tmp/tinygo_amd64.deb
- save_cache: - save_cache:
key: go-cache-v2-{{ checksum "go.mod" }}-{{ .Environment.CIRCLE_BUILD_NUM }} key: go-cache-v2-{{ checksum "go.mod" }}-{{ .Environment.CIRCLE_BUILD_NUM }}
paths: paths:
- ~/.cache/go-build - ~/.cache/go-build
- ~/.cache/tinygo
- /go/pkg/mod - /go/pkg/mod
- run: - run:
name: "Extract release tarball" name: "Extract release tarball"
@@ -267,25 +236,23 @@ commands:
sudo tar -C /usr/local -xzf go1.14.darwin-amd64.tar.gz sudo tar -C /usr/local -xzf go1.14.darwin-amd64.tar.gz
ln -s /usr/local/go/bin/go /usr/local/bin/go ln -s /usr/local/go/bin/go /usr/local/bin/go
HOMEBREW_NO_AUTO_UPDATE=1 brew install qemu HOMEBREW_NO_AUTO_UPDATE=1 brew install qemu
- install-xtensa-toolchain:
variant: "macos"
- restore_cache: - restore_cache:
keys: keys:
- go-cache-macos-v2-{{ checksum "go.mod" }}-{{ .Environment.CIRCLE_PREVIOUS_BUILD_NUM }} - go-cache-macos-v2-{{ checksum "go.mod" }}-{{ .Environment.CIRCLE_PREVIOUS_BUILD_NUM }}
- go-cache-macos-v2-{{ checksum "go.mod" }} - go-cache-macos-v2-{{ checksum "go.mod" }}
- restore_cache: - restore_cache:
keys: keys:
- llvm-source-10-macos-v1 - llvm-source-10-macos-v0
- run: - run:
name: "Fetch LLVM source" name: "Fetch LLVM source"
command: make llvm-source command: make llvm-source
- save_cache: - save_cache:
key: llvm-source-10-macos-v1 key: llvm-source-10-macos-v0
paths: paths:
- llvm-project - llvm-project
- restore_cache: - restore_cache:
keys: keys:
- llvm-build-10-macos-v1 - llvm-build-10-macos-v0
- run: - run:
name: "Build LLVM" name: "Build LLVM"
command: | command: |
@@ -297,17 +264,17 @@ commands:
make llvm-build make llvm-build
fi fi
- save_cache: - save_cache:
key: llvm-build-10-macos-v1 key: llvm-build-10-macos-v0
paths: paths:
llvm-build llvm-build
- restore_cache: - restore_cache:
keys: keys:
- wasi-libc-sysroot-macos-v2 - wasi-libc-sysroot-macos-v1
- run: - run:
name: "Build wasi-libc" name: "Build wasi-libc"
command: make wasi-libc command: make wasi-libc
- save_cache: - save_cache:
key: wasi-libc-sysroot-macos-v2 key: wasi-libc-sysroot-macos-v1
paths: paths:
- lib/wasi-libc/sysroot - lib/wasi-libc/sysroot
- run: - run:
@@ -327,66 +294,19 @@ commands:
tar -C /usr/local/opt -xf /tmp/tinygo.darwin-amd64.tar.gz tar -C /usr/local/opt -xf /tmp/tinygo.darwin-amd64.tar.gz
ln -s /usr/local/opt/tinygo/bin/tinygo /usr/local/bin/tinygo ln -s /usr/local/opt/tinygo/bin/tinygo /usr/local/bin/tinygo
tinygo version 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 - run: make smoketest AVR=0
- save_cache: - save_cache:
key: go-cache-macos-v2-{{ checksum "go.mod" }}-{{ .Environment.CIRCLE_BUILD_NUM }} key: go-cache-macos-v2-{{ checksum "go.mod" }}-{{ .Environment.CIRCLE_BUILD_NUM }}
paths: paths:
- ~/.cache/go-build - ~/.cache/go-build
- ~/.cache/tinygo
- /go/pkg/mod - /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: jobs:
test-llvm9-go111: test-llvm9-go111:
@@ -413,12 +333,6 @@ jobs:
steps: steps:
- test-linux: - test-linux:
llvm: "10" llvm: "10"
test-llvm10-go115:
docker:
- image: circleci/golang:1.15-buster
steps:
- test-linux:
llvm: "10"
assert-test-linux: assert-test-linux:
docker: docker:
- image: circleci/golang:1.14-stretch - image: circleci/golang:1.14-stretch
@@ -434,11 +348,6 @@ jobs:
xcode: "10.1.0" xcode: "10.1.0"
steps: steps:
- build-macos - build-macos
arch-release:
docker:
- image: archlinux:latest
steps:
- arch-release
@@ -450,16 +359,6 @@ workflows:
- test-llvm10-go112 - test-llvm10-go112
- test-llvm10-go113 - test-llvm10-go113
- test-llvm10-go114 - test-llvm10-go114
- test-llvm10-go115
- build-linux - build-linux
- build-macos - build-macos
- assert-test-linux - 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/*.go
src/device/avr/*.ld src/device/avr/*.ld
src/device/avr/*.s src/device/avr/*.s
src/device/esp/*.go
src/device/nrf/*.go src/device/nrf/*.go
src/device/nrf/*.s src/device/nrf/*.s
src/device/nxp/*.go
src/device/nxp/*.s
src/device/sam/*.go src/device/sam/*.go
src/device/sam/*.s src/device/sam/*.s
src/device/sifive/*.go src/device/sifive/*.go
src/device/sifive/*.s src/device/sifive/*.s
src/device/stm32/*.go src/device/stm32/*.go
src/device/stm32/*.s src/device/stm32/*.s
src/device/kendryte/*.go
src/device/kendryte/*.s
vendor vendor
llvm-build llvm-build
llvm-project llvm-project
+1 -1
View File
@@ -9,7 +9,7 @@
url = https://github.com/avr-rust/avr-mcu.git url = https://github.com/avr-rust/avr-mcu.git
[submodule "lib/cmsis-svd"] [submodule "lib/cmsis-svd"]
path = 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"] [submodule "lib/compiler-rt"]
path = lib/compiler-rt path = lib/compiler-rt
url = https://github.com/llvm-mirror/compiler-rt.git url = https://github.com/llvm-mirror/compiler-rt.git
-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 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. 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 ## Dependencies
LLVM, Clang and LLD are quite light on dependencies, requiring only standard 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 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 release tarball is stored in build/release.tar.gz, and can be extracted with
the following command (for example in ~/lib): 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 0.13.1
--- ---
* **standard library** * **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 ## 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: 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> Daniel Esteban <conejo@conejo.me>
Loon, LLC. Loon, LLC.
Ron Evans <ron@hybridgroup.com> 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 - && \ 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 && \ echo "deb http://apt.llvm.org/buster/ llvm-toolchain-buster-10 main" >> /etc/apt/sources.list && \
apt-get update && \ 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 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/src /tinygo/src
COPY --from=tinygo-base /tinygo/targets /tinygo/targets 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 update && \
apt-get install -y make clang-10 libllvm10 lld-10 && \ apt-get install -y libllvm10 lld-10
make wasi-libc
# tinygo-avr stage installs the needed dependencies to compile TinyGo programs for AVR microcontrollers. # tinygo-avr stage installs the needed dependencies to compile TinyGo programs for AVR microcontrollers.
FROM tinygo-base AS tinygo-avr FROM tinygo-base AS tinygo-avr
+11 -89
View File
@@ -51,7 +51,7 @@ else
LLVM_OPTION += '-DLLVM_ENABLE_ASSERTIONS=OFF' LLVM_OPTION += '-DLLVM_ENABLE_ASSERTIONS=OFF'
endif 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 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: clean:
@rm -rf build @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: fmt:
@gofmt -l -w $(FMT_PATHS) @gofmt -l -w $(FMT_PATHS)
fmt-check: fmt-check:
@unformatted=$$(gofmt -l $(FMT_PATHS)); [ -z "$$unformatted" ] && exit 0; echo "Unformatted:"; for fn in $$unformatted; do echo " $$fn"; done; exit 1 @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: 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/ $(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/atmega src/device/avr/
./build/gen-device-avr lib/avr/packs/tiny 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 build/gen-device-svd: ./tools/gen-device-svd/*.go
$(GO) build -o $@ ./tools/gen-device-svd/ $(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 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/ ./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 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 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/ ./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 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/ ./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 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 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/ ./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 GO111MODULE=off $(GO) fmt ./src/device/stm32
@@ -161,13 +148,13 @@ gen-device-stm32: build/gen-device-svd
# Get LLVM sources. # Get LLVM sources.
$(LLVM_PROJECTDIR)/README.md: $(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 llvm-source: $(LLVM_PROJECTDIR)/README.md
# Configure LLVM. # Configure LLVM.
TINYGO_SOURCE_DIR=$(shell pwd) TINYGO_SOURCE_DIR=$(shell pwd)
$(LLVM_BUILDDIR)/build.ninja: llvm-source $(LLVM_BUILDDIR)/build.ninja: llvm-source
mkdir -p $(LLVM_BUILDDIR); cd $(LLVM_BUILDDIR); cmake -G Ninja $(TINYGO_SOURCE_DIR)/$(LLVM_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. # Build LLVM.
$(LLVM_BUILDDIR): $(LLVM_BUILDDIR)/build.ninja $(LLVM_BUILDDIR): $(LLVM_BUILDDIR)/build.ninja
@@ -178,31 +165,24 @@ $(LLVM_BUILDDIR): $(LLVM_BUILDDIR)/build.ninja
.PHONY: wasi-libc .PHONY: wasi-libc
wasi-libc: lib/wasi-libc/sysroot/lib/wasm32-wasi/libc.a wasi-libc: lib/wasi-libc/sysroot/lib/wasm32-wasi/libc.a
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) cd lib/wasi-libc && make -j4 WASM_CC=$(CLANG) WASM_AR=$(LLVM_AR) WASM_NM=$(LLVM_NM)
# Build the Go compiler. # Build the Go compiler.
tinygo: tinygo:
@if [ ! -f "$(LLVM_BUILDDIR)/bin/llvm-config" ]; then echo "Fetch and build LLVM first by running:"; echo " make llvm-source"; echo " make $(LLVM_BUILDDIR)"; exit 1; fi @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 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:
$(TINYGO) test container/list cd tests/tinygotest && tinygo test
$(TINYGO) test container/ring
$(TINYGO) test text/scanner
.PHONY: smoketest .PHONY: smoketest
smoketest: smoketest:
$(TINYGO) version $(TINYGO) version
# test all examples (except pwm) # test all examples
$(TINYGO) build -size short -o test.hex -target=pca10040 examples/blinky1 $(TINYGO) build -size short -o test.hex -target=pca10040 examples/blinky1
@$(MD5SUM) test.hex @$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=pca10040 examples/adc $(TINYGO) build -size short -o test.hex -target=pca10040 examples/adc
@@ -223,7 +203,7 @@ smoketest:
@$(MD5SUM) test.hex @$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=microbit examples/microbit-blink $(TINYGO) build -size short -o test.hex -target=microbit examples/microbit-blink
@$(MD5SUM) test.hex @$(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 @$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=pca10040 examples/serial $(TINYGO) build -size short -o test.hex -target=pca10040 examples/serial
@$(MD5SUM) test.hex @$(MD5SUM) test.hex
@@ -251,8 +231,6 @@ smoketest:
@$(MD5SUM) test.hex @$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=microbit examples/echo $(TINYGO) build -size short -o test.hex -target=microbit examples/echo
@$(MD5SUM) test.hex @$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=microbit-s110v8 examples/echo
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=nrf52840-mdk examples/blinky1 $(TINYGO) build -size short -o test.hex -target=nrf52840-mdk examples/blinky1
@$(MD5SUM) test.hex @$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=pca10031 examples/blinky1 $(TINYGO) build -size short -o test.hex -target=pca10031 examples/blinky1
@@ -279,10 +257,6 @@ smoketest:
@$(MD5SUM) test.hex @$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=stm32f4disco examples/blinky2 $(TINYGO) build -size short -o test.hex -target=stm32f4disco examples/blinky2
@$(MD5SUM) test.hex @$(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 $(TINYGO) build -size short -o test.hex -target=circuitplay-bluefruit examples/blinky1
@$(MD5SUM) test.hex @$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=circuitplay-express examples/i2s $(TINYGO) build -size short -o test.hex -target=circuitplay-express examples/i2s
@@ -317,36 +291,11 @@ smoketest:
@$(MD5SUM) test.hex @$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=reelboard-s140v7 examples/blinky1 $(TINYGO) build -size short -o test.hex -target=reelboard-s140v7 examples/blinky1
@$(MD5SUM) test.hex @$(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) ifneq ($(AVR), 0)
$(TINYGO) build -size short -o test.hex -target=atmega1284p examples/serial $(TINYGO) build -size short -o test.hex -target=atmega1284p examples/serial
@$(MD5SUM) test.hex @$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=arduino examples/blinky1 $(TINYGO) build -size short -o test.hex -target=arduino examples/blinky1
@$(MD5SUM) test.hex @$(MD5SUM) test.hex
$(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 $(TINYGO) build -size short -o test.hex -target=arduino -scheduler=tasks examples/blinky1
@$(MD5SUM) test.hex @$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=arduino-nano examples/blinky1 $(TINYGO) build -size short -o test.hex -target=arduino-nano examples/blinky1
@@ -355,31 +304,13 @@ ifneq ($(AVR), 0)
@$(MD5SUM) test.hex @$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=digispark -gc=leaking examples/blinky1 $(TINYGO) build -size short -o test.hex -target=digispark -gc=leaking examples/blinky1
@$(MD5SUM) test.hex @$(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 endif
$(TINYGO) build -size short -o test.hex -target=hifive1b examples/blinky1 $(TINYGO) build -size short -o test.hex -target=hifive1b examples/blinky1
@$(MD5SUM) test.hex @$(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/export
$(TINYGO) build -o wasm.wasm -target=wasm examples/wasm/main $(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: release: tinygo gen-device wasi-libc
$(GO) test ./tests/wasm
build/release: tinygo gen-device wasi-libc
@mkdir -p build/release/tinygo/bin @mkdir -p build/release/tinygo/bin
@mkdir -p build/release/tinygo/lib/clang/include @mkdir -p build/release/tinygo/lib/clang/include
@mkdir -p build/release/tinygo/lib/CMSIS/CMSIS @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=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=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 ./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 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. 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 Bluefruit](https://www.adafruit.com/product/4333)
* [Adafruit Circuit Playground Express](https://www.adafruit.com/product/3333) * [Adafruit Circuit Playground Express](https://www.adafruit.com/product/3333)
* [Adafruit CLUE Alpha](https://www.adafruit.com/product/4500) * [Adafruit CLUE Alpha](https://www.adafruit.com/product/4500)
* [Adafruit Feather M0](https://www.adafruit.com/product/2772) * [Adafruit Feather M0](https://www.adafruit.com/product/2772)
* [Adafruit Feather M4](https://www.adafruit.com/product/3857) * [Adafruit 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 M0](https://www.adafruit.com/product/3727)
* [Adafruit ItsyBitsy M4](https://www.adafruit.com/product/3800) * [Adafruit ItsyBitsy M4](https://www.adafruit.com/product/3800)
* [Adafruit ItsyBitsy nRF52840](https://www.adafruit.com/product/4481)
* [Adafruit Metro M4 Express Airlift](https://www.adafruit.com/product/4000) * [Adafruit Metro M4 Express Airlift](https://www.adafruit.com/product/4000)
* [Adafruit PyBadge](https://www.adafruit.com/product/4200) * [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 PyPortal](https://www.adafruit.com/product/4116)
* [Adafruit Trinket M0](https://www.adafruit.com/product/3500) * [Adafruit Trinket M0](https://www.adafruit.com/product/3500)
* [Arduino Mega 2560](https://store.arduino.cc/arduino-mega-2560-rev3) * [Arduino Mega 2560](https://store.arduino.cc/arduino-mega-2560-rev3)
* [Arduino Nano](https://store.arduino.cc/arduino-nano) * [Arduino Nano](https://store.arduino.cc/arduino-nano)
* [Arduino Nano33 IoT](https://store.arduino.cc/nano-33-iot) * [Arduino Nano33 IoT](https://store.arduino.cc/nano-33-iot)
* [Arduino Uno](https://store.arduino.cc/arduino-uno-rev3) * [Arduino Uno](https://store.arduino.cc/arduino-uno-rev3)
* [Arduino Zero](https://store.arduino.cc/usa/arduino-zero)
* [BBC micro:bit](https://microbit.org/) * [BBC micro:bit](https://microbit.org/)
* [Digispark](http://digistump.com/products/1) * [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) * [Game Boy Advance](https://en.wikipedia.org/wiki/Game_Boy_Advance)
* [Makerdiary nRF52840-MDK](https://wiki.makerdiary.com/nrf52840-mdk/) * [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 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 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) * [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/) * [Particle Xenon](https://docs.particle.io/datasheets/discontinued/xenon-datasheet/)
* [Phytec reel board](https://www.phytec.eu/product-eu/internet-of-things/reelboard/) * [Phytec reel board](https://www.phytec.eu/product-eu/internet-of-things/reelboard/)
* [PineTime DevKit](https://www.pine64.org/pinetime/) * [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) * [SiFIve HiFive1](https://www.sifive.com/boards/hifive1)
* [ST Micro "Nucleo F103RB"](https://www.st.com/en/evaluation-tools/nucleo-f103rb.html) * [ST Micro "Nucleo F103RB"](https://www.st.com/en/evaluation-tools/nucleo-f103rb.html)
* [ST Micro STM32F103XX "Bluepill"](http://wiki.stm32duino.com/index.php?title=Blue_Pill) * [ST Micro STM32F103XX "Bluepill"](http://wiki.stm32duino.com/index.php?title=Blue_Pill)
@@ -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. 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 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 # Avoid lengthy LLVM rebuilds on each newly pushed branch. Pull requests will
# be built anyway. # be built anyway.
trigger: trigger:
- release - master
- dev - dev
jobs: jobs:
@@ -12,27 +12,22 @@ jobs:
steps: steps:
- task: GoTool@0 - task: GoTool@0
inputs: inputs:
version: '1.15' version: '1.14.1'
- checkout: self - checkout: self
fetchDepth: 1 - task: CacheBeta@0
- task: Cache@2
displayName: Cache LLVM source displayName: Cache LLVM source
inputs: inputs:
key: llvm-source-10-windows-v1 key: llvm-source-10-windows-v0
path: llvm-project path: llvm-project
- task: Bash@3 - task: Bash@3
displayName: Download LLVM source displayName: Download LLVM source
inputs: inputs:
targetType: inline targetType: inline
script: | script: make llvm-source
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
- task: CacheBeta@0 - task: CacheBeta@0
displayName: Cache LLVM build displayName: Cache LLVM build
inputs: inputs:
key: llvm-build-10-windows-v1 key: llvm-build-10-windows-v0
path: llvm-build path: llvm-build
- task: Bash@3 - task: Bash@3
displayName: Build LLVM displayName: Build LLVM
@@ -48,11 +43,11 @@ jobs:
displayName: Install QEMU displayName: Install QEMU
inputs: inputs:
targetType: inline targetType: inline
script: choco install qemu --version=2020.06.12 script: choco install qemu
- task: CacheBeta@0 - task: CacheBeta@0
displayName: Cache wasi-libc sysroot displayName: Cache wasi-libc sysroot
inputs: inputs:
key: wasi-libc-sysroot-v3 key: wasi-libc-sysroot-v2
path: lib/wasi-libc/sysroot path: lib/wasi-libc/sysroot
- task: Bash@3 - task: Bash@3
displayName: Build wasi-libc displayName: Build wasi-libc
@@ -74,7 +69,7 @@ jobs:
script: | script: |
export PATH="$PATH:./llvm-build/bin:/c/Program Files/qemu" export PATH="$PATH:./llvm-build/bin:/c/Program Files/qemu"
unset GOROOT unset GOROOT
make build/release -j4 make release -j4
- publish: $(System.DefaultWorkingDirectory)/build/release/tinygo - publish: $(System.DefaultWorkingDirectory)/build/release/tinygo
displayName: Publish zip as artifact displayName: Publish zip as artifact
artifact: tinygo artifact: tinygo
@@ -85,4 +80,4 @@ jobs:
script: | script: |
export PATH="$PATH:./llvm-build/bin:/c/Program Files/qemu" export PATH="$PATH:./llvm-build/bin:/c/Program Files/qemu"
unset GOROOT unset GOROOT
make smoketest TINYGO=build/tinygo AVR=0 XTENSA=0 make smoketest TINYGO=build/tinygo AVR=0
+7 -319
View File
@@ -4,14 +4,11 @@
package builder package builder
import ( import (
"debug/elf"
"encoding/binary"
"errors" "errors"
"fmt" "fmt"
"io/ioutil" "io/ioutil"
"os" "os"
"path/filepath" "path/filepath"
"sort"
"strconv" "strconv"
"strings" "strings"
@@ -19,40 +16,26 @@ import (
"github.com/tinygo-org/tinygo/compiler" "github.com/tinygo-org/tinygo/compiler"
"github.com/tinygo-org/tinygo/goenv" "github.com/tinygo-org/tinygo/goenv"
"github.com/tinygo-org/tinygo/interp" "github.com/tinygo-org/tinygo/interp"
"github.com/tinygo-org/tinygo/stacksize"
"github.com/tinygo-org/tinygo/transform" "github.com/tinygo-org/tinygo/transform"
"tinygo.org/x/go-llvm" "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 // 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 // name, an output path, and set of compile options and from that it manages the
// whole compilation process. // whole compilation process.
// //
// The error value may be of type *MultiError. Callers will likely want to check // The error value may be of type *MultiError. Callers will likely want to check
// for this case and print such errors individually. // 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. // Compile Go code to IR.
machine, err := compiler.NewTargetMachine(config) machine, err := compiler.NewTargetMachine(config)
if err != nil { if err != nil {
return err return err
} }
buildOutput, errs := compiler.Compile(pkgName, machine, config) mod, extraFiles, errs := compiler.Compile(pkgName, machine, config)
if errs != nil { if errs != nil {
return newMultiError(errs) return newMultiError(errs)
} }
mod := buildOutput.Mod
if config.Options.PrintIR { if config.Options.PrintIR {
fmt.Println("; Generated LLVM IR:") fmt.Println("; Generated LLVM IR:")
@@ -90,15 +73,8 @@ func Build(pkgName, outpath string, config *compileopts.Config, action func(Buil
// exactly. // exactly.
errs = nil errs = nil
switch config.Options.Opt { 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": case "none", "0":
errs = transform.Optimize(mod, config, 0, 0, 0) // -O0 errs = transform.Optimize(mod, config, 0, 0, 0) // -O0
*/
case "1": case "1":
errs = transform.Optimize(mod, config, 1, 0, 0) // -O1 errs = transform.Optimize(mod, config, 1, 0, 0) // -O1
case "2": 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. // Generate output.
outext := filepath.Ext(outpath) outext := filepath.Ext(outpath)
switch outext { switch outext {
@@ -209,7 +178,7 @@ func Build(pkgName, outpath string, config *compileopts.Config, action func(Buil
} }
// Compile C files in packages. // 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") outpath := filepath.Join(dir, "pkg"+strconv.Itoa(i)+"-"+filepath.Base(file)+".o")
err := runCCompiler(config.Target.Compiler, append(config.CFlags(), "-c", "-o", outpath, file)...) err := runCCompiler(config.Target.Compiler, append(config.CFlags(), "-c", "-o", outpath, file)...)
if err != nil { if err != nil {
@@ -218,36 +187,12 @@ func Build(pkgName, outpath string, config *compileopts.Config, action func(Buil
ldflags = append(ldflags, outpath) ldflags = append(ldflags, outpath)
} }
if len(buildOutput.ExtraLDFlags) > 0 {
ldflags = append(ldflags, buildOutput.ExtraLDFlags...)
}
// Link the object files together. // Link the object files together.
err = link(config.Target.Linker, ldflags...) err = link(config.Target.Linker, ldflags...)
if err != nil { if err != nil {
return &commandError{"failed to link", executable, err} 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" { if config.Options.PrintSizes == "short" || config.Options.PrintSizes == "full" {
sizes, err := loadProgramSize(executable) sizes, err := loadProgramSize(executable)
if err != nil { 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. // Get an Intel .hex file or .bin file from the .elf file.
outputBinaryFormat := config.BinaryFormat(outext) if outext == ".hex" || outext == ".bin" || outext == ".gba" {
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.
tmppath = filepath.Join(dir, "main"+outext) tmppath = filepath.Join(dir, "main"+outext)
err := objcopy(executable, tmppath, outputBinaryFormat) err := objcopy(executable, tmppath)
if err != nil { if err != nil {
return err return err
} }
case "uf2": } else if outext == ".uf2" {
// Get UF2 from the .elf file. // Get UF2 from the .elf file.
tmppath = filepath.Join(dir, "main"+outext) tmppath = filepath.Join(dir, "main"+outext)
err := convertELFFileToUF2File(executable, tmppath, config.Target.UF2FamilyID) err := convertELFFileToUF2File(executable, tmppath, config.Target.UF2FamilyID)
if err != nil { if err != nil {
return err 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 == "" { if goroot == "" {
return nil, errors.New("cannot locate $GOROOT, please set it manually") 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 { if err != nil {
return nil, fmt.Errorf("could not read version from GOROOT (%v): %v", goroot, err) return nil, fmt.Errorf("could not read version from GOROOT (%v): %v", goroot, err)
} }
if major != 1 || minor < 11 || minor > 15 { if major != 1 || minor < 11 || minor > 14 {
return nil, fmt.Errorf("requires go version 1.11 through 1.15, got go%d.%d", major, minor) 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")) clangHeaderPath := getClangHeaderPath(goenv.Get("TINYGOROOT"))
return &compileopts.Config{ return &compileopts.Config{
+58
View File
@@ -1,13 +1,71 @@
package builder package builder
import ( import (
"errors"
"fmt"
"io"
"io/ioutil" "io/ioutil"
"os" "os"
"os/exec" "os/exec"
"path/filepath" "path/filepath"
"regexp"
"sort" "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 // getClangHeaderPath returns the path to the built-in Clang headers. It tries
// multiple locations, which should make it find the directory when installed in // multiple locations, which should make it find the directory when installed in
// various ways. // 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. // 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) 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") { 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-") { if strings.HasPrefix(target, "riscv32-") {
args = append(args, "-march=rv32imac", "-mabi=ilp32", "-fforce-enable-int128") 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. // Compile all sources.
var objs []string var objs []string
+15 -19
View File
@@ -4,15 +4,12 @@ import (
"debug/elf" "debug/elf"
"io/ioutil" "io/ioutil"
"os" "os"
"path/filepath"
"sort" "sort"
"github.com/marcinbor85/gohex" "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. // objcopyError is an error returned by functions that act like objcopy.
type objcopyError struct { type objcopyError struct {
Op string Op string
@@ -74,13 +71,8 @@ func extractROM(path string) (uint64, []byte, error) {
var rom []byte var rom []byte
for _, prog := range progs { for _, prog := range progs {
if prog.Paddr != progs[0].Paddr+uint64(len(rom)) { if prog.Paddr != progs[0].Paddr+uint64(len(rom)) {
diff := prog.Paddr - (progs[0].Paddr + uint64(len(rom)))
if diff > maxPadBytes {
return 0, nil, objcopyError{"ROM segments are non-contiguous: " + path, nil} return 0, nil, objcopyError{"ROM segments are non-contiguous: " + path, nil}
} }
// Pad the difference
rom = append(rom, make([]byte, diff)...)
}
data, err := ioutil.ReadAll(prog.Open()) data, err := ioutil.ReadAll(prog.Open())
if err != nil { if err != nil {
return 0, nil, objcopyError{"failed to extract segment from ELF file: " + path, err} return 0, nil, objcopyError{"failed to extract segment from ELF file: " + path, err}
@@ -101,7 +93,7 @@ func extractROM(path string) (uint64, []byte, error) {
// objcopy converts an ELF file to a different (simpler) output file format: // objcopy converts an ELF file to a different (simpler) output file format:
// .bin or .hex. It extracts only the .text section. // .bin or .hex. It extracts only the .text section.
func objcopy(infile, outfile, binaryFormat string) error { func objcopy(infile, outfile string) error {
f, err := os.OpenFile(outfile, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0666) f, err := os.OpenFile(outfile, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0666)
if err != nil { if err != nil {
return err return err
@@ -115,20 +107,24 @@ func objcopy(infile, outfile, binaryFormat string) error {
} }
// Write to the file, in the correct format. // Write to the file, in the correct format.
switch binaryFormat { switch filepath.Ext(outfile) {
case "hex": case ".gba":
// Intel hex file, includes the firmware start address. // 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() mem := gohex.NewMemory()
err := mem.AddBinary(uint32(addr), data) err := mem.AddBinary(uint32(addr), data)
if err != nil { if err != nil {
return objcopyError{"failed to create .hex file", err} return objcopyError{"failed to create .hex file", err}
} }
return mem.DumpIntelHex(f, 16) mem.DumpIntelHex(f, 16) // TODO: handle error
case "bin": return nil
// 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
default: default:
panic("unreachable") panic("unreachable")
} }
+3 -17
View File
@@ -41,7 +41,6 @@ type cgoPackage struct {
elaboratedTypes map[string]*elaboratedTypeInfo elaboratedTypes map[string]*elaboratedTypeInfo
enums map[string]enumInfo enums map[string]enumInfo
anonStructNum int anonStructNum int
ldflags []string
} }
// constantInfo stores some information about a CGo constant found by libclang // 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 // 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 // files. If there is one or more error, it returns these in the []error slice
// but still modifies the AST. // 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{ p := &cgoPackage{
dir: dir, dir: dir,
fset: fset, 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. // Find the absolute path for this package.
packagePath, err := filepath.Abs(fset.File(files[0].Pos()).Name()) packagePath, err := filepath.Abs(fset.File(files[0].Pos()).Name())
if err != nil { if err != nil {
return nil, nil, []error{ return nil, []error{
scanner.Error{ scanner.Error{
Pos: fset.Position(files[0].Pos()), Pos: fset.Position(files[0].Pos()),
Msg: "cgo: cannot find absolute path: " + err.Error(), // TODO: wrap this error 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) makePathsAbsolute(flags, packagePath)
cflags = append(cflags, flags...) 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: default:
startPos := strings.LastIndex(line[4:colon], name) + 4 startPos := strings.LastIndex(line[4:colon], name) + 4
p.addErrorAfter(comment.Slash, comment.Text[:lineStart+startPos], "invalid #cgo line: "+name) 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. // Print the newly generated in-memory AST, for debugging.
//ast.Print(fset, p.generated) //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 // 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. // 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. // Check the AST for type errors.
var typecheckErrors []error 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):] value := source[len(name):]
// Try to convert this #define into a Go constant expression. // Try to convert this #define into a Go constant expression.
expr, scannerError := parseConst(pos+token.Pos(len(name)), p.fset, value) expr, err := parseConst(pos+token.Pos(len(name)), p.fset, value)
if scannerError != nil { if err != nil {
p.errors = append(p.errors, *scannerError) p.errors = append(p.errors, err)
} }
if expr != nil { if expr != nil {
// Parsing was successful. // Parsing was successful.
-7
View File
@@ -21,13 +21,6 @@ package main
#if defined(NOTDEFINED) #if defined(NOTDEFINED)
#warning flag must not be defined #warning flag must not be defined
#endif #endif
// Check Compiler flags
#cgo LDFLAGS: -lc
// This flag is not valid ldflags
#cgo LDFLAGS: -does-not-exists
*/ */
import "C" import "C"
-1
View File
@@ -1,7 +1,6 @@
// CGo errors: // CGo errors:
// testdata/flags.go:5:7: invalid #cgo line: NOFLAGS // testdata/flags.go:5:7: invalid #cgo line: NOFLAGS
// testdata/flags.go:8:13: invalid flag: -fdoes-not-exist // testdata/flags.go:8:13: invalid flag: -fdoes-not-exist
// testdata/flags.go:29:14: invalid flag: -does-not-exists
package main package main
+2 -59
View File
@@ -129,8 +129,8 @@ func (c *Config) NeedsStackObjects() bool {
} }
} }
// Scheduler returns the scheduler implementation. Valid values are "none", // Scheduler returns the scheduler implementation. Valid values are "coroutines"
//"coroutines" and "tasks". // and "tasks".
func (c *Config) Scheduler() string { func (c *Config) Scheduler() string {
if c.Options.Scheduler != "" { if c.Options.Scheduler != "" {
return c.Options.Scheduler return c.Options.Scheduler
@@ -164,16 +164,6 @@ func (c *Config) PanicStrategy() string {
return c.Options.PanicStrategy 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 // CFlags returns the flags to pass to the C compiler. This is necessary for CGo
// preprocessing. // preprocessing.
func (c *Config) CFlags() []string { 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, "-nostdlibinc", "-Xclang", "-internal-isystem", "-Xclang", filepath.Join(root, "lib", "picolibc", "newlib", "libc", "include"))
cflags = append(cflags, "-I"+filepath.Join(root, "lib/picolibc-include")) cflags = append(cflags, "-I"+filepath.Join(root, "lib/picolibc-include"))
} }
if c.Debug() {
cflags = append(cflags, "-g")
}
return cflags return cflags
} }
@@ -239,31 +226,6 @@ func (c *Config) Debug() bool {
return c.Options.Debug 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 // Programmer returns the flash method and OpenOCD interface name given a
// particular configuration. It may either be all configured in the target JSON // particular configuration. It may either be all configured in the target JSON
// file or be modified using the -programmmer command-line option. // 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 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 { type TestConfig struct {
CompileTestBinary bool CompileTestBinary bool
// TODO: Filter the test functions to run, include verbose flag, etc // TODO: Filter the test functions to run, include verbose flag, etc
-63
View File
@@ -1,17 +1,5 @@
package compileopts 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 // Options contains extra options to give to the compiler. These options are
// usually passed from the command line. // usually passed from the command line.
type Options struct { type Options struct {
@@ -25,7 +13,6 @@ type Options struct {
VerifyIR bool VerifyIR bool
Debug bool Debug bool
PrintSizes string PrintSizes string
PrintStacks bool
CFlags []string CFlags []string
LDFlags []string LDFlags []string
Tags string Tags string
@@ -34,53 +21,3 @@ type Options struct {
TestConfig TestConfig TestConfig TestConfig
Programmer string 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"` Linker string `json:"linker"`
RTLib string `json:"rtlib"` // compiler runtime library (libgcc, compiler-rt) RTLib string `json:"rtlib"` // compiler runtime library (libgcc, compiler-rt)
Libc string `json:"libc"` 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"` CFlags []string `json:"cflags"`
LDFlags []string `json:"ldflags"` LDFlags []string `json:"ldflags"`
LinkerScript string `json:"linkerscript"` LinkerScript string `json:"linkerscript"`
@@ -47,13 +45,10 @@ type TargetSpec struct {
FlashVolume string `json:"msd-volume-name"` FlashVolume string `json:"msd-volume-name"`
FlashFilename string `json:"msd-firmware-name"` FlashFilename string `json:"msd-firmware-name"`
UF2FamilyID string `json:"uf2-family-id"` UF2FamilyID string `json:"uf2-family-id"`
BinaryFormat string `json:"binary-format"`
OpenOCDInterface string `json:"openocd-interface"` OpenOCDInterface string `json:"openocd-interface"`
OpenOCDTarget string `json:"openocd-target"` OpenOCDTarget string `json:"openocd-target"`
OpenOCDTransport string `json:"openocd-transport"` OpenOCDTransport string `json:"openocd-transport"`
JLinkDevice string `json:"jlink-device"` 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. // copyProperties copies all properties that are set in spec2 into itself.
@@ -93,12 +88,6 @@ func (spec *TargetSpec) copyProperties(spec2 *TargetSpec) {
if spec2.Libc != "" { if spec2.Libc != "" {
spec.Libc = 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.CFlags = append(spec.CFlags, spec2.CFlags...)
spec.LDFlags = append(spec.LDFlags, spec2.LDFlags...) spec.LDFlags = append(spec.LDFlags, spec2.LDFlags...)
if spec2.LinkerScript != "" { if spec2.LinkerScript != "" {
@@ -129,9 +118,6 @@ func (spec *TargetSpec) copyProperties(spec2 *TargetSpec) {
if spec2.UF2FamilyID != "" { if spec2.UF2FamilyID != "" {
spec.UF2FamilyID = spec2.UF2FamilyID spec.UF2FamilyID = spec2.UF2FamilyID
} }
if spec2.BinaryFormat != "" {
spec.BinaryFormat = spec2.BinaryFormat
}
if spec2.OpenOCDInterface != "" { if spec2.OpenOCDInterface != "" {
spec.OpenOCDInterface = spec2.OpenOCDInterface spec.OpenOCDInterface = spec2.OpenOCDInterface
} }
@@ -144,13 +130,6 @@ func (spec *TargetSpec) copyProperties(spec2 *TargetSpec) {
if spec2.JLinkDevice != "" { if spec2.JLinkDevice != "" {
spec.JLinkDevice = 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 // 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 // slice. This is required by the Go language spec: an index out of bounds must
// cause a panic. // cause a panic.
func (b *builder) createLookupBoundsCheck(arrayLen, index llvm.Value, indexType types.Type) { 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 // The //go:nobounds pragma was added to the function to avoid bounds
// checking. // checking.
return 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 // biggest possible slice capacity, 'low' means len and 'high' means cap. The
// logic is the same in both cases. // logic is the same in both cases.
func (b *builder) createSliceBoundsCheck(capacity, low, high, max llvm.Value, lowType, highType, maxType *types.Basic) { 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 // The //go:nobounds pragma was added to the function to avoid bounds
// checking. // checking.
return 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 // createChanBoundsCheck creates a bounds check before creating a new channel to
// check that the value is not too big for runtime.chanMake. // 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) { 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 // The //go:nobounds pragma was added to the function to avoid bounds
// checking. // checking.
return 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. // createNegativeShiftCheck creates an assertion that panics if the given shift value is negative.
// This function assumes that the shift value is signed. // This function assumes that the shift value is signed.
func (b *builder) createNegativeShiftCheck(shift llvm.Value) { func (b *builder) createNegativeShiftCheck(shift llvm.Value) {
if b.fn.IsNoBounds() { if b.info.nobounds {
// Function disabled bounds checking - skip shift check. // Function disabled bounds checking - skip shift check.
return return
} }
@@ -212,8 +212,8 @@ func (b *builder) createRuntimeAssert(assert llvm.Value, blockPrefix, assertFunc
} }
} }
faultBlock := b.ctx.AddBasicBlock(b.fn.LLVMFn, blockPrefix+".throw") faultBlock := b.ctx.AddBasicBlock(b.llvmFn, blockPrefix+".throw")
nextBlock := b.ctx.AddBasicBlock(b.fn.LLVMFn, blockPrefix+".next") nextBlock := b.ctx.AddBasicBlock(b.llvmFn, blockPrefix+".next")
b.blockExits[b.currentBlock] = nextBlock // adjust outgoing block for phi nodes b.blockExits[b.currentBlock] = nextBlock // adjust outgoing block for phi nodes
// Now branch to the out-of-bounds or the regular block. // 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. // 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 { func (b *builder) createRuntimeCall(fnName string, args []llvm.Value, name string) llvm.Value {
fullName := "runtime." + fnName llvmFn := b.getFunctionRaw(b.getRuntimeFuncType(fnName), functionInfo{
fn := b.mod.NamedFunction(fullName) linkName: "runtime." + fnName,
if fn.IsNil() { })
panic("trying to call non-existent function: " + fullName)
}
args = append(args, llvm.Undef(b.i8ptrType)) // unused context parameter args = append(args, llvm.Undef(b.i8ptrType)) // unused context parameter
args = append(args, llvm.ConstPointerNull(b.i8ptrType)) // coroutine handle 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 // 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 { 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) elementSizeValue := llvm.ConstInt(b.uintptrType, elementSize, false)
bufSize := b.getValue(expr.Size) bufSize := b.getValue(expr.Size)
b.createChanBoundsCheck(elementSize, bufSize, expr.Size.Type().Underlying().(*types.Basic), expr.Pos()) 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") valueAlloca, valueAllocaCast, valueAllocaSize := b.createTemporaryAlloca(valueType, "chan.value")
b.CreateStore(chanValue, valueAlloca) b.CreateStore(chanValue, valueAlloca)
// Allocate blockedlist buffer.
channelBlockedList := b.mod.GetTypeByName("runtime.channelBlockedList")
channelBlockedListAlloca, channelBlockedListAllocaCast, channelBlockedListAllocaSize := b.createTemporaryAlloca(channelBlockedList, "chan.blockedList")
// Do the send. // 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: // This also works around a bug in CoroSplit, at least in LLVM 8:
// https://bugs.llvm.org/show_bug.cgi?id=41742 // https://bugs.llvm.org/show_bug.cgi?id=41742
b.emitLifetimeEnd(channelBlockedListAllocaCast, channelBlockedListAllocaSize)
b.emitLifetimeEnd(valueAllocaCast, valueAllocaSize) b.emitLifetimeEnd(valueAllocaCast, valueAllocaSize)
} }
// createChanRecv emits a pseudo chan receive operation. It is lowered to the // createChanRecv emits a pseudo chan receive operation. It is lowered to the
// actual channel receive operation during goroutine lowering. // actual channel receive operation during goroutine lowering.
func (b *builder) createChanRecv(unop *ssa.UnOp) llvm.Value { 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) ch := b.getValue(unop.X)
// Allocate memory to receive into. // Allocate memory to receive into.
valueAlloca, valueAllocaCast, valueAllocaSize := b.createTemporaryAlloca(valueType, "chan.value") 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. // 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") received := b.CreateLoad(valueAlloca, "chan.received")
b.emitLifetimeEnd(channelBlockedListAllocaCast, channelBlockedListAllocaSize)
b.emitLifetimeEnd(valueAllocaCast, valueAllocaSize) b.emitLifetimeEnd(valueAllocaCast, valueAllocaSize)
if unop.CommaOk { if unop.CommaOk {
@@ -127,7 +117,7 @@ func (b *builder) createSelect(expr *ssa.Select) llvm.Value {
switch state.Dir { switch state.Dir {
case types.RecvOnly: case types.RecvOnly:
// Make sure the receive buffer is big enough and has the correct alignment. // 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 { if size := b.targetData.TypeAllocSize(llvmType); size > recvbufSize {
recvbufSize = size recvbufSize = size
} }
+382 -285
View File
@@ -1,20 +1,25 @@
package compiler package compiler
//go:generate go run ./mkruntimetypes.go
import ( import (
"debug/dwarf" "debug/dwarf"
"errors" "errors"
"fmt" "fmt"
"go/ast" "go/ast"
"go/build"
"go/constant" "go/constant"
"go/token" "go/token"
"go/types" "go/types"
"os"
"path/filepath" "path/filepath"
"sort"
"strconv" "strconv"
"strings" "strings"
"github.com/tinygo-org/tinygo/compileopts" "github.com/tinygo-org/tinygo/compileopts"
"github.com/tinygo-org/tinygo/compiler/llvmutil" "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" "github.com/tinygo-org/tinygo/loader"
"golang.org/x/tools/go/ssa" "golang.org/x/tools/go/ssa"
"tinygo.org/x/go-llvm" "tinygo.org/x/go-llvm"
@@ -46,18 +51,23 @@ type compilerContext struct {
targetData llvm.TargetData targetData llvm.TargetData
intType llvm.Type intType llvm.Type
i8ptrType llvm.Type // for convenience i8ptrType llvm.Type // for convenience
runtimeTypes map[string]types.Type
funcPtrAddrSpace int funcPtrAddrSpace int
uintptrType llvm.Type uintptrType llvm.Type
ir *ir.Program program *ssa.Program
diagnostics []error diagnostics []error
astComments map[string]*ast.CommentGroup 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. // builder contains all information relevant to build a single function.
type builder struct { type builder struct {
*compilerContext *compilerContext
llvm.Builder llvm.Builder
fn *ir.Function fn *ssa.Function
llvmFn llvm.Value
info functionInfo
locals map[ssa.Value]llvm.Value // local variables locals map[ssa.Value]llvm.Value // local variables
blockEntries map[*ssa.BasicBlock]llvm.BasicBlock // a *ssa.BasicBlock may be split up blockEntries map[*ssa.BasicBlock]llvm.BasicBlock // a *ssa.BasicBlock may be split up
blockExits map[*ssa.BasicBlock]llvm.BasicBlock // these are the exit blocks blockExits map[*ssa.BasicBlock]llvm.BasicBlock // these are the exit blocks
@@ -68,17 +78,10 @@ type builder struct {
difunc llvm.Metadata difunc llvm.Metadata
dilocals map[*types.Var]llvm.Metadata dilocals map[*types.Var]llvm.Metadata
allDeferFuncs []interface{} allDeferFuncs []interface{}
deferFuncs map[*ir.Function]int deferFuncs map[*ssa.Function]int
deferInvokeFuncs map[string]int deferInvokeFuncs map[string]int
deferClosureFuncs map[*ir.Function]int deferClosureFuncs map[*ssa.Function]int
deferExprFuncs map[ssa.Value]int
selectRecvBuf map[*ssa.Select]llvm.Value selectRecvBuf map[*ssa.Select]llvm.Value
deferBuiltinFuncs map[ssa.Value]deferBuiltin
}
type deferBuiltin struct {
funcName string
callback int
} }
type phiNode struct { type phiNode struct {
@@ -95,75 +98,20 @@ func NewTargetMachine(config *compileopts.Config) (llvm.TargetMachine, error) {
return llvm.TargetMachine{}, err return llvm.TargetMachine{}, err
} }
features := strings.Join(config.Features(), ",") features := strings.Join(config.Features(), ",")
machine := target.CreateTargetMachine(config.Triple(), config.CPU(), features, llvm.CodeGenLevelDefault, llvm.RelocStatic, llvm.CodeModelDefault)
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)
return machine, nil return machine, nil
} }
// CompilerOutput is returned from the Compile() call. It contains the compile // newCompilerContext builds a new *compilerContext based on the provided
// output and information necessary to continue to compile and link the program. // configuration, ready to compile Go SSA to LLVM IR.
type CompilerOutput struct { func newCompilerContext(pkgName string, machine llvm.TargetMachine, config *compileopts.Config) *compilerContext {
// 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) {
c := &compilerContext{ c := &compilerContext{
Config: config, Config: config,
difiles: make(map[string]llvm.Metadata), difiles: make(map[string]llvm.Metadata),
ditypes: make(map[types.Type]llvm.Metadata), ditypes: make(map[types.Type]llvm.Metadata),
machine: machine, machine: machine,
targetData: machine.CreateTargetData(), targetData: machine.CreateTargetData(),
runtimeTypes: make(map[string]types.Type),
} }
c.ctx = llvm.NewContext() c.ctx = llvm.NewContext()
@@ -173,7 +121,6 @@ func Compile(pkgName string, machine llvm.TargetMachine, config *compileopts.Con
if c.Debug() { if c.Debug() {
c.dibuilder = llvm.NewDIBuilder(c.mod) c.dibuilder = llvm.NewDIBuilder(c.mod)
} }
output.Mod = c.mod
c.uintptrType = c.ctx.IntType(c.targetData.PointerSize() * 8) c.uintptrType = c.ctx.IntType(c.targetData.PointerSize() * 8)
if c.targetData.PointerSize() <= 4 { if c.targetData.PointerSize() <= 4 {
@@ -192,30 +139,121 @@ func Compile(pkgName string, machine llvm.TargetMachine, config *compileopts.Con
c.funcPtrAddrSpace = dummyFunc.Type().PointerAddressSpace() c.funcPtrAddrSpace = dummyFunc.Type().PointerAddressSpace()
dummyFunc.EraseFromParentAsFunction() dummyFunc.EraseFromParentAsFunction()
lprogram, err := loader.Load(c.Config, []string{pkgName}, c.ClangHeaders, types.Config{ 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
}
wd, err := os.Getwd()
if err != nil {
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{ Sizes: &stdSizes{
IntSize: int64(c.targetData.TypeAllocSize(c.intType)), IntSize: int64(c.targetData.TypeAllocSize(c.intType)),
PtrSize: int64(c.targetData.PointerSize()), PtrSize: int64(c.targetData.PointerSize()),
MaxAlign: int64(c.targetData.PrefTypeAlignment(c.i8ptrType)), MaxAlign: int64(c.targetData.PrefTypeAlignment(c.i8ptrType)),
}}) },
if err != nil { },
return output, []error{err} Dir: wd,
TINYGOROOT: goenv.Get("TINYGOROOT"),
CFlags: c.CFlags(),
ClangHeaders: c.ClangHeaders,
} }
err = lprogram.Parse() if strings.HasSuffix(pkgName, ".go") {
_, err = lprogram.ImportFile(pkgName)
if err != nil { if err != nil {
return output, []error{err} return c.mod, nil, []error{err}
} }
output.ExtraLDFlags = lprogram.LDFlags } else {
output.MainDir = lprogram.MainPkg().Dir _, err = lprogram.Import(pkgName, wd, token.Position{
Filename: "build command-line-arguments",
c.ir = ir.NewProgram(lprogram) })
// Run a simple dead code elimination pass.
err = c.ir.SimpleDCE()
if err != nil { if err != nil {
return output, []error{err} return c.mod, nil, []error{err}
} }
}
_, err = lprogram.Import("runtime", "", token.Position{
Filename: "build default import",
})
if err != nil {
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. // Initialize debug information.
if c.Debug() { if c.Debug() {
@@ -230,66 +268,44 @@ func Compile(pkgName string, machine llvm.TargetMachine, config *compileopts.Con
c.loadASTComments(lprogram) c.loadASTComments(lprogram)
// Declare runtime types. // Predeclare the runtime.alloc function, which is used by the wordpack
// TODO: lazily create runtime types in getLLVMRuntimeType when they are // functionality.
// needed. Eventually this will be required anyway, when packages are c.getFunction(c.program.ImportedPackage("runtime").Members["alloc"].(*ssa.Function))
// 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)
}
}
}
}
// Declare all functions. sortedPackages := sortPackages(c.program, pkgName)
for _, f := range c.ir.Functions {
c.createFunctionDeclaration(f) // 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))
}
}
}
} }
// Add definitions to declarations. // Add definitions to declarations.
var initFuncs []llvm.Value
irbuilder := c.ctx.NewBuilder() irbuilder := c.ctx.NewBuilder()
defer irbuilder.Dispose() defer irbuilder.Dispose()
for _, f := range c.ir.Functions { for _, pkg := range sortedPackages {
if f.Synthetic == "package initializer" { c.createPackage(pkg, irbuilder)
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()
} }
// After all packages are imported, add a synthetic initializer function // After all packages are imported, add a synthetic initializer function
// that calls the initializer of each package. // that calls the initializer of each package.
initFn := c.ir.GetFunction(c.ir.Program.ImportedPackage("runtime").Members["initAll"].(*ssa.Function)) initFn := c.program.ImportedPackage("runtime").Members["initAll"].(*ssa.Function)
initFn.LLVMFn.SetLinkage(llvm.InternalLinkage) llvmInitFn := c.getFunction(initFn)
initFn.LLVMFn.SetUnnamedAddr(true) llvmInitFn.SetLinkage(llvm.InternalLinkage)
llvmInitFn.SetUnnamedAddr(true)
if c.Debug() { if c.Debug() {
difunc := c.attachDebugInfo(initFn) difunc := c.attachDebugInfo(initFn)
pos := c.ir.Program.Fset.Position(initFn.Pos()) pos := c.program.Fset.Position(initFn.Pos())
irbuilder.SetCurrentDebugLocation(uint(pos.Line), uint(pos.Column), difunc, llvm.Metadata{}) irbuilder.SetCurrentDebugLocation(uint(pos.Line), uint(pos.Column), difunc, llvm.Metadata{})
} }
initFn.LLVMFn.Param(0).SetName("context") block := c.ctx.AddBasicBlock(llvmInitFn, "entry")
initFn.LLVMFn.Param(1).SetName("parentHandle")
block := c.ctx.AddBasicBlock(initFn.LLVMFn, "entry")
irbuilder.SetInsertPointAtEnd(block) irbuilder.SetInsertPointAtEnd(block)
for _, fn := range initFuncs { for _, fn := range initFuncs {
irbuilder.CreateCall(fn, []llvm.Value{llvm.Undef(c.i8ptrType), llvm.Undef(c.i8ptrType)}, "") 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 // Conserve for goroutine lowering. Without marking these as external, they
// would be optimized away. // 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 realMain.SetLinkage(llvm.ExternalLinkage) // keep alive until goroutine lowering
// Replace callMain placeholder with actual main function. // 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. // Gather the list of (C) file paths that should be included in the build.
for _, pkg := range c.ir.LoaderProgram.Sorted() { var extraFiles []string
for _, filename := range pkg.CFiles { for _, pkg := range lprogram.Sorted() {
output.ExtraFiles = append(output.ExtraFiles, filepath.Join(pkg.Dir, filename)) 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 // 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 // it as a LLVM type, creating it if necessary.
// getLLVMType(getRuntimeType(name)).
func (c *compilerContext) getLLVMRuntimeType(name string) llvm.Type { func (c *compilerContext) getLLVMRuntimeType(name string) llvm.Type {
fullName := "runtime." + name fullName := "runtime." + name
typ := c.mod.GetTypeByName(fullName) llvmType := c.mod.GetTypeByName(fullName)
if typ.IsNil() { if llvmType.IsNil() {
println(c.mod.String()) llvmType = c.getLLVMType(c.getRuntimeType(name))
panic("could not find runtime type: " + fullName)
} }
return typ return llvmType
} }
// getLLVMType creates and returns a LLVM type for a Go type. In the case of // 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) llvmType = c.ctx.StructCreateNamed(llvmName)
underlying := c.getLLVMType(st) underlying := c.getLLVMType(st)
llvmType.StructSetBody(underlying.StructElementTypes(), false) 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 return llvmType
} }
@@ -452,6 +538,23 @@ func (c *compilerContext) getLLVMType(goType types.Type) llvm.Type {
case *types.Struct: case *types.Struct:
members := make([]llvm.Type, typ.NumFields()) members := make([]llvm.Type, typ.NumFields())
for i := 0; i < typ.NumFields(); i++ { 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()) members[i] = c.getLLVMType(typ.Field(i).Type())
} }
return c.ctx.StructType(members, false) return c.ctx.StructType(members, false)
@@ -555,11 +658,11 @@ func (c *compilerContext) createDIType(typ types.Type) llvm.Metadata {
Encoding: encoding, Encoding: encoding,
}) })
case *types.Chan: 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: 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: 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: case *types.Named:
return c.dibuilder.CreateTypedef(llvm.DITypedef{ return c.dibuilder.CreateTypedef(llvm.DITypedef{
Type: c.getDIType(typ.Underlying()), Type: c.getDIType(typ.Underlying()),
@@ -667,7 +770,7 @@ func (b *builder) getLocalVariable(variable *types.Var) llvm.Metadata {
return dilocal return dilocal
} }
pos := b.ir.Program.Fset.Position(variable.Pos()) pos := b.program.Fset.Position(variable.Pos())
// Check whether this is a function parameter. // Check whether this is a function parameter.
for i, param := range b.fn.Params { for i, param := range b.fn.Params {
@@ -698,95 +801,17 @@ func (b *builder) getLocalVariable(variable *types.Var) llvm.Metadata {
return dilocal 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 // attachDebugInfo adds debug info to a function declaration. It returns the
// DISubprogram metadata node. // DISubprogram metadata node.
func (c *compilerContext) attachDebugInfo(f *ir.Function) llvm.Metadata { func (c *compilerContext) attachDebugInfo(f *ssa.Function) llvm.Metadata {
pos := c.ir.Program.Fset.Position(f.Syntax().Pos()) pos := c.program.Fset.Position(f.Syntax().Pos())
return c.attachDebugInfoRaw(f, f.LLVMFn, "", pos.Filename, pos.Line) return c.attachDebugInfoRaw(f, c.getFunction(f), "", pos.Filename, pos.Line)
} }
// attachDebugInfo adds debug info to a function declaration. It returns the // attachDebugInfo adds debug info to a function declaration. It returns the
// DISubprogram metadata node. This method allows some more control over how // DISubprogram metadata node. This method allows some more control over how
// debug info is added to the function. // 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. // Debug info for this function.
diparams := make([]llvm.Metadata, 0, len(f.Params)) diparams := make([]llvm.Metadata, 0, len(f.Params))
for _, param := range 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{ difunc := c.dibuilder.CreateFunction(c.getDIFile(filename), llvm.DIFunction{
Name: f.RelString(nil) + suffix, Name: f.RelString(nil) + suffix,
LinkageName: f.LinkName() + suffix, LinkageName: c.getFunctionInfo(f).linkName + suffix,
File: c.getDIFile(filename), File: c.getDIFile(filename),
Line: line, Line: line,
Type: diFuncType, Type: diFuncType,
@@ -827,37 +852,99 @@ func (c *compilerContext) getDIFile(filename string) llvm.Metadata {
return c.difiles[filename] return c.difiles[filename]
} }
// createFunctionDefinition builds the LLVM IR implementation for this function. func (c *compilerContext) createPackage(pkg *ssa.Package, irbuilder llvm.Builder) {
// The function must be declared but not yet defined, otherwise this function memberNames := make([]string, 0)
// will create a diagnostic. for name := range pkg.Members {
func (b *builder) createFunctionDefinition() { memberNames = append(memberNames, name)
if b.DumpSSA() {
fmt.Printf("\nfunc %s:\n", b.fn.Function)
} }
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" errValue := b.fn.Name() + " redeclared in this program"
fnPos := getPosition(b.fn.LLVMFn) fnPos := getPosition(b.llvmFn)
if fnPos.IsValid() { if fnPos.IsValid() {
errValue += "\n\tprevious declaration at " + fnPos.String() errValue += "\n\tprevious declaration at " + fnPos.String()
} }
b.addError(b.fn.Pos(), errValue) b.addError(b.fn.Pos(), errValue)
return return
} }
if !b.fn.IsExported() { if !b.info.exported {
b.fn.LLVMFn.SetLinkage(llvm.InternalLinkage) b.llvmFn.SetLinkage(llvm.InternalLinkage)
b.fn.LLVMFn.SetUnnamedAddr(true) b.llvmFn.SetUnnamedAddr(true)
} }
// Some functions have a pragma controlling the inlining level. // Some functions have a pragma controlling the inlining level.
switch b.fn.Inline() { switch b.info.inline {
case ir.InlineHint: case inlineHint:
// Add LLVM inline hint to functions with //go:inline pragma. // Add LLVM inline hint to functions with //go:inline pragma.
inline := b.ctx.CreateEnumAttribute(llvm.AttributeKindID("inlinehint"), 0) inline := b.ctx.CreateEnumAttribute(llvm.AttributeKindID("inlinehint"), 0)
b.fn.LLVMFn.AddFunctionAttr(inline) b.llvmFn.AddFunctionAttr(inline)
case ir.InlineNone: case inlineNone:
// Add LLVM attribute to always avoid inlining this function. // Add LLVM attribute to always avoid inlining this function.
noinline := b.ctx.CreateEnumAttribute(llvm.AttributeKindID("noinline"), 0) noinline := b.ctx.CreateEnumAttribute(llvm.AttributeKindID("noinline"), 0)
b.fn.LLVMFn.AddFunctionAttr(noinline) b.llvmFn.AddFunctionAttr(noinline)
} }
// Add debug info, if needed. // Add debug info, if needed.
@@ -866,18 +953,18 @@ func (b *builder) createFunctionDefinition() {
// Package initializers have no debug info. Create some fake debug // Package initializers have no debug info. Create some fake debug
// info to at least have *something*. // info to at least have *something*.
filename := b.fn.Package().Pkg.Path() + "/<init>" 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 { } else if b.fn.Syntax() != nil {
// Create debug info file if needed. // Create debug info file if needed.
b.difunc = b.attachDebugInfo(b.fn) 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{}) b.SetCurrentDebugLocation(uint(pos.Line), uint(pos.Column), b.difunc, llvm.Metadata{})
} }
// Pre-create all basic blocks in the function. // Pre-create all basic blocks in the function.
for _, block := range b.fn.DomPreorder() { 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.blockEntries[block] = llvmBlock
b.blockExits[block] = llvmBlock b.blockExits[block] = llvmBlock
} }
@@ -890,7 +977,7 @@ func (b *builder) createFunctionDefinition() {
llvmType := b.getLLVMType(param.Type()) llvmType := b.getLLVMType(param.Type())
fields := make([]llvm.Value, 0, 1) fields := make([]llvm.Value, 0, 1)
for _, info := range expandFormalParamType(llvmType, param.Name(), param.Type()) { for _, info := range expandFormalParamType(llvmType, param.Name(), param.Type()) {
param := b.fn.LLVMFn.Param(llvmParamIndex) param := b.llvmFn.Param(llvmParamIndex)
param.SetName(info.name) param.SetName(info.name)
fields = append(fields, param) fields = append(fields, param)
llvmParamIndex++ llvmParamIndex++
@@ -921,8 +1008,8 @@ func (b *builder) createFunctionDefinition() {
// Load free variables from the context. This is a closure (or bound // Load free variables from the context. This is a closure (or bound
// method). // method).
var context llvm.Value var context llvm.Value
if !b.fn.IsExported() { if !b.info.exported {
parentHandle := b.fn.LLVMFn.LastParam() parentHandle := b.llvmFn.LastParam()
parentHandle.SetName("parentHandle") parentHandle.SetName("parentHandle")
context = llvm.PrevParam(parentHandle) context = llvm.PrevParam(parentHandle)
context.SetName("context") context.SetName("context")
@@ -973,7 +1060,7 @@ func (b *builder) createFunctionDefinition() {
continue continue
} }
dbgVar := b.getLocalVariable(variable) 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{ b.dibuilder.InsertValueAtEnd(b.getValue(instr.X), dbgVar, b.dibuilder.CreateExpression(nil), llvm.DebugLoc{
Line: uint(pos.Line), Line: uint(pos.Line),
Col: uint(pos.Column), Col: uint(pos.Column),
@@ -1016,13 +1103,18 @@ func (b *builder) createFunctionDefinition() {
b.trackValue(phi.llvm) 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 // createInstruction builds the LLVM IR equivalent instructions for the
// particular Go SSA instruction. // particular Go SSA instruction.
func (b *builder) createInstruction(instr ssa.Instruction) { func (b *builder) createInstruction(instr ssa.Instruction) {
if b.Debug() { 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{}) 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 { if callee := instr.Call.StaticCallee(); callee != nil {
// Static callee is known. This makes it easier to start a new // Static callee is known. This makes it easier to start a new
// goroutine. // goroutine.
calleeFn := b.ir.GetFunction(callee) calleeFn := b.getFunction(callee)
var context llvm.Value var context llvm.Value
switch value := instr.Call.Value.(type) { switch value := instr.Call.Value.(type) {
case *ssa.Function: case *ssa.Function:
@@ -1073,14 +1165,14 @@ func (b *builder) createInstruction(instr ssa.Instruction) {
panic("StaticCallee returned an unexpected value") panic("StaticCallee returned an unexpected value")
} }
params = append(params, context) // context parameter params = append(params, context) // context parameter
b.createGoInstruction(calleeFn.LLVMFn, params, "", callee.Pos()) b.createGoInstruction(calleeFn, params, "", callee.Pos())
} else if !instr.Call.IsInvoke() { } else if !instr.Call.IsInvoke() {
// This is a function pointer. // This is a function pointer.
// At the moment, two extra params are passed to the newly started // At the moment, two extra params are passed to the newly started
// goroutine: // goroutine:
// * The function context, for closures. // * The function context, for closures.
// * The function pointer (for tasks). // * 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 params = append(params, context) // context parameter
switch b.Scheduler() { switch b.Scheduler() {
case "none", "coroutines": case "none", "coroutines":
@@ -1121,7 +1213,7 @@ func (b *builder) createInstruction(instr ssa.Instruction) {
b.CreateRet(b.getValue(instr.Results[0])) b.CreateRet(b.getValue(instr.Results[0]))
} else { } else {
// Multiple return values. Put them all in a struct. // 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 { for i, result := range instr.Results {
val := b.getValue(result) val := b.getValue(result)
retVal = b.CreateInsertValue(retVal, val, i, "") 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 var llvmCap llvm.Value
switch args[0].Type().(type) { switch args[0].Type().(type) {
case *types.Chan: 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: case *types.Slice:
llvmCap = b.CreateExtractValue(value, 2, "cap") llvmCap = b.CreateExtractValue(value, 2, "cap")
default: default:
@@ -1233,7 +1327,9 @@ func (b *builder) createBuiltin(args []ssa.Value, callName string, pos token.Pos
// string or slice // string or slice
llvmLen = b.CreateExtractValue(value, 1, "len") llvmLen = b.CreateExtractValue(value, 1, "len")
case *types.Chan: 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: case *types.Map:
llvmLen = b.createRuntimeCall("hashmapLen", []llvm.Value{value}, "len") llvmLen = b.createRuntimeCall("hashmapLen", []llvm.Value{value}, "len")
default: default:
@@ -1336,35 +1432,36 @@ func (b *builder) createFunctionCall(instr *ssa.CallCommon) (llvm.Value, error)
return b.createMemoryCopyCall(fn, instr.Args) return b.createMemoryCopyCall(fn, instr.Args)
case name == "runtime.memzero": case name == "runtime.memzero":
return b.createMemoryZeroCall(instr.Args) 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) 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) return b.createInlineAsmFull(instr)
case strings.HasPrefix(name, "device/arm.SVCall"): case strings.HasPrefix(name, "device/arm.SVCall"):
return b.emitSVCall(instr.Args) return b.emitSVCall(instr.Args)
case strings.HasPrefix(name, "(device/riscv.CSR)."): case strings.HasPrefix(name, "(device/riscv.CSR)."):
// 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) return b.emitCSROperation(instr)
}
case strings.HasPrefix(name, "syscall.Syscall"): case strings.HasPrefix(name, "syscall.Syscall"):
return b.createSyscall(instr) return b.createSyscall(instr)
case strings.HasPrefix(name, "runtime/volatile.Load"): case strings.HasPrefix(name, "runtime/volatile.Load"):
return b.createVolatileLoad(instr) return b.createVolatileLoad(instr)
case strings.HasPrefix(name, "runtime/volatile.Store"): case strings.HasPrefix(name, "runtime/volatile.Store"):
return b.createVolatileStore(instr) 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": case name == "runtime/interrupt.New":
return b.createInterruptGlobal(instr) return b.createInterruptGlobal(instr)
} }
targetFunc := b.ir.GetFunction(fn) callee = b.getFunction(fn)
if targetFunc.LLVMFn.IsNil() { info := b.getFunctionInfo(fn)
return llvm.Value{}, b.makeError(instr.Pos(), "undefined function: "+targetFunc.LinkName()) if callee.IsNil() {
return llvm.Value{}, b.makeError(instr.Pos(), "undefined function: "+info.linkName)
} }
switch value := instr.Value.(type) { switch value := instr.Value.(type) {
case *ssa.Function: case *ssa.Function:
@@ -1378,8 +1475,7 @@ func (b *builder) createFunctionCall(instr *ssa.CallCommon) (llvm.Value, error)
default: default:
panic("StaticCallee returned an unexpected value") panic("StaticCallee returned an unexpected value")
} }
callee = targetFunc.LLVMFn exported = info.exported
exported = targetFunc.IsExported()
} else if call, ok := instr.Value.(*ssa.Builtin); ok { } else if call, ok := instr.Value.(*ssa.Builtin); ok {
// Builtin function (append, close, delete, etc.).) // Builtin function (append, close, delete, etc.).)
return b.createBuiltin(instr.Args, call.Name(), instr.Pos()) 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 { func (b *builder) getValue(expr ssa.Value) llvm.Value {
switch expr := expr.(type) { switch expr := expr.(type) {
case *ssa.Const: case *ssa.Const:
return b.createConst(b.fn.LinkName(), expr) return b.createConst(b.info.linkName, expr)
case *ssa.Function: case *ssa.Function:
fn := b.ir.GetFunction(expr) info := b.getFunctionInfo(expr)
if fn.IsExported() { if info.exported {
b.addError(expr.Pos(), "cannot use an exported function as value: "+expr.String()) b.addError(expr.Pos(), "cannot use an exported function as value: "+expr.String())
return llvm.Undef(b.getLLVMType(expr.Type())) 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: case *ssa.Global:
value := b.getGlobal(expr) value := b.getGlobal(expr)
if value.IsNil() { if value.IsNil() {
@@ -1551,7 +1648,7 @@ func (b *builder) createExpr(expr ssa.Value) (llvm.Value, error) {
index := b.getValue(expr.Index) index := b.getValue(expr.Index)
// Check bounds. // 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) arrayLenLLVM := llvm.ConstInt(b.uintptrType, uint64(arrayLen), false)
b.createLookupBoundsCheck(arrayLenLLVM, index, expr.Index.Type()) b.createLookupBoundsCheck(arrayLenLLVM, index, expr.Index.Type())
@@ -1663,8 +1760,8 @@ func (b *builder) createExpr(expr ssa.Value) (llvm.Value, error) {
} }
// Bounds checking. // Bounds checking.
lenType := expr.Len.Type().Underlying().(*types.Basic) lenType := expr.Len.Type().(*types.Basic)
capType := expr.Cap.Type().Underlying().(*types.Basic) capType := expr.Cap.Type().(*types.Basic)
b.createSliceBoundsCheck(maxSize, sliceLen, sliceCap, sliceCap, lenType, capType, capType) b.createSliceBoundsCheck(maxSize, sliceLen, sliceCap, sliceCap, lenType, capType, capType)
// Allocate the backing array. // 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()) return llvm.Value{}, b.makeError(pos, "todo: convert: basic non-integer type: "+typeFrom.String()+" -> "+typeTo.String())
case *types.Slice: 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") 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)
}
}
+25 -122
View File
@@ -15,8 +15,6 @@ package compiler
import ( import (
"github.com/tinygo-org/tinygo/compiler/llvmutil" "github.com/tinygo-org/tinygo/compiler/llvmutil"
"github.com/tinygo-org/tinygo/ir"
"go/types"
"golang.org/x/tools/go/ssa" "golang.org/x/tools/go/ssa"
"tinygo.org/x/go-llvm" "tinygo.org/x/go-llvm"
) )
@@ -26,11 +24,9 @@ import (
// calls. // calls.
func (b *builder) deferInitFunc() { func (b *builder) deferInitFunc() {
// Some setup. // Some setup.
b.deferFuncs = make(map[*ir.Function]int) b.deferFuncs = make(map[*ssa.Function]int)
b.deferInvokeFuncs = make(map[string]int) b.deferInvokeFuncs = make(map[string]int)
b.deferClosureFuncs = make(map[*ir.Function]int) b.deferClosureFuncs = make(map[*ssa.Function]int)
b.deferExprFuncs = make(map[ssa.Value]int)
b.deferBuiltinFuncs = make(map[ssa.Value]deferBuiltin)
// Create defer list pointer. // Create defer list pointer.
deferType := llvm.PointerType(b.getLLVMRuntimeType("_defer"), 0) 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 { } else if callee, ok := instr.Call.Value.(*ssa.Function); ok {
// Regular function call. // Regular function call.
fn := b.ir.GetFunction(callee)
if _, ok := b.deferFuncs[fn]; !ok { if _, ok := b.deferFuncs[callee]; !ok {
b.deferFuncs[fn] = len(b.allDeferFuncs) b.deferFuncs[callee] = len(b.allDeferFuncs)
b.allDeferFuncs = append(b.allDeferFuncs, fn) 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 // Collect all values to be put in the struct (starting with
// runtime._defer fields). // runtime._defer fields).
@@ -135,7 +130,7 @@ func (b *builder) createDefer(instr *ssa.Defer) {
context := b.CreateExtractValue(closure, 0, "") context := b.CreateExtractValue(closure, 0, "")
// Get the callback number. // Get the callback number.
fn := b.ir.GetFunction(makeClosure.Fn.(*ssa.Function)) fn := makeClosure.Fn.(*ssa.Function)
if _, ok := b.deferClosureFuncs[fn]; !ok { if _, ok := b.deferClosureFuncs[fn]; !ok {
b.deferClosureFuncs[fn] = len(b.allDeferFuncs) b.deferClosureFuncs[fn] = len(b.allDeferFuncs)
b.allDeferFuncs = append(b.allDeferFuncs, makeClosure) b.allDeferFuncs = append(b.allDeferFuncs, makeClosure)
@@ -154,54 +149,9 @@ func (b *builder) createDefer(instr *ssa.Defer) {
values = append(values, context) values = append(values, context)
valueTypes = append(valueTypes, context.Type()) 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 { } else {
funcValue := b.getValue(instr.Call.Value) b.addError(instr.Pos(), "todo: defer on uncommon function call type")
return
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())
}
} }
// Make a struct out of the collected values to put in the defer frame. // Make a struct out of the collected values to put in the defer frame.
@@ -251,10 +201,10 @@ func (b *builder) createRunDefers() {
// } // }
// Create loop. // Create loop.
loophead := b.ctx.AddBasicBlock(b.fn.LLVMFn, "rundefers.loophead") loophead := b.ctx.AddBasicBlock(b.llvmFn, "rundefers.loophead")
loop := b.ctx.AddBasicBlock(b.fn.LLVMFn, "rundefers.loop") loop := b.ctx.AddBasicBlock(b.llvmFn, "rundefers.loop")
unreachable := b.ctx.AddBasicBlock(b.fn.LLVMFn, "rundefers.default") unreachable := b.ctx.AddBasicBlock(b.llvmFn, "rundefers.default")
end := b.ctx.AddBasicBlock(b.fn.LLVMFn, "rundefers.end") end := b.ctx.AddBasicBlock(b.llvmFn, "rundefers.end")
b.CreateBr(loophead) b.CreateBr(loophead)
// Create loop head: // Create loop head:
@@ -286,28 +236,21 @@ func (b *builder) createRunDefers() {
// Create switch case, for example: // Create switch case, for example:
// case 0: // case 0:
// // run first deferred call // // 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) sw.AddCase(llvm.ConstInt(b.uintptrType, uint64(i), false), block)
b.SetInsertPointAtEnd(block) b.SetInsertPointAtEnd(block)
switch callback := callback.(type) { switch callback := callback.(type) {
case *ssa.CallCommon: case *ssa.CallCommon:
// Call on an value or interface value. // Call on an interface value.
// Get the real defer struct type and cast to it.
valueTypes := []llvm.Type{b.uintptrType, llvm.PointerType(b.getLLVMRuntimeType("_defer"), 0)}
if !callback.IsInvoke() { if !callback.IsInvoke() {
//Expect funcValue to be passed through the defer frame. panic("expected an invoke call, not a direct call")
valueTypes = append(valueTypes, b.getFuncType(callback.Signature()))
} else {
//Expect typecode
valueTypes = append(valueTypes, b.uintptrType, b.i8ptrType)
} }
// 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 { for _, arg := range callback.Args {
valueTypes = append(valueTypes, b.getLLVMType(arg.Type())) valueTypes = append(valueTypes, b.getLLVMType(arg.Type()))
} }
deferFrameType := b.ctx.StructType(valueTypes, false) deferFrameType := b.ctx.StructType(valueTypes, false)
deferFramePtr := b.CreateBitCast(deferData, llvm.PointerType(deferFrameType, 0), "deferFrame") deferFramePtr := b.CreateBitCast(deferData, llvm.PointerType(deferFrameType, 0), "deferFrame")
@@ -320,37 +263,21 @@ func (b *builder) createRunDefers() {
forwardParams = append(forwardParams, forwardParam) forwardParams = append(forwardParams, forwardParam)
} }
var fnPtr llvm.Value
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. // Isolate the typecode.
typecode := forwardParams[0] typecode, forwardParams := forwardParams[0], forwardParams[1:]
forwardParams = forwardParams[1:]
fnPtr = b.getInvokePtr(callback, typecode)
// Add the context parameter. An interface call cannot also be a // Add the context parameter. An interface call cannot also be a
// closure but we have to supply the parameter anyway for platforms // closure but we have to supply the parameter anyway for platforms
// with a strict calling convention. // with a strict calling convention.
forwardParams = append(forwardParams, llvm.Undef(b.i8ptrType)) forwardParams = append(forwardParams, llvm.Undef(b.i8ptrType))
}
// Parent coroutine handle. // Parent coroutine handle.
forwardParams = append(forwardParams, llvm.Undef(b.i8ptrType)) forwardParams = append(forwardParams, llvm.Undef(b.i8ptrType))
fnPtr := b.getInvokePtr(callback, typecode)
b.createCall(fnPtr, forwardParams, "") b.createCall(fnPtr, forwardParams, "")
case *ir.Function: case *ssa.Function:
// Direct call. // Direct call.
// Get the real defer struct type and cast to it. // 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. // 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. // 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 // Add the context parameter. We know it is ignored by the receiving
// function, but we have to pass one anyway. // function, but we have to pass one anyway.
forwardParams = append(forwardParams, llvm.Undef(b.i8ptrType)) forwardParams = append(forwardParams, llvm.Undef(b.i8ptrType))
@@ -382,11 +309,11 @@ func (b *builder) createRunDefers() {
} }
// Call real function. // Call real function.
b.createCall(callback.LLVMFn, forwardParams, "") b.createCall(b.getFunction(callback), forwardParams, "")
case *ssa.MakeClosure: case *ssa.MakeClosure:
// Get the real defer struct type and cast to it. // 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)} valueTypes := []llvm.Type{b.uintptrType, llvm.PointerType(b.getLLVMRuntimeType("_defer"), 0)}
params := fn.Signature.Params() params := fn.Signature.Params()
for i := 0; i < params.Len(); i++ { for i := 0; i < params.Len(); i++ {
@@ -409,32 +336,8 @@ func (b *builder) createRunDefers() {
forwardParams = append(forwardParams, llvm.Undef(b.i8ptrType)) forwardParams = append(forwardParams, llvm.Undef(b.i8ptrType))
// Call deferred function. // Call deferred function.
b.createCall(fn.LLVMFn, forwardParams, "") b.createCall(b.getFunction(fn), forwardParams, "")
case *ssa.Builtin:
db := b.deferBuiltinFuncs[callback]
//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: default:
panic("unknown deferred function type") 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. // 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 { func (c *compilerContext) makeError(pos token.Pos, msg string) types.Error {
return types.Error{ return types.Error{
Fset: c.ir.Program.Fset, Fset: c.program.Fset,
Pos: pos, Pos: pos,
Msg: msg, Msg: msg,
} }
+12 -2
View File
@@ -5,6 +5,7 @@ package compiler
import ( import (
"go/types" "go/types"
"strings"
"github.com/tinygo-org/tinygo/compileopts" "github.com/tinygo-org/tinygo/compileopts"
"golang.org/x/tools/go/ssa" "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 { if len(expr.Bindings) == 0 {
panic("unexpected: MakeClosure without bound variables") 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. // Collect all bound variables.
boundVars := make([]llvm.Value, len(expr.Bindings)) 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) context := b.emitPointerPack(boundVars)
// Create the closure. // 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" "go/token"
"github.com/tinygo-org/tinygo/compiler/llvmutil" "github.com/tinygo-org/tinygo/compiler/llvmutil"
"golang.org/x/tools/go/ssa"
"tinygo.org/x/go-llvm" "tinygo.org/x/go-llvm"
) )
@@ -19,30 +20,17 @@ import (
// Because a go statement doesn't return anything, return undef. // 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 { func (b *builder) createGoInstruction(funcPtr llvm.Value, params []llvm.Value, prefix string, pos token.Pos) llvm.Value {
paramBundle := b.emitPointerPack(params) paramBundle := b.emitPointerPack(params)
var callee, stackSize llvm.Value var callee llvm.Value
switch b.Scheduler() { switch b.Scheduler() {
case "none", "tasks": case "none", "tasks":
callee = b.createGoroutineStartWrapper(funcPtr, prefix, pos) 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": case "coroutines":
callee = b.CreatePtrToInt(funcPtr, b.uintptrType, "") callee = b.CreatePtrToInt(funcPtr, b.uintptrType, "")
// There is no goroutine stack size: coroutines are used instead of
// stacks.
stackSize = llvm.Undef(b.uintptrType)
default: default:
panic("unreachable") 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()) return llvm.Undef(funcPtr.Type().ElementType().ReturnType())
} }
@@ -81,14 +69,13 @@ func (c *compilerContext) createGoroutineStartWrapper(fn llvm.Value, prefix stri
// Create the wrapper. // Create the wrapper.
wrapperType := llvm.FunctionType(c.ctx.VoidType(), []llvm.Type{c.i8ptrType}, false) wrapperType := llvm.FunctionType(c.ctx.VoidType(), []llvm.Type{c.i8ptrType}, false)
wrapper = llvm.AddFunction(c.mod, name+"$gowrapper", wrapperType) wrapper = llvm.AddFunction(c.mod, name+"$gowrapper", wrapperType)
wrapper.SetLinkage(llvm.InternalLinkage) wrapper.SetLinkage(llvm.PrivateLinkage)
wrapper.SetUnnamedAddr(true) wrapper.SetUnnamedAddr(true)
wrapper.AddAttributeAtIndex(-1, c.ctx.CreateStringAttribute("tinygo-gowrapper", name))
entry := c.ctx.AddBasicBlock(wrapper, "entry") entry := c.ctx.AddBasicBlock(wrapper, "entry")
builder.SetInsertPointAtEnd(entry) builder.SetInsertPointAtEnd(entry)
if c.Debug() { if c.Debug() {
pos := c.ir.Program.Fset.Position(pos) pos := c.program.Fset.Position(pos)
diFuncType := c.dibuilder.CreateSubroutineType(llvm.DISubroutineType{ diFuncType := c.dibuilder.CreateSubroutineType(llvm.DISubroutineType{
File: c.getDIFile(pos.Filename), File: c.getDIFile(pos.Filename),
Parameters: nil, // do not show parameters in debugger 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 = llvm.AddFunction(c.mod, prefix+".gowrapper", wrapperType)
wrapper.SetLinkage(llvm.InternalLinkage) wrapper.SetLinkage(llvm.InternalLinkage)
wrapper.SetUnnamedAddr(true) wrapper.SetUnnamedAddr(true)
wrapper.AddAttributeAtIndex(-1, c.ctx.CreateStringAttribute("tinygo-gowrapper", ""))
entry := c.ctx.AddBasicBlock(wrapper, "entry") entry := c.ctx.AddBasicBlock(wrapper, "entry")
builder.SetInsertPointAtEnd(entry) builder.SetInsertPointAtEnd(entry)
if c.Debug() { if c.Debug() {
pos := c.ir.Program.Fset.Position(pos) pos := c.program.Fset.Position(pos)
diFuncType := c.dibuilder.CreateSubroutineType(llvm.DISubroutineType{ diFuncType := c.dibuilder.CreateSubroutineType(llvm.DISubroutineType{
File: c.getDIFile(pos.Filename), File: c.getDIFile(pos.Filename),
Parameters: nil, // do not show parameters in debugger Parameters: nil, // do not show parameters in debugger
+87 -21
View File
@@ -11,7 +11,6 @@ import (
"strconv" "strconv"
"strings" "strings"
"github.com/tinygo-org/tinygo/ir"
"golang.org/x/tools/go/ssa" "golang.org/x/tools/go/ssa"
"tinygo.org/x/go-llvm" "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}) 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 { if ms.Len() == 0 {
// no methods, so can leave that one out // no methods, so can leave that one out
return llvm.ConstPointerNull(llvm.PointerType(c.getLLVMRuntimeType("interfaceMethodInfo"), 0)) 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++ { for i := 0; i < ms.Len(); i++ {
method := ms.At(i) method := ms.At(i)
signatureGlobal := c.getMethodSignature(method.Obj().(*types.Func)) signatureGlobal := c.getMethodSignature(method.Obj().(*types.Func))
f := c.ir.GetFunction(c.ir.Program.MethodValue(method)) fn := c.program.MethodValue(method)
if f.LLVMFn.IsNil() { llvmFn := c.getFunction(fn)
if llvmFn.IsNil() {
// compiler error, so panic // 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{ methodInfo := llvm.ConstNamedStruct(interfaceMethodInfoType, []llvm.Value{
signatureGlobal, signatureGlobal,
llvm.ConstPtrToInt(fn, c.uintptrType), llvm.ConstPtrToInt(wrapper, c.uintptrType),
}) })
methods[i] = methodInfo 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 // external *i8 indicating the indicating the signature of this method. It is
// used during the interface lowering pass. // used during the interface lowering pass.
func (c *compilerContext) getMethodSignature(method *types.Func) llvm.Value { func (c *compilerContext) getMethodSignature(method *types.Func) llvm.Value {
signature := ir.MethodSignature(method) signature := methodSignature(method)
signatureGlobal := c.mod.NamedGlobal("func " + signature) signatureGlobal := c.mod.NamedGlobal("func " + signature)
if signatureGlobal.IsNil() { if signatureGlobal.IsNil() {
signatureGlobal = llvm.AddGlobal(c.mod, c.ctx.Int8Type(), "func "+signature) signatureGlobal = llvm.AddGlobal(c.mod, c.ctx.Int8Type(), "func "+signature)
@@ -357,8 +364,8 @@ func (b *builder) createTypeAssert(expr *ssa.TypeAssert) llvm.Value {
// value. // value.
prevBlock := b.GetInsertBlock() prevBlock := b.GetInsertBlock()
okBlock := b.ctx.AddBasicBlock(b.fn.LLVMFn, "typeassert.ok") okBlock := b.ctx.AddBasicBlock(b.llvmFn, "typeassert.ok")
nextBlock := b.ctx.AddBasicBlock(b.fn.LLVMFn, "typeassert.next") nextBlock := b.ctx.AddBasicBlock(b.llvmFn, "typeassert.next")
b.blockExits[b.currentBlock] = nextBlock // adjust outgoing block for phi nodes b.blockExits[b.currentBlock] = nextBlock // adjust outgoing block for phi nodes
b.CreateCondBr(commaOk, okBlock, nextBlock) 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. // 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 // If the method to wrap has a pointer receiver, no wrapping is necessary and
// the function is returned directly. // the function is returned directly.
func (c *compilerContext) getInterfaceInvokeWrapper(f *ir.Function) llvm.Value { func (c *compilerContext) getInterfaceInvokeWrapper(fn *ssa.Function, llvmFn llvm.Value) llvm.Value {
wrapperName := f.LinkName() + "$invoke" wrapperName := llvmFn.Name() + "$invoke"
wrapper := c.mod.NamedFunction(wrapperName) wrapper := c.mod.NamedFunction(wrapperName)
if !wrapper.IsNil() { if !wrapper.IsNil() {
// Wrapper already created. Return it directly. // Wrapper already created. Return it directly.
@@ -445,7 +452,7 @@ func (c *compilerContext) getInterfaceInvokeWrapper(f *ir.Function) llvm.Value {
} }
// Get the expanded receiver type. // Get the expanded receiver type.
receiverType := c.getLLVMType(f.Params[0].Type()) receiverType := c.getLLVMType(fn.Params[0].Type())
var expandedReceiverType []llvm.Type var expandedReceiverType []llvm.Type
for _, info := range expandFormalParamType(receiverType, "", nil) { for _, info := range expandFormalParamType(receiverType, "", nil) {
expandedReceiverType = append(expandedReceiverType, info.llvmType) 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 // Casting a function signature to a different signature and calling it
// with a receiver pointer bitcasted to *i8 (as done in calls on an // with a receiver pointer bitcasted to *i8 (as done in calls on an
// interface) is hopefully a safe (defined) operation. // interface) is hopefully a safe (defined) operation.
return f.LLVMFn return llvmFn
} }
// create wrapper function // create wrapper function
fnType := f.LLVMFn.Type().ElementType() fnType := llvmFn.Type().ElementType()
paramTypes := append([]llvm.Type{c.i8ptrType}, fnType.ParamTypes()[len(expandedReceiverType):]...) paramTypes := append([]llvm.Type{c.i8ptrType}, fnType.ParamTypes()[len(expandedReceiverType):]...)
wrapFnType := llvm.FunctionType(fnType.ReturnType(), paramTypes, false) wrapFnType := llvm.FunctionType(fnType.ReturnType(), paramTypes, false)
wrapper = llvm.AddFunction(c.mod, wrapperName, wrapFnType) wrapper = llvm.AddFunction(c.mod, wrapperName, wrapFnType)
if f.LLVMFn.LastParam().Name() == "parentHandle" { if llvmFn.LastParam().Name() == "parentHandle" {
wrapper.LastParam().SetName("parentHandle") wrapper.LastParam().SetName("parentHandle")
} }
@@ -481,8 +488,8 @@ func (c *compilerContext) getInterfaceInvokeWrapper(f *ir.Function) llvm.Value {
// add debug info if needed // add debug info if needed
if c.Debug() { if c.Debug() {
pos := c.ir.Program.Fset.Position(f.Pos()) pos := c.program.Fset.Position(fn.Pos())
difunc := c.attachDebugInfoRaw(f, wrapper, "$invoke", pos.Filename, pos.Line) difunc := c.attachDebugInfoRaw(fn, wrapper, "$invoke", pos.Filename, pos.Line)
b.SetCurrentDebugLocation(uint(pos.Line), uint(pos.Column), difunc, llvm.Metadata{}) 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] receiverValue := b.emitPointerUnpack(wrapper.Param(0), []llvm.Type{receiverType})[0]
params := append(b.expandFormalParam(receiverValue), wrapper.Params()[1:]...) params := append(b.expandFormalParam(receiverValue), wrapper.Params()[1:]...)
if f.LLVMFn.Type().ElementType().ReturnType().TypeKind() == llvm.VoidTypeKind { if llvmFn.Type().ElementType().ReturnType().TypeKind() == llvm.VoidTypeKind {
b.CreateCall(f.LLVMFn, params, "") b.CreateCall(llvmFn, params, "")
b.CreateRetVoid() b.CreateRetVoid()
} else { } else {
ret := b.CreateCall(f.LLVMFn, params, "ret") ret := b.CreateCall(llvmFn, params, "ret")
b.CreateRet(ret) b.CreateRet(ret)
} }
return wrapper 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 // Create a new global of type runtime/interrupt.handle. Globals of this
// type are lowered in the interrupt lowering pass. // 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) globalLLVMType := b.getLLVMType(globalType)
globalName := "runtime/interrupt.$interrupt" + strconv.FormatInt(id.Int64(), 10) globalName := "runtime/interrupt.$interrupt" + strconv.FormatInt(id.Int64(), 10)
if global := b.mod.NamedGlobal(globalName); !global.IsNil() { 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. // Add debug info to the interrupt global.
if b.Debug() { 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{ diglobal := b.dibuilder.CreateGlobalVariableExpression(b.getDIFile(pos.Filename), llvm.DIGlobalVariableExpression{
Name: "interrupt" + strconv.FormatInt(id.Int64(), 10), Name: "interrupt" + strconv.FormatInt(id.Int64(), 10),
LinkageName: globalName, 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" "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, // 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 // 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). // 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. // Add debug info.
// TODO: this should be done for every global in the program, not just // TODO: this should be done for every global in the program, not just
// the ones that are referenced from some code. // 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{ diglobal := c.dibuilder.CreateGlobalVariableExpression(c.difiles[pos.Filename], llvm.DIGlobalVariableExpression{
Name: g.RelString(nil), Name: g.RelString(nil),
LinkageName: info.linkName, 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 ( require (
github.com/blakesmith/ar v0.0.0-20150311145944-8bd4349a67f2 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/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 go.bug.st/serial v1.0.0
golang.org/x/tools v0.0.0-20200216192241-b320d3a0f5a2 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 h1:oMCHnXa6CCCafdPDbMh/lWRhRByN0VFLvv+g+ayx1SI=
github.com/blakesmith/ar v0.0.0-20150311145944-8bd4349a67f2/go.mod h1:PkYb9DJNAwrSvRx5DYA+gUcOIgTGVMNkfSCbZM8cWpI= 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 h1:tiSSgKE1eJtxs1h/VgGQWuXUP0YS4CDIFMp6vaI1ls0=
github.com/creack/goselect v0.1.1/go.mod h1:a/NhLweNvqIYMuxcMOuWY516Cimucms3DglDzQP3hKY= 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/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/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
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/google/shlex v0.0.0-20181106134648-c34317bd91bf h1:7+FW5aGwISbqUtkfmIpZJGRgNFg2ioYPvFaUxdqpDsg= 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/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/marcinbor85/gohex v0.0.0-20180128172054-7a43cd876e46 h1:wXG2bA8fO7Vv7lLk2PihFMTqmbT173Tje39oKzQ50Mo=
github.com/knq/sysutil v0.0.0-20191005231841-15668db23d08/go.mod h1:dFWs1zEqDjFtnBXsd1vPOZaLsESovai349994nHx3e0= github.com/marcinbor85/gohex v0.0.0-20180128172054-7a43cd876e46/go.mod h1:Pb6XcsXyropB9LNHhnqaknG/vEwYztLkQzVCHv8sQ3M=
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/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 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/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= 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/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 h1:WG0RUwxtNT4qqaXX3DPA8zHFNm/D9xaBpxzHt1WcA/E=
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= 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-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/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/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-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-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 h1:ZBzSG/7F4eNKz2L3GE9o300RX0Az1Bw5HF7PDraD+qU=
golang.org/x/sys v0.0.0-20191128015809-6d18c012aee9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 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/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 h1:0sfSpGSa544Fwnbot3Oxq/U6SXqjty6Jy/3wRhVS7ig=
golang.org/x/tools v0.0.0-20200216192241-b320d3a0f5a2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= 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 h1:/atklqdjdhuosWIl6AIbOeHJjicWYPqR9bpxqxYG2pA=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 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/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= 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-20190224120431-7707ae5d1261 h1:rJS2Hga39YAnm7DE4qrPm6Dr/67EOojL0XPzvbEeBiw=
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/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 // location of the instruction. The location information may not be complete as
// it depends on debug information in the IR. // it depends on debug information in the IR.
func (e *evalPackage) errorAt(inst llvm.Value, err error) *Error { func (e *evalPackage) errorAt(inst llvm.Value, err error) *Error {
pos := getPosition(inst)
return &Error{ return &Error{
ImportPath: e.packagePath, ImportPath: e.packagePath,
Pos: pos, Pos: getPosition(inst),
Err: err, 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) { if !operand.IsConstant() || inst.IsVolatile() || (!operand.Underlying.IsAConstantExpr().IsNil() && operand.Underlying.Opcode() == llvm.BitCast) {
value = fr.builder.CreateLoad(operand.Value(), inst.Name()) value = fr.builder.CreateLoad(operand.Value(), inst.Name())
} else { } else {
var err error value = operand.Load()
value, err = operand.Load()
if err != nil {
return nil, nil, fr.errorAt(inst, err)
}
} }
if value.Type() != inst.Type() { if value.Type() != inst.Type() {
return nil, nil, fr.errorAt(inst, errors.New("interp: load: type does not match")) 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() { if inst.IsVolatile() {
fr.builder.CreateStore(value.Value(), ptr.Value()) fr.builder.CreateStore(value.Value(), ptr.Value())
} else { } else {
err := ptr.Store(value.Value()) ptr.Store(value.Value())
if err != nil {
return nil, nil, fr.errorAt(inst, err)
}
} }
case !inst.IsAGetElementPtrInst().IsNil(): case !inst.IsAGetElementPtrInst().IsNil():
value := fr.getLocal(inst.Operand(0)) value := fr.getLocal(inst.Operand(0))
@@ -290,6 +283,7 @@ func (fr *frame) evalBasicBlock(bb, incoming llvm.BasicBlock, indent string) (re
PkgName: fr.packagePath, PkgName: fr.packagePath,
KeySize: int(keySize), KeySize: int(keySize),
ValueSize: int(valueSize), ValueSize: int(valueSize),
MapType: inst.Type().ElementType(),
} }
case callee.Name() == "runtime.hashmapStringSet": case callee.Name() == "runtime.hashmapStringSet":
// set a string key in the map // 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 // "key" is a Go string value, which in the TinyGo calling convention is split up
// into separate pointer and length parameters. // into separate pointer and length parameters.
err := m.PutString(keyBuf, keyLen, valPtr) m.PutString(keyBuf, keyLen, valPtr)
if err != nil {
return nil, nil, fr.errorAt(inst, err)
}
case callee.Name() == "runtime.hashmapBinarySet": case callee.Name() == "runtime.hashmapBinarySet":
// set a binary (int etc.) key in the map // set a binary (int etc.) key in the map
keyBuf := fr.getLocal(inst.Operand(1)).(*LocalValue) 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, "") fr.builder.CreateCall(callee, llvmParams, "")
continue continue
} }
err := m.PutBinary(keyBuf, valPtr) m.PutBinary(keyBuf, valPtr)
if err != nil {
return nil, nil, fr.errorAt(inst, err)
}
case callee.Name() == "runtime.stringConcat": case callee.Name() == "runtime.stringConcat":
// adding two strings together // adding two strings together
buf1Ptr := fr.getLocal(inst.Operand(0)) buf1Ptr := fr.getLocal(inst.Operand(0))
buf1Len := fr.getLocal(inst.Operand(1)) buf1Len := fr.getLocal(inst.Operand(1))
buf2Ptr := fr.getLocal(inst.Operand(2)) buf2Ptr := fr.getLocal(inst.Operand(2))
buf2Len := fr.getLocal(inst.Operand(3)) buf2Len := fr.getLocal(inst.Operand(3))
buf1, err := getStringBytes(buf1Ptr, buf1Len.Value()) buf1 := getStringBytes(buf1Ptr, buf1Len.Value())
if err != nil { buf2 := getStringBytes(buf2Ptr, buf2Len.Value())
return nil, nil, fr.errorAt(inst, err)
}
buf2, err := getStringBytes(buf2Ptr, buf2Len.Value())
if err != nil {
return nil, nil, fr.errorAt(inst, err)
}
result := []byte(string(buf1) + string(buf2)) result := []byte(string(buf1) + string(buf2))
vals := make([]llvm.Value, len(result)) vals := make([]llvm.Value, len(result))
for i := range vals { for i := range vals {
@@ -369,7 +351,7 @@ func (fr *frame) evalBasicBlock(bb, incoming llvm.BasicBlock, indent string) (re
global.SetLinkage(llvm.InternalLinkage) global.SetLinkage(llvm.InternalLinkage)
global.SetGlobalConstant(true) global.SetGlobalConstant(true)
global.SetUnnamedAddr(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})) retPtr := llvm.ConstGEP(global, getLLVMIndices(fr.Mod.Context().Int32Type(), []uint32{0, 0}))
retLen := llvm.ConstInt(stringType.StructElementTypes()[1], uint64(len(result)), false) retLen := llvm.ConstInt(stringType.StructElementTypes()[1], uint64(len(result)), false)
ret := llvm.ConstNull(stringType) 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?")) return nil, nil, fr.errorAt(inst, errors.New("interp: trying to copy a slice with negative length?"))
} }
for i := int64(0); i < length; i++ { for i := int64(0); i < length; i++ {
var err error
// *dst = *src // *dst = *src
val, err := srcArray.Load() dstArray.Store(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)
}
// dst++ // dst++
dstArrayValue, err := dstArray.GetElementPtr([]uint32{1}) dstArrayValue, err := dstArray.GetElementPtr([]uint32{1})
if err != nil { if err != nil {
@@ -446,10 +422,7 @@ func (fr *frame) evalBasicBlock(bb, incoming llvm.BasicBlock, indent string) (re
// convert a string to a []byte // convert a string to a []byte
bufPtr := fr.getLocal(inst.Operand(0)) bufPtr := fr.getLocal(inst.Operand(0))
bufLen := fr.getLocal(inst.Operand(1)) bufLen := fr.getLocal(inst.Operand(1))
result, err := getStringBytes(bufPtr, bufLen.Value()) result := getStringBytes(bufPtr, bufLen.Value())
if err != nil {
return nil, nil, fr.errorAt(inst, err)
}
vals := make([]llvm.Value, len(result)) vals := make([]llvm.Value, len(result))
for i := range vals { for i := range vals {
vals[i] = llvm.ConstInt(fr.Mod.Context().Int8Type(), uint64(result[i]), false) 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 return &sideEffectResult{severity: sideEffectNone}, nil
case name == "llvm.dbg.value": case name == "llvm.dbg.value":
return &sideEffectResult{severity: sideEffectNone}, nil 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."): case strings.HasPrefix(name, "llvm.lifetime."):
return &sideEffectResult{severity: sideEffectNone}, nil 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 // External function call. Assume only limited side effects
// (no affected globals, etc.). // (no affected globals, etc.).
switch child.Name() { switch child.Name() {
case "runtime.alloc":
continue
case "runtime.typeAssert": case "runtime.typeAssert":
continue // implemented in interp continue // implemented in interp
case "runtime.interfaceImplements": case "runtime.interfaceImplements":
+5 -10
View File
@@ -1,8 +1,6 @@
package interp package interp
import ( import (
"errors"
"tinygo.org/x/go-llvm" "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 // getStringBytes loads the byte slice of a Go string represented as a
// {ptr, len} pair. // {ptr, len} pair.
func getStringBytes(strPtr Value, strLen llvm.Value) ([]byte, error) { func getStringBytes(strPtr Value, strLen llvm.Value) []byte {
if !strLen.IsConstant() { 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()) buf := make([]byte, strLen.ZExtValue())
for i := range buf { for i := range buf {
gep, err := strPtr.GetElementPtr([]uint32{uint32(i)}) gep, err := strPtr.GetElementPtr([]uint32{uint32(i)})
if err != nil { if err != nil {
return nil, err panic(err) // TODO
}
c, err := gep.Load()
if err != nil {
return nil, err
} }
c := gep.Load()
buf[i] = byte(c.ZExtValue()) buf[i] = byte(c.ZExtValue())
} }
return buf, nil return buf
} }
// getLLVMIndices converts an []uint32 into an []llvm.Value, for use in // getLLVMIndices converts an []uint32 into an []llvm.Value, for use in
+72 -66
View File
@@ -15,8 +15,8 @@ type Value interface {
Value() llvm.Value // returns a LLVM value Value() llvm.Value // returns a LLVM value
Type() llvm.Type // equal to Value().Type() Type() llvm.Type // equal to Value().Type()
IsConstant() bool // returns true if this value is a constant value IsConstant() bool // returns true if this value is a constant value
Load() (llvm.Value, error) // dereference a pointer Load() llvm.Value // dereference a pointer
Store(llvm.Value) error // store to a pointer Store(llvm.Value) // store to a pointer
GetElementPtr([]uint32) (Value, error) // returns an interior pointer GetElementPtr([]uint32) (Value, error) // returns an interior pointer
String() string // string representation, for debugging 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. // 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() { if !v.Underlying.IsAGlobalVariable().IsNil() {
return v.Underlying.Initializer(), nil return v.Underlying.Initializer()
} }
switch v.Underlying.Opcode() { switch v.Underlying.Opcode() {
case llvm.GetElementPtr: case llvm.GetElementPtr:
indices := v.getConstGEPIndices() indices := v.getConstGEPIndices()
if indices[0] != 0 { if indices[0] != 0 {
return llvm.Value{}, errors.New("invalid GEP") panic("invalid GEP")
} }
global := v.Eval.getValue(v.Underlying.Operand(0)) global := v.Eval.getValue(v.Underlying.Operand(0))
agg, err := global.Load() agg := global.Load()
if err != nil { return llvm.ConstExtractValue(agg, indices[1:])
return llvm.Value{}, err
}
return llvm.ConstExtractValue(agg, indices[1:]), nil
case llvm.BitCast: case llvm.BitCast:
return llvm.Value{}, errors.New("interp: load from a bitcast") panic("interp: load from a bitcast")
default: 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, // Store stores to the underlying value if the value type is a pointer type,
// otherwise it returns an error. // otherwise it panics.
func (v *LocalValue) Store(value llvm.Value) error { func (v *LocalValue) Store(value llvm.Value) {
if !v.Underlying.IsAGlobalVariable().IsNil() { if !v.Underlying.IsAGlobalVariable().IsNil() {
if !value.IsConstant() { if !value.IsConstant() {
v.MarkDirty() v.MarkDirty()
@@ -77,28 +74,26 @@ func (v *LocalValue) Store(value llvm.Value) error {
} else { } else {
v.Underlying.SetInitializer(value) v.Underlying.SetInitializer(value)
} }
return nil return
} }
if !value.IsConstant() { if !value.IsConstant() {
v.MarkDirty() v.MarkDirty()
v.Eval.builder.CreateStore(value, v.Underlying) v.Eval.builder.CreateStore(value, v.Underlying)
return nil return
} }
switch v.Underlying.Opcode() { switch v.Underlying.Opcode() {
case llvm.GetElementPtr: case llvm.GetElementPtr:
indices := v.getConstGEPIndices() indices := v.getConstGEPIndices()
if indices[0] != 0 { if indices[0] != 0 {
return errors.New("invalid GEP") panic("invalid GEP")
} }
global := &LocalValue{v.Eval, v.Underlying.Operand(0)} global := &LocalValue{v.Eval, v.Underlying.Operand(0)}
agg, err := global.Load() agg := global.Load()
if err != nil {
return err
}
agg = llvm.ConstInsertValue(agg, value, indices[1:]) agg = llvm.ConstInsertValue(agg, value, indices[1:])
return global.Store(agg) global.Store(agg)
return
default: default:
return errors.New("interp: store on a constant") panic("interp: store on a constant")
} }
} }
@@ -183,6 +178,8 @@ type MapValue struct {
ValueSize int ValueSize int
KeyType llvm.Type KeyType llvm.Type
ValueType llvm.Type ValueType llvm.Type
MapType llvm.Type // *%runtime.hashmap
keyVariant string
} }
func (v *MapValue) newBucket() llvm.Value { func (v *MapValue) newBucket() llvm.Value {
@@ -226,16 +223,14 @@ func (v *MapValue) Value() llvm.Value {
var keyBuf []byte var keyBuf []byte
llvmKey := key.Value() llvmKey := key.Value()
llvmValue := v.Values[i].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}) keyPtr := llvm.ConstExtractValue(llvmKey, []uint32{0})
keyLen := llvm.ConstExtractValue(llvmKey, []uint32{1}) keyLen := llvm.ConstExtractValue(llvmKey, []uint32{1})
keyPtrVal := v.Eval.getValue(keyPtr) keyPtrVal := v.Eval.getValue(keyPtr)
var err error keyBuf = getStringBytes(keyPtrVal, keyLen)
keyBuf, err = getStringBytes(keyPtrVal, keyLen) case "binary":
if err != nil { if key.Type().TypeKind() == llvm.IntegerTypeKind {
panic(err) // TODO
}
} else if key.Type().TypeKind() == llvm.IntegerTypeKind {
keyBuf = make([]byte, v.Eval.TargetData.TypeAllocSize(key.Type())) keyBuf = make([]byte, v.Eval.TargetData.TypeAllocSize(key.Type()))
n := key.Value().ZExtValue() n := key.Value().ZExtValue()
for i := range keyBuf { for i := range keyBuf {
@@ -252,6 +247,9 @@ func (v *MapValue) Value() llvm.Value {
} else { } else {
panic("interp: map key type not implemented: " + key.Type().String()) panic("interp: map key type not implemented: " + key.Type().String())
} }
default:
panic("interp: map key variant: " + v.keyVariant)
}
hash := v.hash(keyBuf) hash := v.hash(keyBuf)
if i%8 == 0 && i != 0 { if i%8 == 0 && i != 0 {
@@ -300,7 +298,7 @@ func (v *MapValue) Value() llvm.Value {
// Type returns type runtime.hashmap, which is the actual hashmap type. // Type returns type runtime.hashmap, which is the actual hashmap type.
func (v *MapValue) Type() llvm.Type { func (v *MapValue) Type() llvm.Type {
return v.Eval.Mod.GetTypeByName("runtime.hashmap") return v.MapType
} }
func (v *MapValue) IsConstant() bool { 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. // 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") panic("interp: load from a map")
} }
// Store returns an error: maps are of reference type so cannot be stored to. // Store panics: maps are of reference type so cannot be stored to.
func (v *MapValue) Store(value llvm.Value) error { func (v *MapValue) Store(value llvm.Value) {
// This must be a bug, but it might be helpful to indicate the location panic("interp: store on a map")
// anyway.
return errors.New("interp: store on a map")
} }
// GetElementPtr panics: maps are of reference type so their (interior) // 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") 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 // PutString does a map assign operation, assuming that the map is of type
// map[string]T. // map[string]T.
func (v *MapValue) PutString(keyBuf, keyLen, valPtr *LocalValue) error { func (v *MapValue) PutString(keyBuf, keyLen, valPtr *LocalValue) {
if !v.Underlying.IsNil() { if !v.Underlying.IsNil() {
return errors.New("map already created") panic("map already created")
} }
v.setKeyVariant("string")
if valPtr.Underlying.Opcode() == llvm.BitCast { if valPtr.Underlying.Opcode() == llvm.BitCast {
valPtr = &LocalValue{v.Eval, valPtr.Underlying.Operand(0)} valPtr = &LocalValue{v.Eval, valPtr.Underlying.Operand(0)}
} }
value, err := valPtr.Load() value := valPtr.Load()
if err != nil {
return err
}
if v.ValueType.IsNil() { if v.ValueType.IsNil() {
v.ValueType = value.Type() v.ValueType = value.Type()
if int(v.Eval.TargetData.TypeAllocSize(v.ValueType)) != v.ValueSize { 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 { } else {
if value.Type() != v.ValueType { 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") if v.KeyType.IsNil() {
v.KeyType = keyType v.KeyType = v.Eval.Mod.Context().StructType([]llvm.Type{
key := llvm.ConstNull(keyType) keyBuf.Type(),
keyLen.Type(),
}, false)
}
key := llvm.ConstNull(v.KeyType)
key = llvm.ConstInsertValue(key, keyBuf.Value(), []uint32{0}) key = llvm.ConstInsertValue(key, keyBuf.Value(), []uint32{0})
key = llvm.ConstInsertValue(key, keyLen.Value(), []uint32{1}) key = llvm.ConstInsertValue(key, keyLen.Value(), []uint32{1})
// TODO: avoid duplicate keys // TODO: avoid duplicate keys
v.Keys = append(v.Keys, &LocalValue{v.Eval, key}) v.Keys = append(v.Keys, &LocalValue{v.Eval, key})
v.Values = append(v.Values, &LocalValue{v.Eval, value}) v.Values = append(v.Values, &LocalValue{v.Eval, value})
return nil
} }
// PutBinary does a map assign operation. // 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() { if !v.Underlying.IsNil() {
return errors.New("map already created") panic("map already created")
} }
v.setKeyVariant("binary")
if valPtr.Underlying.Opcode() == llvm.BitCast { if valPtr.Underlying.Opcode() == llvm.BitCast {
valPtr = &LocalValue{v.Eval, valPtr.Underlying.Operand(0)} valPtr = &LocalValue{v.Eval, valPtr.Underlying.Operand(0)}
} }
value, err := valPtr.Load() value := valPtr.Load()
if err != nil {
return err
}
if v.ValueType.IsNil() { if v.ValueType.IsNil() {
v.ValueType = value.Type() v.ValueType = value.Type()
if int(v.Eval.TargetData.TypeAllocSize(v.ValueType)) != v.ValueSize { 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 { } else {
if value.Type() != v.ValueType { 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)} keyPtr = &LocalValue{v.Eval, keyPtr.Underlying.Operand(0)}
} }
} }
key, err := keyPtr.Load() key := keyPtr.Load()
if err != nil {
return err
}
if v.KeyType.IsNil() { if v.KeyType.IsNil() {
v.KeyType = key.Type() v.KeyType = key.Type()
if int(v.Eval.TargetData.TypeAllocSize(v.KeyType)) != v.KeySize { 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 { } else {
if key.Type() != v.KeyType { 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 // TODO: avoid duplicate keys
v.Keys = append(v.Keys, &LocalValue{v.Eval, key}) v.Keys = append(v.Keys, &LocalValue{v.Eval, key})
v.Values = append(v.Values, &LocalValue{v.Eval, value}) v.Values = append(v.Values, &LocalValue{v.Eval, value})
return nil
} }
// Get FNV-1a hash of this string. // 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 package loader
import "go/scanner" import (
"go/token"
"strings"
)
// Errors contains a list of parser errors or a list of typechecker errors for // Errors contains a list of parser errors or a list of typechecker errors for
// the given package. // the given package.
@@ -13,13 +16,24 @@ func (e Errors) Error() string {
return "could not compile: " + e.Errs[0].Error() return "could not compile: " + e.Errs[0].Error()
} }
// Error is a regular error but with an added import stack. This is especially // ImportCycleErrors is returned when encountering an import cycle. The list of
// useful for debugging import cycle errors. // packages is a list from the root package to the leaf package that imports one
type Error struct { // of the packages in the list.
ImportStack []string type ImportCycleError struct {
Err scanner.Error Packages []string
ImportPositions []token.Position
} }
func (e Error) Error() string { func (e *ImportCycleError) Error() string {
return e.Err.Error() 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
}
+331 -209
View File
@@ -2,127 +2,125 @@ package loader
import ( import (
"bytes" "bytes"
"encoding/json"
"errors" "errors"
"fmt"
"go/ast" "go/ast"
"go/build"
"go/parser" "go/parser"
"go/scanner" "go/scanner"
"go/token" "go/token"
"go/types" "go/types"
"io"
"os" "os"
"os/exec"
"path/filepath" "path/filepath"
"strconv" "sort"
"strings" "strings"
"syscall" "text/template"
"github.com/tinygo-org/tinygo/cgo" "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. // Program holds all packages and some metadata about the program as a whole.
type Program struct { type Program struct {
config *compileopts.Config mainPkg string
clangHeaders string Build *build.Context
typeChecker types.Config OverlayBuild *build.Context
goroot string // synthetic GOROOT OverlayPath func(path string) string
workingDir string
Packages map[string]*Package Packages map[string]*Package
sorted []*Package sorted []*Package
fset *token.FileSet fset *token.FileSet
TypeChecker types.Config
// Information obtained during parsing. Dir string // current working directory (for error reporting)
LDFlags []string TINYGOROOT string // root of the TinyGo installation or root of the source code
} CFlags []string
ClangHeaders 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
}
} }
// Package holds a loaded package, its imports, and its parsed files. // Package holds a loaded package, its imports, and its parsed files.
type Package struct { type Package struct {
PackageJSON *Program
*build.Package
program *Program Imports map[string]*Package
Importing bool
Files []*ast.File Files []*ast.File
Pkg *types.Package Pkg *types.Package
info types.Info types.Info
} }
// Load loads the given package with all dependencies (including the runtime // Import loads the given package relative to srcDir (for the vendor directory).
// package). Call .Parse() afterwards to parse all Go files (including CGo // It only loads the current package without recursion.
// processing, if necessary). func (p *Program) Import(path, srcDir string, pos token.Position) (*Package, error) {
func Load(config *compileopts.Config, inputPkgs []string, clangHeaders string, typeChecker types.Config) (*Program, error) { if p.Packages == nil {
goroot, err := GetCachedGoroot(config) p.Packages = make(map[string]*Package)
}
// Load this package.
ctx := p.Build
if newPath := p.OverlayPath(path); newPath != "" {
ctx = p.OverlayBuild
path = newPath
}
buildPkg, err := ctx.Import(path, srcDir, build.ImportComment)
if err != nil {
return nil, scanner.Error{
Pos: pos,
Msg: err.Error(), // TODO: define a new error type that will wrap the inner error
}
}
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
}
return pkg, nil
}
// 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 { if err != nil {
return nil, err return nil, err
} }
wd, err := os.Getwd() buildPkg := &build.Package{
if err != nil { Dir: filepath.Dir(path),
return nil, err ImportPath: path,
GoFiles: []string{filepath.Base(path)},
} }
p := &Program{ for _, importSpec := range file.Imports {
config: config, buildPkg.Imports = append(buildPkg.Imports, importSpec.Path.Value[1:len(importSpec.Path.Value)-1])
clangHeaders: clangHeaders, }
typeChecker: typeChecker, p.sorted = nil // invalidate the sorted order of packages
goroot: goroot, pkg := p.newPackage(buildPkg)
workingDir: wd, p.Packages[buildPkg.ImportPath] = pkg
Packages: make(map[string]*Package),
fset: token.NewFileSet(), if p.mainPkg == "" {
p.mainPkg = buildPkg.ImportPath
} }
// List the dependencies of this package, in raw JSON format. return pkg, nil
extraArgs := []string{"-json", "-deps"}
if config.TestConfig.CompileTestBinary {
extraArgs = append(extraArgs, "-test")
}
cmd, err := List(config, extraArgs, inputPkgs)
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, fmt.Errorf("failed to run `go list`: %s", err)
} }
// Parse the returned json from `go list`. // newPackage instantiates a new *Package object with initialized members.
decoder := json.NewDecoder(buf) func (p *Program) newPackage(pkg *build.Package) *Package {
for { return &Package{
pkg := &Package{ Program: p,
program: p, Package: pkg,
info: types.Info{ Imports: make(map[string]*Package, len(pkg.Imports)),
Info: types.Info{
Types: make(map[ast.Expr]types.TypeAndValue), Types: make(map[ast.Expr]types.TypeAndValue),
Defs: make(map[*ast.Ident]types.Object), Defs: make(map[*ast.Ident]types.Object),
Uses: make(map[*ast.Ident]types.Object), Uses: make(map[*ast.Ident]types.Object),
@@ -131,110 +129,99 @@ func Load(config *compileopts.Config, inputPkgs []string, clangHeaders string, t
Selections: make(map[*ast.SelectorExpr]*types.Selection), 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
}
// 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
}
}
}
return originalPath
} }
// Sorted returns a list of all packages, sorted in a way that no packages come // Sorted returns a list of all packages, sorted in a way that no packages come
// before the packages they depend upon. // before the packages they depend upon.
func (p *Program) Sorted() []*Package { func (p *Program) Sorted() []*Package {
if p.sorted == nil {
p.sort()
}
return p.sorted return p.sorted
} }
// MainPkg returns the last package in the Sorted() slice. This is the main func (p *Program) sort() {
// package of the program. p.sorted = nil
func (p *Program) MainPkg() *Package { packageList := make([]*Package, 0, len(p.Packages))
return p.sorted[len(p.sorted)-1] 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
} }
// Parse parses all packages and typechecks them. 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 recursively imports all packages, parses them, and typechecks them.
// //
// The returned error may be an Errors error, which contains a list of errors. // The returned error may be an Errors error, which contains a list of errors.
// //
// Idempotent. // 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. // Parse all packages.
// TODO: do this in parallel. for _, pkg := range p.Sorted() {
for _, pkg := range p.sorted { err := pkg.Parse(includeTests)
err := pkg.Parse() if err != nil {
return err
}
}
if compileTestBinary {
err := p.SwapTestMain()
if err != nil { if err != nil {
return err return err
} }
} }
// Typecheck all packages. // Typecheck all packages.
for _, pkg := range p.sorted { for _, pkg := range p.Sorted() {
err := pkg.Check() err := pkg.Check()
if err != nil { if err != nil {
return err return err
@@ -244,6 +231,83 @@ func (p *Program) Parse() error {
return nil 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. // parseFile is a wrapper around parser.ParseFile.
func (p *Program) parseFile(path string, mode parser.Mode) (*ast.File, error) { func (p *Program) parseFile(path string, mode parser.Mode) (*ast.File, error) {
if p.fset == nil { if p.fset == nil {
@@ -255,26 +319,34 @@ func (p *Program) parseFile(path string, mode parser.Mode) (*ast.File, error) {
return nil, err return nil, err
} }
defer rd.Close() 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. // Parse parses and typechecks this package.
// //
// Idempotent. // Idempotent.
func (p *Package) Parse() error { func (p *Package) Parse(includeTests bool) error {
if len(p.Files) != 0 { if len(p.Files) != 0 {
return nil // nothing to do (?) return nil
} }
// Load the AST. // Load the AST.
// TODO: do this in parallel.
if p.ImportPath == "unsafe" { if p.ImportPath == "unsafe" {
// Special case for the unsafe package, which is defined internally by // Special case for the unsafe package. Don't even bother loading
// the types package. // the files.
p.Pkg = types.Unsafe p.Pkg = types.Unsafe
return nil return nil
} }
files, err := p.parseFiles() files, err := p.parseFiles(includeTests)
if err != nil { if err != nil {
return err return err
} }
@@ -289,11 +361,11 @@ func (p *Package) Parse() error {
// Idempotent. // Idempotent.
func (p *Package) Check() error { func (p *Package) Check() error {
if p.Pkg != nil { if p.Pkg != nil {
return nil // already typechecked return nil
} }
var typeErrors []error var typeErrors []error
checker := p.program.typeChecker // make a copy, because it will be modified checker := p.TypeChecker
checker.Error = func(err error) { checker.Error = func(err error) {
typeErrors = append(typeErrors, err) typeErrors = append(typeErrors, err)
} }
@@ -301,7 +373,7 @@ func (p *Package) Check() error {
// Do typechecking of the package. // Do typechecking of the package.
checker.Importer = p 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 != nil {
if err, ok := err.(Errors); ok { if err, ok := err.(Errors); ok {
return err return err
@@ -313,47 +385,52 @@ func (p *Package) Check() error {
} }
// parseFiles parses the loaded list of files and returns this list. // 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 files []*ast.File
var fileErrs []error var fileErrs []error
// Parse all files (incuding CgoFiles). var gofiles []string
parseFile := func(file string) { if includeTests {
if !filepath.IsAbs(file) { gofiles = make([]string, 0, len(p.GoFiles)+len(p.TestGoFiles))
file = filepath.Join(p.Dir, file) gofiles = append(gofiles, p.GoFiles...)
gofiles = append(gofiles, p.TestGoFiles...)
} else {
gofiles = p.GoFiles
} }
f, err := p.program.parseFile(file, parser.ParseComments)
for _, file := range gofiles {
f, err := p.parseFile(filepath.Join(p.Package.Dir, file), parser.ParseComments)
if err != nil { if err != nil {
fileErrs = append(fileErrs, err) fileErrs = append(fileErrs, err)
return continue
}
if err != nil {
fileErrs = append(fileErrs, err)
continue
} }
files = append(files, f) files = append(files, f)
} }
for _, file := range p.GoFiles {
parseFile(file)
}
for _, file := range p.CgoFiles { for _, file := range p.CgoFiles {
parseFile(file) path := filepath.Join(p.Package.Dir, file)
f, err := p.parseFile(path, parser.ParseComments)
if err != nil {
fileErrs = append(fileErrs, err)
continue
}
files = append(files, f)
} }
// Do CGo processing.
if len(p.CgoFiles) != 0 { if len(p.CgoFiles) != 0 {
var cflags []string cflags := append(p.CFlags, "-I"+p.Package.Dir)
cflags = append(cflags, p.program.config.CFlags()...) if p.ClangHeaders != "" {
cflags = append(cflags, "-I"+p.Dir) cflags = append(cflags, "-Xclang", "-internal-isystem", "-Xclang", p.ClangHeaders)
if p.program.clangHeaders != "" {
cflags = append(cflags, "-Xclang", "-internal-isystem", "-Xclang", p.program.clangHeaders)
} }
generated, ldflags, errs := cgo.Process(files, p.program.workingDir, p.program.fset, cflags) generated, errs := cgo.Process(files, p.Program.Dir, p.fset, cflags)
if errs != nil { if errs != nil {
fileErrs = append(fileErrs, errs...) fileErrs = append(fileErrs, errs...)
} }
files = append(files, generated) 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 { if len(fileErrs) != 0 {
return nil, Errors{p, fileErrs} return nil, Errors{p, fileErrs}
} }
@@ -367,14 +444,59 @@ func (p *Package) Import(to string) (*types.Package, error) {
if to == "unsafe" { if to == "unsafe" {
return types.Unsafe, nil return types.Unsafe, nil
} }
if replace, ok := p.ImportMap[to]; ok { if _, ok := p.Imports[to]; ok {
// This import path should be replaced by another import path, according return p.Imports[to].Pkg, nil
// to `go list`.
to = replace
}
if imported, ok := p.program.Packages[to]; ok {
return imported.Pkg, nil
} else { } else {
return nil, errors.New("package not imported: " + to) 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 { func (p *Program) LoadSSA() *ssa.Program {
prog := ssa.NewProgram(p.fset, ssa.SanityCheckFunctions|ssa.BareInits|ssa.GlobalDebug) prog := ssa.NewProgram(p.fset, ssa.SanityCheckFunctions|ssa.BareInits|ssa.GlobalDebug)
for _, pkg := range p.sorted { for _, pkg := range p.Sorted() {
prog.CreatePackage(pkg.Pkg, pkg.Files, &pkg.info, true) prog.CreatePackage(pkg.Pkg, pkg.Files, &pkg.Info, true)
} }
return prog return prog
+99 -222
View File
@@ -8,7 +8,6 @@ import (
"go/scanner" "go/scanner"
"go/types" "go/types"
"io" "io"
"io/ioutil"
"os" "os"
"os/exec" "os/exec"
"os/signal" "os/signal"
@@ -29,12 +28,6 @@ import (
"go.bug.st/serial" "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 // commandError is an error type to wrap os/exec.Command errors. This provides
// some more information regarding what went wrong while running a command. // some more information regarding what went wrong while running a command.
type commandError struct { type commandError struct {
@@ -65,25 +58,35 @@ func moveFile(src, dst string) error {
return os.Remove(src) return os.Remove(src)
} }
// copyFile copies the given file from src to dst. It can copy over // copyFile copies the given file from src to dst. It copies first to a .tmp
// a possibly already existing file at the destination. // file which is then moved over a possibly already existing file at the
// destination.
func copyFile(src, dst string) error { func copyFile(src, dst string) error {
source, err := os.Open(src) inf, err := os.Open(src)
if err != nil { if err != nil {
return err return err
} }
defer source.Close() defer inf.Close()
outpath := dst + ".tmp"
destination, err := os.Create(dst) outf, err := os.Create(outpath)
if err != nil { if err != nil {
return err return err
} }
defer destination.Close()
_, err = io.Copy(destination, source) _, err = io.Copy(outf, inf)
if err != nil {
os.Remove(outpath)
return err 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. // Build compiles and links the given package and writes it to outpath.
func Build(pkgName, outpath string, options *compileopts.Options) error { func Build(pkgName, outpath string, options *compileopts.Options) error {
config, err := builder.NewConfig(options) config, err := builder.NewConfig(options)
@@ -91,10 +94,10 @@ func Build(pkgName, outpath string, options *compileopts.Options) error {
return err return err
} }
return builder.Build(pkgName, outpath, config, func(result builder.BuildResult) error { return builder.Build(pkgName, outpath, config, func(tmppath string) error {
if err := os.Rename(result.Binary, outpath); err != nil { if err := os.Rename(tmppath, outpath); err != nil {
// Moving failed. Do a file copy. // Moving failed. Do a file copy.
inf, err := os.Open(result.Binary) inf, err := os.Open(tmppath)
if err != nil { if err != nil {
return err return err
} }
@@ -121,17 +124,22 @@ func Build(pkgName, outpath string, options *compileopts.Options) error {
// Test runs the tests in the given package. // Test runs the tests in the given package.
func Test(pkgName string, options *compileopts.Options) error { func Test(pkgName string, options *compileopts.Options) error {
options.TestConfig.CompileTestBinary = true
config, err := builder.NewConfig(options) config, err := builder.NewConfig(options)
if err != nil { if err != nil {
return err return err
} }
return builder.Build(pkgName, ".elf", config, func(result builder.BuildResult) error { // Add test build tag. This is incorrect: `go test` only looks at the
cmd := exec.Command(result.Binary) // _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.Stdout = os.Stdout
cmd.Stderr = os.Stderr cmd.Stderr = os.Stderr
cmd.Dir = result.MainDir
err := cmd.Run() err := cmd.Run()
if err != nil { if err != nil {
// Propagate the exit code // Propagate the exit code
@@ -141,7 +149,7 @@ func Test(pkgName string, options *compileopts.Options) error {
} }
os.Exit(1) os.Exit(1)
} }
return &commandError{"failed to run compiled binary", result.Binary, err} return &commandError{"failed to run compiled binary", tmppath, err}
} }
return nil return nil
}) })
@@ -185,9 +193,9 @@ func Flash(pkgName, port string, options *compileopts.Options) error {
return errors.New("unknown flash method: " + flashMethod) 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? // 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 == "" { if port == "" {
var err error var err error
port, err = getDefaultPort() port, err = getDefaultPort()
@@ -198,7 +206,7 @@ func Flash(pkgName, port string, options *compileopts.Options) error {
err := touchSerialPortAt1200bps(port) err := touchSerialPortAt1200bps(port)
if err != nil { 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 // give the target MCU a chance to restart into bootloader
time.Sleep(3 * time.Second) time.Sleep(3 * time.Second)
@@ -210,7 +218,7 @@ func Flash(pkgName, port string, options *compileopts.Options) error {
// Create the command. // Create the command.
flashCmd := config.Target.FlashCommand flashCmd := config.Target.FlashCommand
fileToken := "{" + fileExt[1:] + "}" fileToken := "{" + fileExt[1:] + "}"
flashCmd = strings.Replace(flashCmd, fileToken, result.Binary, -1) flashCmd = strings.Replace(flashCmd, fileToken, tmppath, -1)
if port == "" && strings.Contains(flashCmd, "{port}") { if port == "" && strings.Contains(flashCmd, "{port}") {
var err error var err error
@@ -240,21 +248,21 @@ func Flash(pkgName, port string, options *compileopts.Options) error {
cmd.Dir = goenv.Get("TINYGOROOT") cmd.Dir = goenv.Get("TINYGOROOT")
err := cmd.Run() err := cmd.Run()
if err != nil { if err != nil {
return &commandError{"failed to flash", result.Binary, err} return &commandError{"failed to flash", tmppath, err}
} }
return nil return nil
case "msd": case "msd":
switch fileExt { switch fileExt {
case ".uf2": case ".uf2":
err := flashUF2UsingMSD(config.Target.FlashVolume, result.Binary) err := flashUF2UsingMSD(config.Target.FlashVolume, tmppath)
if err != nil { if err != nil {
return &commandError{"failed to flash", result.Binary, err} return &commandError{"failed to flash", tmppath, err}
} }
return nil return nil
case ".hex": case ".hex":
err := flashHexUsingMSD(config.Target.FlashVolume, result.Binary) err := flashHexUsingMSD(config.Target.FlashVolume, tmppath)
if err != nil { if err != nil {
return &commandError{"failed to flash", result.Binary, err} return &commandError{"failed to flash", tmppath, err}
} }
return nil return nil
default: default:
@@ -265,13 +273,13 @@ func Flash(pkgName, port string, options *compileopts.Options) error {
if err != nil { if err != nil {
return err 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 := exec.Command("openocd", args...)
cmd.Stdout = os.Stdout cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr cmd.Stderr = os.Stderr
err = cmd.Run() err = cmd.Run()
if err != nil { if err != nil {
return &commandError{"failed to flash", result.Binary, err} return &commandError{"failed to flash", tmppath, err}
} }
return nil return nil
default: 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 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. // Find a good way to run GDB.
gdbInterface, openocdInterface := config.Programmer() gdbInterface, openocdInterface := config.Programmer()
switch gdbInterface { switch gdbInterface {
@@ -359,7 +367,7 @@ func FlashGDB(pkgName string, ocdOutput bool, options *compileopts.Options) erro
gdbCommands = append(gdbCommands, "target remote :1234") gdbCommands = append(gdbCommands, "target remote :1234")
// Run in an emulator. // 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 = exec.Command(config.Target.Emulator[0], args...)
daemon.Stdout = os.Stdout daemon.Stdout = os.Stdout
daemon.Stderr = os.Stderr daemon.Stderr = os.Stderr
@@ -367,7 +375,7 @@ func FlashGDB(pkgName string, ocdOutput bool, options *compileopts.Options) erro
gdbCommands = append(gdbCommands, "target remote :2345") gdbCommands = append(gdbCommands, "target remote :2345")
// Run in an emulator. // 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 = exec.Command(config.Target.Emulator[0], args...)
daemon.Stdout = os.Stdout daemon.Stdout = os.Stdout
daemon.Stderr = os.Stderr daemon.Stderr = os.Stderr
@@ -405,7 +413,7 @@ func FlashGDB(pkgName string, ocdOutput bool, options *compileopts.Options) erro
// Construct and execute a gdb command. // Construct and execute a gdb command.
// By default: gdb -ex run <binary> // By default: gdb -ex run <binary>
// Exit GDB with Ctrl-D. // Exit GDB with Ctrl-D.
params := []string{result.Binary} params := []string{tmppath}
for _, cmd := range gdbCommands { for _, cmd := range gdbCommands {
params = append(params, "-ex", cmd) params = append(params, "-ex", cmd)
} }
@@ -415,7 +423,7 @@ func FlashGDB(pkgName string, ocdOutput bool, options *compileopts.Options) erro
cmd.Stderr = os.Stderr cmd.Stderr = os.Stderr
err := cmd.Run() err := cmd.Run()
if err != nil { if err != nil {
return &commandError{"failed to run gdb with", result.Binary, err} return &commandError{"failed to run gdb with", tmppath, err}
} }
return nil return nil
}) })
@@ -431,10 +439,10 @@ func Run(pkgName string, options *compileopts.Options) error {
return err 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 { if len(config.Target.Emulator) == 0 {
// Run directly. // Run directly.
cmd := exec.Command(result.Binary) cmd := exec.Command(tmppath)
cmd.Stdout = os.Stdout cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr cmd.Stderr = os.Stderr
err := cmd.Run() err := cmd.Run()
@@ -443,12 +451,12 @@ func Run(pkgName string, options *compileopts.Options) error {
// Workaround for QEMU which always exits with an error. // Workaround for QEMU which always exits with an error.
return nil return nil
} }
return &commandError{"failed to run compiled binary", result.Binary, err} return &commandError{"failed to run compiled binary", tmppath, err}
} }
return nil return nil
} else { } else {
// Run in an emulator. // 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 := exec.Command(config.Target.Emulator[0], args...)
cmd.Stdout = os.Stdout cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr 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. // Workaround for QEMU which always exits with an error.
return nil return nil
} }
return &commandError{"failed to run emulator with", result.Binary, err} return &commandError{"failed to run emulator with", tmppath, err}
} }
return nil return nil
} }
@@ -471,13 +479,6 @@ func touchSerialPortAt1200bps(port string) (err error) {
// Open port // Open port
p, e := serial.Open(port, &serial.Mode{BaudRate: 1200}) p, e := serial.Open(port, &serial.Mode{BaudRate: 1200})
if e != nil { 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) time.Sleep(1 * time.Second)
err = e err = e
continue continue
@@ -490,8 +491,6 @@ func touchSerialPortAt1200bps(port string) (err error) {
return fmt.Errorf("opening port: %s", err) return fmt.Errorf("opening port: %s", err)
} }
const maxMSDRetries = 10
func flashUF2UsingMSD(volume, tmppath string) error { func flashUF2UsingMSD(volume, tmppath string) error {
// find standard UF2 info path // find standard UF2 info path
var infoPath string var infoPath string
@@ -508,12 +507,15 @@ func flashUF2UsingMSD(volume, tmppath string) error {
infoPath = path + "/INFO_UF2.TXT" infoPath = path + "/INFO_UF2.TXT"
} }
d, err := locateDevice(volume, infoPath) d, err := filepath.Glob(infoPath)
if err != nil { if err != nil {
return err 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 { func flashHexUsingMSD(volume, tmppath string) error {
@@ -532,31 +534,15 @@ func flashHexUsingMSD(volume, tmppath string) error {
destPath = path + "/" destPath = path + "/"
} }
d, err := locateDevice(volume, destPath) d, err := filepath.Glob(destPath)
if err != nil { if err != nil {
return err 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 { 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) { func windowsFindUSBDrive(volume string) (string, error) {
@@ -617,18 +603,29 @@ func getDefaultPort() (port string, err error) {
case "freebsd": case "freebsd":
portPath = "/dev/cuaU*" portPath = "/dev/cuaU*"
case "windows": 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 { if err != nil {
return "", err return "", err
} }
if len(ports) == 0 { if out.String() == "No Instance(s) Available." {
return "", errors.New("no serial ports 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: default:
return "", errors.New("unable to search for a default USB device to be flashed on this OS") 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() { func usage() {
fmt.Fprintln(os.Stderr, "TinyGo is a Go compiler for small places.") 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.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, "\ncommands:")
fmt.Fprintln(os.Stderr, " build: compile packages and dependencies") 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, " flash: compile and flash to the device")
fmt.Fprintln(os.Stderr, " gdb: run/flash and immediately enter GDB") 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, " 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, " clean: empty cache directory ("+goenv.Get("GOCACHE")+")")
fmt.Fprintln(os.Stderr, " help: print this help text") fmt.Fprintln(os.Stderr, " help: print this help text")
fmt.Fprintln(os.Stderr, "\nflags:") fmt.Fprintln(os.Stderr, "\nflags:")
flag.PrintDefaults() 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 // printCompilerError prints compiler errors using the provided logger function
// (similar to fmt.Println). // (similar to fmt.Println).
// //
@@ -683,24 +665,8 @@ func tryToMakePathRelative(dir string) string {
// to limitations in the LLVM bindings. // to limitations in the LLVM bindings.
func printCompilerError(logln func(...interface{}), err error) { func printCompilerError(logln func(...interface{}), err error) {
switch err := err.(type) { switch err := err.(type) {
case types.Error: case types.Error, scanner.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)
}
logln(err) logln(err)
case scanner.ErrorList:
for _, scannerErr := range err {
printCompilerError(logln, *scannerErr)
}
case *interp.Error: case *interp.Error:
logln("#", err.ImportPath) logln("#", err.ImportPath)
logln(err.Error()) logln(err.Error())
@@ -719,17 +685,11 @@ func printCompilerError(logln func(...interface{}), err error) {
case loader.Errors: case loader.Errors:
logln("#", err.Pkg.ImportPath) logln("#", err.Pkg.ImportPath)
for _, err := range err.Errs { for _, err := range err.Errs {
printCompilerError(logln, err) logln(err)
}
case loader.Error:
logln(err.Err.Error())
logln("package", err.ImportStack[0])
for _, pkgPath := range err.ImportStack[1:] {
logln("\timports", pkgPath)
} }
case *builder.MultiError: case *builder.MultiError:
for _, err := range err.Errs { for _, err := range err.Errs {
printCompilerError(logln, err) logln(err)
} }
default: default:
logln("error:", err) logln("error:", err)
@@ -746,25 +706,17 @@ func handleCompilerError(err error) {
} }
func main() { 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") outpath := flag.String("o", "", "output filename")
opt := flag.String("opt", "z", "optimization level: 0, 1, 2, s, z") opt := flag.String("opt", "z", "optimization level: 0, 1, 2, s, z")
gc := flag.String("gc", "", "garbage collector to use (none, leaking, extalloc, conservative)") gc := flag.String("gc", "", "garbage collector to use (none, leaking, extalloc, conservative)")
panicStrategy := flag.String("panic", "print", "panic strategy (print, trap)") 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") printIR := flag.Bool("printir", false, "print LLVM IR")
dumpSSA := flag.Bool("dumpssa", false, "dump internal Go SSA") dumpSSA := flag.Bool("dumpssa", false, "dump internal Go SSA")
verifyIR := flag.Bool("verifyir", false, "run extra verification steps on LLVM IR") verifyIR := flag.Bool("verifyir", false, "run extra verification steps on LLVM IR")
tags := flag.String("tags", "", "a space-separated list of extra build tags") tags := flag.String("tags", "", "a space-separated list of extra build tags")
target := flag.String("target", "", "LLVM target | .json file with TargetSpec") target := flag.String("target", "", "LLVM target | .json file with TargetSpec")
printSize := flag.String("size", "", "print sizes (none, short, full)") 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") nodebug := flag.Bool("no-debug", false, "disable DWARF debug symbol generation")
ocdOutput := flag.Bool("ocd-output", false, "print OCD daemon output during debug") ocdOutput := flag.Bool("ocd-output", false, "print OCD daemon output during debug")
port := flag.String("port", "", "flash port") 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") 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)") heapSize := flag.String("heap-size", "1M", "default heap size in bytes (only supported by WebAssembly)")
var flagJSON, flagDeps *bool if len(os.Args) < 2 {
if command == "list" { fmt.Fprintln(os.Stderr, "No command-line arguments supplied.")
flagJSON = flag.Bool("json", false, "print data in JSON format") usage()
flagDeps = flag.Bool("deps", false, "") os.Exit(1)
} }
command := os.Args[1]
// Early command processing, before commands are interpreted by the Go flag // Early command processing, before commands are interpreted by the Go flag
// library. // library.
@@ -804,7 +757,6 @@ func main() {
VerifyIR: *verifyIR, VerifyIR: *verifyIR,
Debug: !*nodebug, Debug: !*nodebug,
PrintSizes: *printSize, PrintSizes: *printSize,
PrintStacks: *printStacks,
Tags: *tags, Tags: *tags,
WasmAbi: *wasmAbi, WasmAbi: *wasmAbi,
Programmer: *programmer, Programmer: *programmer,
@@ -818,6 +770,12 @@ func main() {
options.LDFlags = strings.Split(*ldFlags, " ") 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 var err error
if options.HeapSize, err = parseSize(*heapSize); err != nil { if options.HeapSize, err = parseSize(*heapSize); err != nil {
fmt.Fprintln(os.Stderr, "Could not read heap size:", *heapSize) fmt.Fprintln(os.Stderr, "Could not read heap size:", *heapSize)
@@ -827,13 +785,6 @@ func main() {
os.Setenv("CC", "clang -target="+*target) os.Setenv("CC", "clang -target="+*target)
err = options.Verify()
if err != nil {
fmt.Fprintln(os.Stderr, err.Error())
usage()
os.Exit(1)
}
switch command { switch command {
case "build": case "build":
if *outpath == "" { if *outpath == "" {
@@ -843,7 +794,7 @@ func main() {
} }
pkgName := "." pkgName := "."
if flag.NArg() == 1 { if flag.NArg() == 1 {
pkgName = filepath.ToSlash(flag.Arg(0)) pkgName = flag.Arg(0)
} else if flag.NArg() > 1 { } else if flag.NArg() > 1 {
fmt.Fprintln(os.Stderr, "build only accepts a single positional argument: package name, but multiple were specified") fmt.Fprintln(os.Stderr, "build only accepts a single positional argument: package name, but multiple were specified")
usage() usage()
@@ -852,7 +803,6 @@ func main() {
if options.Target == "" && filepath.Ext(*outpath) == ".wasm" { if options.Target == "" && filepath.Ext(*outpath) == ".wasm" {
options.Target = "wasm" options.Target = "wasm"
} }
err := Build(pkgName, *outpath, options) err := Build(pkgName, *outpath, options)
handleCompilerError(err) handleCompilerError(err)
case "build-library": case "build-library":
@@ -889,9 +839,8 @@ func main() {
usage() usage()
os.Exit(1) os.Exit(1)
} }
pkgName := filepath.ToSlash(flag.Arg(0))
if command == "flash" { if command == "flash" {
err := Flash(pkgName, *port, options) err := Flash(flag.Arg(0), *port, options)
handleCompilerError(err) handleCompilerError(err)
} else { } else {
if !options.Debug { if !options.Debug {
@@ -899,7 +848,7 @@ func main() {
usage() usage()
os.Exit(1) os.Exit(1)
} }
err := FlashGDB(pkgName, *ocdOutput, options) err := FlashGDB(flag.Arg(0), *ocdOutput, options)
handleCompilerError(err) handleCompilerError(err)
} }
case "run": case "run":
@@ -908,13 +857,12 @@ func main() {
usage() usage()
os.Exit(1) os.Exit(1)
} }
pkgName := filepath.ToSlash(flag.Arg(0)) err := Run(flag.Arg(0), options)
err := Run(pkgName, options)
handleCompilerError(err) handleCompilerError(err)
case "test": case "test":
pkgName := "." pkgName := "."
if flag.NArg() == 1 { if flag.NArg() == 1 {
pkgName = filepath.ToSlash(flag.Arg(0)) pkgName = flag.Arg(0)
} else if flag.NArg() > 1 { } else if flag.NArg() > 1 {
fmt.Fprintln(os.Stderr, "test only accepts a single positional argument: package name, but multiple were specified") fmt.Fprintln(os.Stderr, "test only accepts a single positional argument: package name, but multiple were specified")
usage() usage()
@@ -922,35 +870,6 @@ func main() {
} }
err := Test(pkgName, options) err := Test(pkgName, options)
handleCompilerError(err) 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": case "info":
if flag.NArg() == 1 { if flag.NArg() == 1 {
options.Target = flag.Arg(0) options.Target = flag.Arg(0)
@@ -970,50 +889,12 @@ func main() {
fmt.Fprintln(os.Stderr, err) fmt.Fprintln(os.Stderr, err)
os.Exit(1) 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("LLVM triple: %s\n", config.Triple())
fmt.Printf("GOOS: %s\n", config.GOOS()) fmt.Printf("GOOS: %s\n", config.GOOS())
fmt.Printf("GOARCH: %s\n", config.GOARCH()) fmt.Printf("GOARCH: %s\n", config.GOARCH())
fmt.Printf("build tags: %s\n", strings.Join(config.BuildTags(), " ")) fmt.Printf("build tags: %s\n", strings.Join(config.BuildTags(), " "))
fmt.Printf("garbage collector: %s\n", config.GC()) fmt.Printf("garbage collector: %s\n", config.GC())
fmt.Printf("scheduler: %s\n", config.Scheduler()) 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": case "clean":
// remove cache directory // remove cache directory
err := os.RemoveAll(goenv.Get("GOCACHE")) err := os.RemoveAll(goenv.Get("GOCACHE"))
@@ -1025,13 +906,9 @@ func main() {
usage() usage()
case "version": case "version":
goversion := "<unknown>" goversion := "<unknown>"
if s, err := goenv.GorootVersionString(goenv.Get("GOROOT")); err == nil { if s, err := builder.GorootVersionString(goenv.Get("GOROOT")); err == nil {
goversion = s 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) 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": case "env":
if flag.NArg() == 0 { if flag.NArg() == 0 {
+2 -13
View File
@@ -6,7 +6,6 @@ package main
import ( import (
"bufio" "bufio"
"bytes" "bytes"
"flag"
"fmt" "fmt"
"io/ioutil" "io/ioutil"
"os" "os"
@@ -26,8 +25,6 @@ import (
const TESTDATA = "testdata" const TESTDATA = "testdata"
var testTarget = flag.String("target", "", "override test target")
func TestCompiler(t *testing.T) { func TestCompiler(t *testing.T) {
matches, err := filepath.Glob(filepath.Join(TESTDATA, "*.go")) matches, err := filepath.Glob(filepath.Join(TESTDATA, "*.go"))
if err != nil { if err != nil {
@@ -47,14 +44,6 @@ func TestCompiler(t *testing.T) {
sort.Strings(matches) 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" { if runtime.GOOS != "windows" {
t.Run("Host", func(t *testing.T) { t.Run("Host", func(t *testing.T) {
runPlatTests("", matches, t) runPlatTests("", matches, t)
@@ -84,7 +73,7 @@ func TestCompiler(t *testing.T) {
t.Run("ARM64Linux", func(t *testing.T) { t.Run("ARM64Linux", func(t *testing.T) {
runPlatTests("aarch64--linux-gnu", matches, t) runPlatTests("aarch64--linux-gnu", matches, t)
}) })
goVersion, err := goenv.GorootVersionString(goenv.Get("GOROOT")) goVersion, err := builder.GorootVersionString(goenv.Get("GOROOT"))
if err != nil { if err != nil {
t.Error("could not get Go version:", err) t.Error("could not get Go version:", err)
return return
@@ -157,7 +146,7 @@ func runTest(path, target string, t *testing.T) {
PrintIR: false, PrintIR: false,
DumpSSA: false, DumpSSA: false,
VerifyIR: true, VerifyIR: true,
Debug: true, Debug: false,
PrintSizes: "", PrintSizes: "",
WasmAbi: "js", WasmAbi: "js",
} }
+1 -6
View File
@@ -174,11 +174,6 @@ func EnableIRQ(irq uint32) {
NVIC.ISER[irq>>5].Set(1 << (irq & 0x1F)) 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. // Set the priority of the given interrupt number.
// Note that the priority is given as a 0-255 number, where some of the lower // 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 // 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 { func DisableInterrupts() uintptr {
return AsmFull(` return AsmFull(`
mrs {}, PRIMASK mrs {}, PRIMASK
cpsid i cpsid if
`, nil) `, nil)
} }
-7
View File
@@ -1,11 +1,9 @@
.syntax unified .syntax unified
.cfi_sections .debug_frame
.section .text.HardFault_Handler .section .text.HardFault_Handler
.global HardFault_Handler .global HardFault_Handler
.type HardFault_Handler, %function .type HardFault_Handler, %function
HardFault_Handler: HardFault_Handler:
.cfi_startproc
// Put the old stack pointer in the first argument, for easy debugging. This // Put the old stack pointer in the first argument, for easy debugging. This
// is especially useful on Cortex-M0, which supports far fewer debug // is especially useful on Cortex-M0, which supports far fewer debug
// facilities. // facilities.
@@ -21,8 +19,6 @@ HardFault_Handler:
// Continue handling this error in Go. // Continue handling this error in Go.
bl handleHardFault bl handleHardFault
.cfi_endproc
.size HardFault_Handler, .-HardFault_Handler
// This is a convenience function for semihosting support. // This is a convenience function for semihosting support.
// At some point, this should be replaced by inline assembly. // At some point, this should be replaced by inline assembly.
@@ -30,8 +26,5 @@ HardFault_Handler:
.global SemihostingCall .global SemihostingCall
.type SemihostingCall, %function .type SemihostingCall, %function
SemihostingCall: SemihostingCall:
.cfi_startproc
bkpt 0xab bkpt 0xab
bx lr 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 // You can use {} in the asm string (which expands to a register) to set the
// return value. // return value.
func AsmFull(asm string, regs map[string]interface{}) uintptr 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 // Load the globals pointer. The program will load pointers relative to this
// register, so it must be set to the right value on startup. // 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 // 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$ la gp, __global_pointer$
.option pop
// Jump to runtime.main // Jump to runtime.main
call 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. // These are the supported alternate function numberings on the stm32f407
// STM32FXXX (except stm32f1xx) bitfield definitions that are not // +build stm32,stm32f407
// auto-generated by gen-device-svd.go
// +build stm32f4
// Alternate function settings on the stm32f4 series // Alternate function settings on the stm32f4xx series
package stm32 package stm32
+4 -2
View File
@@ -5,14 +5,16 @@ import (
"time" "time"
) )
// This example assumes that the button is connected to pin 8. Change the value
// below to use a different pin.
const ( const (
led = machine.LED led = machine.LED
button = machine.BUTTON button = machine.Pin(8)
) )
func main() { func main() {
led.Configure(machine.PinConfig{Mode: machine.PinOutput}) led.Configure(machine.PinConfig{Mode: machine.PinOutput})
button.Configure(machine.PinConfig{Mode: machine.PinInputPullup}) button.Configure(machine.PinConfig{Mode: machine.PinInput})
for { for {
if button.Get() { 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. // 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. // Change the values below to use different pins.
const ( const (
redPin = machine.D4 redPin = 3
greenPin = machine.D5 greenPin = 5
bluePin = machine.D6 bluePin = 6
) )
// cycleColor is just a placeholder until math/rand or some equivalent is working. // cycleColor is just a placeholder until math/rand or some equivalent is working.
@@ -28,16 +28,13 @@ func main() {
machine.InitPWM() machine.InitPWM()
red := machine.PWM{redPin} red := machine.PWM{redPin}
err := red.Configure() red.Configure()
checkError(err, "failed to configure red pin")
green := machine.PWM{greenPin} green := machine.PWM{greenPin}
err = green.Configure() green.Configure()
checkError(err, "failed to configure green pin")
blue := machine.PWM{bluePin} blue := machine.PWM{bluePin}
err = blue.Configure() blue.Configure()
checkError(err, "failed to configure blue pin")
var rc uint8 var rc uint8
var gc uint8 = 20 var gc uint8 = 20
@@ -55,10 +52,3 @@ func main() {
time.Sleep(time.Millisecond * 500) 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" "machine"
) )
var timerCh = make(chan struct{}, 1)
func main() { func main() {
machine.LED.Configure(machine.PinConfig{Mode: machine.PinOutput}) machine.LED.Configure(machine.PinConfig{Mode: machine.PinOutput})
@@ -14,18 +12,17 @@ func main() {
arm.SetupSystemTimer(machine.CPUFrequency() / 10) arm.SetupSystemTimer(machine.CPUFrequency() / 10)
for { for {
machine.LED.Low()
<-timerCh
machine.LED.High()
<-timerCh
} }
} }
var led_state bool
//export SysTick_Handler //export SysTick_Handler
func timer_isr() { func timer_isr() {
select { if led_state {
case timerCh <- struct{}{}: machine.LED.Low()
default: } else {
// The consumer is running behind. 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 package task
import "runtime/interrupt"
const asserts = false const asserts = false
// Queue is a FIFO container of tasks. // Queue is a FIFO container of tasks.
@@ -12,9 +10,7 @@ type Queue struct {
// Push a task onto the queue. // Push a task onto the queue.
func (q *Queue) Push(t *Task) { func (q *Queue) Push(t *Task) {
i := interrupt.Disable()
if asserts && t.Next != nil { if asserts && t.Next != nil {
interrupt.Restore(i)
panic("runtime: pushing a task to a queue with a non-nil Next pointer") panic("runtime: pushing a task to a queue with a non-nil Next pointer")
} }
if q.tail != nil { if q.tail != nil {
@@ -25,15 +21,12 @@ func (q *Queue) Push(t *Task) {
if q.head == nil { if q.head == nil {
q.head = t q.head = t
} }
interrupt.Restore(i)
} }
// Pop a task off of the queue. // Pop a task off of the queue.
func (q *Queue) Pop() *Task { func (q *Queue) Pop() *Task {
i := interrupt.Disable()
t := q.head t := q.head
if t == nil { if t == nil {
interrupt.Restore(i)
return nil return nil
} }
q.head = t.Next q.head = t.Next
@@ -41,13 +34,11 @@ func (q *Queue) Pop() *Task {
q.tail = nil q.tail = nil
} }
t.Next = nil t.Next = nil
interrupt.Restore(i)
return t return t
} }
// Append pops the contents of another queue and pushes them onto the end of this queue. // Append pops the contents of another queue and pushes them onto the end of this queue.
func (q *Queue) Append(other *Queue) { func (q *Queue) Append(other *Queue) {
i := interrupt.Disable()
if q.head == nil { if q.head == nil {
q.head = other.head q.head = other.head
} else { } else {
@@ -55,15 +46,6 @@ func (q *Queue) Append(other *Queue) {
} }
q.tail = other.tail q.tail = other.tail
other.head, other.tail = nil, nil 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. // Stack is a LIFO container of tasks.
@@ -75,24 +57,19 @@ type Stack struct {
// Push a task onto the stack. // Push a task onto the stack.
func (s *Stack) Push(t *Task) { func (s *Stack) Push(t *Task) {
i := interrupt.Disable()
if asserts && t.Next != nil { if asserts && t.Next != nil {
interrupt.Restore(i)
panic("runtime: pushing a task to a stack with a non-nil Next pointer") panic("runtime: pushing a task to a stack with a non-nil Next pointer")
} }
s.top, t.Next = t, s.top s.top, t.Next = t, s.top
interrupt.Restore(i)
} }
// Pop a task off of the stack. // Pop a task off of the stack.
func (s *Stack) Pop() *Task { func (s *Stack) Pop() *Task {
i := interrupt.Disable()
t := s.top t := s.top
if t != nil { if t != nil {
s.top = t.Next s.top = t.Next
t.Next = nil
} }
interrupt.Restore(i) t.Next = nil
return t return t
} }
@@ -112,13 +89,10 @@ func (t *Task) tail() *Task {
// Queue moves the contents of the stack into a queue. // 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. // Elements can be popped from the queue in the same order that they would be popped from the stack.
func (s *Stack) Queue() Queue { func (s *Stack) Queue() Queue {
i := interrupt.Disable()
head := s.top head := s.top
s.top = nil s.top = nil
q := Queue{ return Queue{
head: head, head: head,
tail: head.tail(), 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 is the underlying running state of the task.
state state 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. // start invokes a function in a new goroutine. Calls to this are inserted by the compiler.
// The created goroutine starts running immediately. // The created goroutine starts running immediately.
// This is implemented inside the compiler. // 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. // Current returns the current active task.
// This is implemented inside the compiler. // This is implemented inside the compiler.
@@ -85,14 +85,9 @@ type taskHolder interface {
getReturnPtr() unsafe.Pointer 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() { func fake() {
// Hack to ensure intrinsics are discovered. // Hack to ensure intrinsics are discovered.
Current() Current()
go func() {}()
Pause() Pause()
} }
+1 -1
View File
@@ -17,7 +17,7 @@ func Current() *Task {
} }
//go:noinline //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. // The compiler will error if this is reachable.
runtimePanic("scheduler is disabled") 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. // 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. // Create a stack.
stack := make([]uintptr, stackSize/unsafe.Sizeof(uintptr(0))) 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. // start creates and starts a new goroutine with the given function and arguments.
// The new goroutine is scheduled to run later. // 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 := &Task{}
t.state.initialize(fn, args, stackSize) t.state.initialize(fn, args)
runqueuePushBack(t) runqueuePushBack(t)
} }
+2
View File
@@ -4,6 +4,8 @@ package task
import "unsafe" import "unsafe"
const stackSize = 256
// calleeSavedRegs is the list of registers that must be saved and restored when // 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 // switching between tasks. Also see scheduler_avr.S that relies on the
// exact layout of this struct. // exact layout of this struct.
+2
View File
@@ -4,6 +4,8 @@ package task
import "unsafe" import "unsafe"
const stackSize = 1024
// calleeSavedRegs is the list of registers that must be saved and restored when // 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 // switching between tasks. Also see scheduler_cortexm.S that relies on the
// exact layout of this struct. // exact layout of this struct.
+9 -27
View File
@@ -7,39 +7,21 @@ func CPUFrequency() uint32 {
return 16000000 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 // LED on the Arduino
const LED Pin = D13 const LED Pin = 13
// ADC on the Arduino // ADC on the Arduino
const ( const (
ADC0 Pin = PC0 ADC0 Pin = 0
ADC1 Pin = PC1 ADC1 Pin = 1
ADC2 Pin = PC2 ADC2 Pin = 2
ADC3 Pin = PC3 ADC3 Pin = 3
ADC4 Pin = PC4 // Used by TWI for SDA ADC4 Pin = 4 // Used by TWI for SDA
ADC5 Pin = PC5 // Used by TWI for SCL ADC5 Pin = 5 // Used by TWI for SCL
) )
// UART pins // UART pins
const ( const (
UART_TX_PIN Pin = PD1 UART_TX_PIN Pin = 1
UART_RX_PIN Pin = PD0 UART_RX_PIN Pin = 0
) )
+1 -1
View File
@@ -49,7 +49,7 @@ const (
// Digital pins // Digital pins
D0 Pin = PE0 D0 Pin = PE0
D1 Pin = PE1 D1 Pin = PE1
D2 Pin = PE4 D2 Pin = PE6
D3 Pin = PE5 D3 Pin = PE5
D4 Pin = PG5 D4 Pin = PG5
D5 Pin = PE3 D5 Pin = PE3
+9 -27
View File
@@ -7,39 +7,21 @@ func CPUFrequency() uint32 {
return 16000000 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 // LED on the Arduino
const LED Pin = D13 const LED Pin = 13
// ADC on the Arduino // ADC on the Arduino
const ( const (
ADC0 Pin = PC0 ADC0 Pin = 0
ADC1 Pin = PC1 ADC1 Pin = 1
ADC2 Pin = PC2 ADC2 Pin = 2
ADC3 Pin = PC3 ADC3 Pin = 3
ADC4 Pin = PC4 // Used by TWI for SDA ADC4 Pin = 4 // Used by TWI for SDA
ADC5 Pin = PC5 // Used by TWI for SCL ADC5 Pin = 5 // Used by TWI for SCL
) )
// UART pins // UART pins
const ( const (
UART_TX_PIN Pin = PD1 UART_TX_PIN Pin = 1
UART_RX_PIN Pin = PD0 UART_RX_PIN Pin = 0
) )
+4 -4
View File
@@ -67,14 +67,14 @@ const (
// SPI pins // SPI pins
const ( const (
SPI0_SCK_PIN Pin = D13 // SCK: SERCOM1/PAD[1] SPI0_SCK_PIN Pin = D13 // SCK: SERCOM1/PAD[1]
SPI0_SDO_PIN Pin = D11 // SDO: SERCOM1/PAD[0] SPI0_MOSI_PIN Pin = D11 // MOSI: SERCOM1/PAD[0]
SPI0_SDI_PIN Pin = D12 // SDI: SERCOM1/PAD[3] SPI0_MISO_PIN Pin = D12 // MISO: SERCOM1/PAD[3]
) )
// NINA-W102 Pins // NINA-W102 Pins
const ( const (
NINA_SDO Pin = PA12 NINA_MOSI Pin = PA12
NINA_SDI Pin = PA13 NINA_MISO Pin = PA13
NINA_CS Pin = PA14 NINA_CS Pin = PA14
NINA_SCK Pin = PA15 NINA_SCK Pin = PA15
NINA_GPIO0 Pin = PA27 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
)
+3 -7
View File
@@ -65,22 +65,18 @@ var (
Buffer: NewRingBuffer(), Buffer: NewRingBuffer(),
Bus: stm32.USART1, Bus: stm32.USART1,
} }
UART1 = UART{ UART1 = &UART0
Buffer: NewRingBuffer(),
Bus: stm32.USART2,
}
) )
func init() { func init() {
UART0.Interrupt = interrupt.New(stm32.IRQ_USART1, UART0.handleInterrupt) UART0.Interrupt = interrupt.New(stm32.IRQ_USART1, UART0.handleInterrupt)
UART1.Interrupt = interrupt.New(stm32.IRQ_USART2, UART1.handleInterrupt)
} }
// SPI pins // SPI pins
const ( const (
SPI0_SCK_PIN = PA5 SPI0_SCK_PIN = PA5
SPI0_SDO_PIN = PA7 SPI0_MOSI_PIN = PA7
SPI0_SDI_PIN = PA6 SPI0_MISO_PIN = PA6
) )
// I2C pins // I2C pins
+3 -3
View File
@@ -2,7 +2,7 @@
package machine package machine
const HasLowFrequencyCrystal = false const HasLowFrequencyCrystal = true
// GPIO Pins // GPIO Pins
const ( const (
@@ -73,8 +73,8 @@ const (
// SPI pins (internal flash) // SPI pins (internal flash)
const ( const (
SPI0_SCK_PIN = P0_19 // SCK SPI0_SCK_PIN = P0_19 // SCK
SPI0_SDO_PIN = P0_21 // SDO SPI0_MOSI_PIN = P0_21 // MOSI
SPI0_SDI_PIN = P0_23 // SDI SPI0_MISO_PIN = P0_23 // MISO
) )
// USB CDC identifiers // USB CDC identifiers

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