Compare commits

..

1 Commits

Author SHA1 Message Date
sago35 13c0714fda test: remove thread-safe wrapper 2021-04-06 21:46:13 +09:00
675 changed files with 7358 additions and 33301 deletions
+58 -58
View File
@@ -17,15 +17,18 @@ commands:
echo 'deb https://apt.llvm.org/buster/ llvm-toolchain-buster-<<parameters.llvm>> main' | sudo tee /etc/apt/sources.list.d/llvm.list echo 'deb https://apt.llvm.org/buster/ llvm-toolchain-buster-<<parameters.llvm>> main' | sudo tee /etc/apt/sources.list.d/llvm.list
wget -O - https://apt.llvm.org/llvm-snapshot.gpg.key|sudo apt-key add - wget -O - https://apt.llvm.org/llvm-snapshot.gpg.key|sudo apt-key add -
sudo apt-get update sudo apt-get update
sudo apt-get install --no-install-recommends \ sudo apt-get install \
llvm-<<parameters.llvm>>-dev \ llvm-<<parameters.llvm>>-dev \
clang-<<parameters.llvm>> \ clang-<<parameters.llvm>> \
libclang-<<parameters.llvm>>-dev \ libclang-<<parameters.llvm>>-dev \
lld-<<parameters.llvm>> \ lld-<<parameters.llvm>> \
gcc-arm-linux-gnueabihf \
gcc-aarch64-linux-gnu \
qemu-system-arm \ qemu-system-arm \
qemu-user \ qemu-user \
gcc-avr \ gcc-avr \
avr-libc avr-libc
sudo apt-get install --no-install-recommends libc6-dev-i386 lib32gcc-8-dev
install-node: install-node:
steps: steps:
- run: - run:
@@ -65,17 +68,14 @@ commands:
steps: steps:
- restore_cache: - restore_cache:
keys: keys:
- llvm-source-11-v2 - llvm-source-11-v1
- 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-11-v2 key: llvm-source-11-v1
paths: paths:
- llvm-project/clang/lib/Headers - llvm-project
- llvm-project/clang/include
- llvm-project/lld/include
- llvm-project/llvm/include
build-wasi-libc: build-wasi-libc:
steps: steps:
- restore_cache: - restore_cache:
@@ -114,10 +114,7 @@ commands:
key: wasi-libc-sysroot-systemclang-v3 key: wasi-libc-sysroot-systemclang-v3
paths: paths:
- lib/wasi-libc/sysroot - lib/wasi-libc/sysroot
- run: - run: go test -v -tags=llvm<<parameters.llvm>> ./cgo ./compileopts ./compiler ./interp ./transform .
name: "Test TinyGo"
command: go test -v -timeout=20m -tags=llvm<<parameters.llvm>> ./builder ./cgo ./compileopts ./compiler ./interp ./transform .
no_output_timeout: 20m
- run: make gen-device -j4 - run: make gen-device -j4
- run: make smoketest XTENSA=0 - run: make smoketest XTENSA=0
- run: make tinygo-test - run: make tinygo-test
@@ -136,12 +133,16 @@ commands:
name: "Install apt dependencies" name: "Install apt dependencies"
command: | command: |
sudo apt-get update sudo apt-get update
sudo apt-get install --no-install-recommends \ sudo apt-get install \
libgnutls30 libssl1.0.2 \ gcc-arm-linux-gnueabihf \
libc6-dev-armel-cross \
gcc-aarch64-linux-gnu \
libc6-dev-arm64-cross \
qemu-system-arm \ qemu-system-arm \
qemu-user \ qemu-user \
gcc-avr \ gcc-avr \
avr-libc avr-libc
sudo apt-get install --no-install-recommends libc6-dev-i386 lib32gcc-6-dev
- install-node - install-node
- install-wasmtime - install-wasmtime
- install-xtensa-toolchain: - install-xtensa-toolchain:
@@ -153,15 +154,12 @@ commands:
- llvm-source-linux - llvm-source-linux
- restore_cache: - restore_cache:
keys: keys:
- llvm-build-11-linux-v4-assert - llvm-build-11-linux-v2-assert
- run: - run:
name: "Build LLVM" name: "Build LLVM"
command: | command: |
if [ ! -f llvm-build/lib/liblldELF.a ] if [ ! -f llvm-build/lib/liblldELF.a ]
then then
# fetch LLVM source
rm -rf llvm-project
make llvm-source
# install dependencies # install dependencies
sudo apt-get install cmake ninja-build sudo apt-get install cmake ninja-build
# hack ninja to use less jobs # hack ninja to use less jobs
@@ -169,23 +167,20 @@ commands:
chmod +x /go/bin/ninja chmod +x /go/bin/ninja
# build! # build!
make ASSERT=1 llvm-build make ASSERT=1 llvm-build
find llvm-build -name CMakeFiles -prune -exec rm -r '{}' \;
fi fi
- save_cache: - save_cache:
key: llvm-build-11-linux-v4-assert key: llvm-build-11-linux-v2-assert
paths: paths:
llvm-build llvm-build
- run: make ASSERT=1 - run: |
# Note: -p=2 limits parallelism to two jobs at a time, which is
# necessary to keep memory consumption down and avoid OOM (for a
# 2CPU/4GB executor).
GOFLAGS="-p=2" make ASSERT=1
- build-wasi-libc - build-wasi-libc
- run: - run:
name: "Test TinyGo" name: "Test TinyGo"
command: make ASSERT=1 test command: make ASSERT=1 test
no_output_timeout: 20m
environment:
# Note: -p=2 limits parallelism to two jobs at a time, which is
# necessary to keep memory consumption down and avoid OOM (for a
# 2CPU/4GB executor).
GOFLAGS: -p=2
- 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:
@@ -201,12 +196,16 @@ commands:
name: "Install apt dependencies" name: "Install apt dependencies"
command: | command: |
sudo apt-get update sudo apt-get update
sudo apt-get install --no-install-recommends \ sudo apt-get install \
libgnutls30 libssl1.0.2 \ gcc-arm-linux-gnueabihf \
libc6-dev-armel-cross \
gcc-aarch64-linux-gnu \
libc6-dev-arm64-cross \
qemu-system-arm \ qemu-system-arm \
qemu-user \ qemu-user \
gcc-avr \ gcc-avr \
avr-libc avr-libc
sudo apt-get install --no-install-recommends libc6-dev-i386 lib32gcc-6-dev
- install-node - install-node
- install-wasmtime - install-wasmtime
- install-xtensa-toolchain: - install-xtensa-toolchain:
@@ -218,15 +217,12 @@ commands:
- llvm-source-linux - llvm-source-linux
- restore_cache: - restore_cache:
keys: keys:
- llvm-build-11-linux-v4-noassert - llvm-build-11-linux-v2-noassert
- run: - run:
name: "Build LLVM" name: "Build LLVM"
command: | command: |
if [ ! -f llvm-build/lib/liblldELF.a ] if [ ! -f llvm-build/lib/liblldELF.a ]
then then
# fetch LLVM source
rm -rf llvm-project
make llvm-source
# install dependencies # install dependencies
sudo apt-get install cmake ninja-build sudo apt-get install cmake ninja-build
# hack ninja to use less jobs # hack ninja to use less jobs
@@ -234,17 +230,15 @@ commands:
chmod +x /go/bin/ninja chmod +x /go/bin/ninja
# build! # build!
make llvm-build make llvm-build
find llvm-build -name CMakeFiles -prune -exec rm -r '{}' \;
fi fi
- save_cache: - save_cache:
key: llvm-build-11-linux-v4-noassert key: llvm-build-11-linux-v2-noassert
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
no_output_timeout: 20m
- run: - run:
name: "Install fpm" name: "Install fpm"
command: | command: |
@@ -280,64 +274,56 @@ commands:
- run: - run:
name: "Install dependencies" name: "Install dependencies"
command: | command: |
curl https://dl.google.com/go/go1.17.darwin-amd64.tar.gz -o go1.17.darwin-amd64.tar.gz curl https://dl.google.com/go/go1.16.darwin-amd64.tar.gz -o go1.16.darwin-amd64.tar.gz
sudo tar -C /usr/local -xzf go1.17.darwin-amd64.tar.gz sudo tar -C /usr/local -xzf go1.16.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: - install-xtensa-toolchain:
variant: "macos" variant: "macos"
- restore_cache: - restore_cache:
keys: keys:
- go-cache-macos-v3-{{ checksum "go.mod" }}-{{ .Environment.CIRCLE_PREVIOUS_BUILD_NUM }} - go-cache-macos-v2-{{ checksum "go.mod" }}-{{ .Environment.CIRCLE_PREVIOUS_BUILD_NUM }}
- go-cache-macos-v3-{{ checksum "go.mod" }} - go-cache-macos-v2-{{ checksum "go.mod" }}
- restore_cache: - restore_cache:
keys: keys:
- llvm-source-11-macos-v3 - llvm-source-11-macos-v1
- 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-11-macos-v3 key: llvm-source-11-macos-v1
paths: paths:
- llvm-project/clang/lib/Headers - llvm-project
- llvm-project/clang/include
- llvm-project/lld/include
- llvm-project/llvm/include
- restore_cache: - restore_cache:
keys: keys:
- llvm-build-11-macos-v5 - llvm-build-11-macos-v2
- run: - run:
name: "Build LLVM" name: "Build LLVM"
command: | command: |
if [ ! -f llvm-build/lib/liblldELF.a ] if [ ! -f llvm-build/lib/liblldELF.a ]
then then
# fetch LLVM source
rm -rf llvm-project
make llvm-source
# install dependencies # install dependencies
HOMEBREW_NO_AUTO_UPDATE=1 brew install cmake ninja HOMEBREW_NO_AUTO_UPDATE=1 brew install cmake ninja
# build! # build!
make llvm-build make llvm-build
find llvm-build -name CMakeFiles -prune -exec rm -r '{}' \;
fi fi
- save_cache: - save_cache:
key: llvm-build-11-macos-v5 key: llvm-build-11-macos-v2
paths: paths:
llvm-build llvm-build
- restore_cache: - restore_cache:
keys: keys:
- wasi-libc-sysroot-macos-v4 - wasi-libc-sysroot-macos-v3
- 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-v4 key: wasi-libc-sysroot-macos-v3
paths: paths:
- lib/wasi-libc/sysroot - lib/wasi-libc/sysroot
- run: - run:
name: "Test TinyGo" name: "Test TinyGo"
command: make test command: make test
no_output_timeout: 20m
- run: - run:
name: "Build TinyGo release" name: "Build TinyGo release"
command: | command: |
@@ -354,12 +340,24 @@ commands:
tinygo version tinygo version
- run: make smoketest AVR=0 - run: make smoketest AVR=0
- save_cache: - save_cache:
key: go-cache-macos-v3-{{ 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
- /go/pkg/mod - /go/pkg/mod
jobs: jobs:
test-llvm10-go113:
docker:
- image: circleci/golang:1.13-buster
steps:
- test-linux:
llvm: "10"
test-llvm10-go114:
docker:
- image: circleci/golang:1.14-buster
steps:
- test-linux:
llvm: "10"
test-llvm11-go115: test-llvm11-go115:
docker: docker:
- image: circleci/golang:1.15-buster - image: circleci/golang:1.15-buster
@@ -374,17 +372,17 @@ jobs:
llvm: "11" llvm: "11"
assert-test-linux: assert-test-linux:
docker: docker:
- image: circleci/golang:1.17-stretch - image: circleci/golang:1.14-stretch
steps: steps:
- assert-test-linux - assert-test-linux
build-linux: build-linux:
docker: docker:
- image: circleci/golang:1.17-stretch - image: circleci/golang:1.14-stretch
steps: steps:
- build-linux - build-linux
build-macos: build-macos:
macos: macos:
xcode: "11.1.0" # macOS 10.14 xcode: "10.1.0"
steps: steps:
- build-macos - build-macos
@@ -393,6 +391,8 @@ jobs:
workflows: workflows:
test-all: test-all:
jobs: jobs:
- test-llvm10-go113
- test-llvm10-go114
- test-llvm11-go115 - test-llvm11-go115
- test-llvm11-go116 - test-llvm11-go116
- build-linux - build-linux
-3
View File
@@ -1,3 +0,0 @@
build/
llvm-*/
@@ -1,45 +0,0 @@
name: CI for tinygo-dev docker container
on:
push:
branches: [ dev, fix-docker-llvm-build ]
jobs:
push_to_registry:
name: Push Docker image to GHCR/Docker Hub
runs-on: ubuntu-latest
permissions:
packages: write
contents: read
steps:
- name: Check out the repo
uses: actions/checkout@v2
with:
submodules: recursive
- name: Docker meta
id: meta
uses: docker/metadata-action@v3
with:
images: |
tinygo/tinygo-dev
ghcr.io/${{ github.repository }}/tinygo-dev
tags: |
type=sha,format=long
type=raw,value=latest
- name: Log in to Docker Hub
uses: docker/login-action@v1
with:
username: ${{ secrets.DOCKER_HUB_USERNAME }}
password: ${{ secrets.DOCKER_HUB_ACCESS_TOKEN }}
- name: Log in to Github Container Registry
uses: docker/login-action@v1
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Build and push
uses: docker/build-push-action@v2
with:
context: .
push: true
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
-85
View File
@@ -1,85 +0,0 @@
name: Windows
on: push
jobs:
build-windows:
runs-on: windows-2016
steps:
- name: Install Go
uses: actions/setup-go@v2
with:
go-version: '1.17'
- name: Install QEMU
shell: bash
run: |
choco install qemu --version=2020.06.12
echo "C:\Program Files\QEMU" >> $GITHUB_PATH
- name: Checkout
uses: actions/checkout@v2
with:
submodules: true
- name: Cache LLVM source
uses: actions/cache@v2
id: cache-llvm-source
with:
key: llvm-source-11-windows-v1
path: |
llvm-project/clang/lib/Headers
llvm-project/clang/include
llvm-project/lld/include
llvm-project/llvm/include
- name: Download LLVM source
if: steps.cache-llvm-source.outputs.cache-hit != 'true'
run: make llvm-source
- name: Cache LLVM build
uses: actions/cache@v2
id: cache-llvm-build
with:
key: llvm-build-11-windows-v2
path: llvm-build
- name: Build LLVM
if: steps.cache-llvm-build.outputs.cache-hit != 'true'
shell: bash
run: |
# fetch LLVM source
rm -rf llvm-project
make llvm-source
# install dependencies
choco install ninja
# build!
make llvm-build
# Remove unnecessary object files (to reduce cache size).
find llvm-build -name CMakeFiles -prune -exec rm -r '{}' \;
- name: Cache wasi-libc sysroot
uses: actions/cache@v2
id: cache-wasi-libc
with:
key: wasi-libc-sysroot-v1
path: lib/wasi-libc/sysroot
- name: Build wasi-libc
if: steps.cache-wasi-libc.outputs.cache-hit != 'true'
run: make wasi-libc
- name: Test TinyGo
shell: bash
run: make test
- name: Build TinyGo release tarball
run: make build/release -j4
- name: Make release artifact
shell: bash
working-directory: build/release
run: 7z -tzip a release.zip tinygo
- name: Publish release artifact
# Note: this release artifact is double-zipped, see:
# https://github.com/actions/upload-artifact/issues/39
# We can essentially pick one of these:
# - have a dobule-zipped artifact when downloaded from the UI
# - have a very slow artifact upload
# We're doing the former here, to keep artifact uploads fast.
uses: actions/upload-artifact@v2
with:
name: release-double-zipped
path: build/release/release.zip
- name: Smoke tests
shell: bash
run: make smoketest TINYGO=build/tinygo AVR=0 XTENSA=0
-2
View File
@@ -16,8 +16,6 @@ src/device/stm32/*.go
src/device/stm32/*.s src/device/stm32/*.s
src/device/kendryte/*.go src/device/kendryte/*.go
src/device/kendryte/*.s src/device/kendryte/*.s
src/device/rp/*.go
src/device/rp/*.s
vendor vendor
llvm-build llvm-build
llvm-project llvm-project
-3
View File
@@ -23,6 +23,3 @@
[submodule "lib/stm32-svd"] [submodule "lib/stm32-svd"]
path = lib/stm32-svd path = lib/stm32-svd
url = https://github.com/tinygo-org/stm32-svd url = https://github.com/tinygo-org/stm32-svd
[submodule "lib/musl"]
path = lib/musl
url = git://git.musl-libc.org/musl
+1 -1
View File
@@ -23,7 +23,7 @@ different guide:
LLVM, Clang and LLD are quite light on dependencies, requiring only standard LLVM, Clang and LLD are quite light on dependencies, requiring only standard
build tools to be built. Go is of course necessary to build TinyGo itself. build tools to be built. Go is of course necessary to build TinyGo itself.
* Go (1.15+) * Go (1.13+)
* Standard build tools (gcc/clang) * Standard build tools (gcc/clang)
* git * git
* CMake * CMake
-258
View File
@@ -1,261 +1,3 @@
0.20.0
---
* **command line**
- add support for Go 1.17
- improve Go version detection
- add support for the Black Magic Probe (BMP)
- add a flag for creating cpu profiles
* **compiler**
- `builder:` list libraries at the end of the linker command
- `builder:` strip debug information at link time instead of at compile time
- `builder:` add missing error check for `ioutil.TempFile()`
- `builder:` simplify running of jobs
- `compiler:` move LLVM math builtin support into the compiler
- `compiler:` move math aliases from the runtime to the compiler
- `compiler:` add aliases for many hashing packages
- `compiler:` add `*ssa.MakeSlice` bounds tests
- `compiler:` fix max possible slice
- `compiler:` add support for new language features of Go 1.17
- `compiler:` fix equally named structs in different scopes
- `compiler:` avoid zero-sized alloca in channel operations
- `interp:` don't ignore array indices for untyped objects
- `interp:` keep reverted package initializers in order
- `interp:` fix bug in compiler-time/run-time package initializers
- `loader:` fix panic in CGo files with syntax errors
- `transform:` improve GC stack slot pass to work around a bug
* **standard library**
- `crypto/rand`: switch to `arc4random_buf`
- `math:` fix `math.Max` and `math.Min`
- `math/big`: fix undefined symbols error
- `net:` add MAC address implementation
- `os:` implement `os.Executable`
- `os:` add `SEEK_SET`, `SEEK_CUR`, and `SEEK_END`
- `reflect:` add StructField.IsExported method
- `runtime:` reset heapptr to heapStart after preinit()
- `runtime:` add `subsections_via_symbols` to assembly files on darwin
- `testing:` add subset implementation of Benchmark
- `testing:` test testing package using `tinygo test`
- `testing:` add support for the `-test.v` flag
* **targets**
- `386:` bump minimum requirement to the Pentium 4
- `arm:` switch to Thumb instruction set on ARM
- `atsamd:` fix copy-paste error for atsamd21/51 calibTrim block
- `baremetal`,`wasm`: support command line params and environment variables
- `cortexm:` fix stack overflow because of unaligned stacks
- `esp32c3:` add support for the ESP32-C3 from Espressif
- `nrf52840:` fix ram size
- `nxpmk66f18:` fix a suspicious bitwise operation
- `rp2040:` add SPI support
- `rp2040:` add I2C support
- `rp2040:` add PWM implementation
- `rp2040:` add openocd configuration
- `stm32:` add support for PortMask* functions for WS2812 support
- `unix:` fix time base for time.Now()
- `unix:` check for mmap error and act accordingly
- `wasm:` override dlmalloc heap implementation from wasi-libc
- `wasm:` align heap to 16 bytes
- `wasm:` add support for the crypto/rand package
* **boards**
- add `DefaultUART` to adafruit boards
- `arduino-mkrwifi1010:` add board definition for Arduino MKR WiFi 1010
- `arduino-mkrwifi1010:` fix pin definition of `NINA_RESETN`
- `feather-nrf52:` fix pin definition of uart
- `feather-rp2040:` add pin name definition
- `gameboy-advance:` fix ROM header
- `mdbt50qrx-uf2:` add Raytac MDBT50Q-RX Dongle with TinyUF2
- `nano-rp2040:` define `NINA_SPI` and fix wifinina pins
- `teensy40:` enable hardware UART reconfiguration, fix receive watermark interrupt
0.19.0
---
* **command line**
- don't consider compile-only tests as failing
- add -test flag for `tinygo list`
- escape commands while printing them with the -x flag
- make flash-command portable and safer to use
- use `extended-remote` instead of `remote` in GDB
- detect specific serial port IDs based on USB vid/pid
- add a flag to the command line to select the serial implementation
* **compiler**
- `cgo`: improve constant parser
- `compiler`: support chained interrupt handlers
- `compiler`: add support for running a builtin in a goroutine
- `compiler`: do not emit nil checks for loading closure variables
- `compiler`: skip context parameter when starting regular goroutine
- `compiler`: refactor method names
- `compiler`: add function and global section pragmas
- `compiler`: implement `syscall.rawSyscallNoError` in inline assembly
- `interp`: ignore inline assembly in markExternal
- `interp`: fix a bug in pointer cast workaround
- `loader`: fix testing a main package
* **standard library**
- `crypto/rand`: replace this package with a TinyGo version
- `machine`: make USBCDC global a pointer
- `machine`: make UART objects pointer receivers
- `machine`: define Serial as the default output
- `net`: add initial support for net.IP
- `net`: add more net compatibility
- `os`: add stub for os.ReadDir
- `os`: add FileMode constants from Go 1.16
- `os`: add stubs required for net/http
- `os`: implement process related functions
- `reflect`: implement AppendSlice
- `reflect`: add stubs required for net/http
- `runtime`: make task.Data a 64-bit integer to avoid overflow
- `runtime`: expose memory stats
- `sync`: implement NewCond
- `syscall`: fix int type in libc version
* **targets**
- `cortexm`: do not disable interrupts on abort
- `cortexm`: bump default stack size to 2048 bytes
- `nrf`: avoid heap allocation in waitForEvent
- `nrf`: don't trigger a heap allocation in SPI.Transfer
- `nrf52840`: add support for flashing with the BOSSA tool
- `rp2040`: add support for GPIO input
- `rp2040`: add basic support for ADC
- `rp2040`: gpio and adc pin definitions
- `rp2040`: implement UART
- `rp2040`: patch elf to checksum 2nd stage boot
- `stm32`: add PWM for most chips
- `stm32`: add support for pin interrupts
- `stm32f103`: add support for PinInputPullup / PinInputPulldown
- `wasi`: remove wasm build tag
* **boards**
- `feather-rp2040`: add support for this board
- `feather-nrf52840-sense`: add board definition for this board
- `pca10059`: support flashing from Windows
- `nano-rp2040`: add this board
- `nano-33-ble`: add support for this board
- `pico`: add the Raspberry Pi Pico board with the new RP2040 chip
- `qtpy`: add pin for neopixels
- all: add definition for ws2812 for supported boards
0.18.0
---
* **command line**
- drop support for Go 1.11 and 1.12
- throw an error when no target is specified on Windows
- improve error messages in `getDefaultPort()`, support for multiple ports
- remove `-cflags` and `-ldflags` flags
- implement `-ldflags="-X ..."`
- add `-print-allocs` flag that lets you print all heap allocations
- openocd commands in tinygo command line
- add `-llvm-features` parameter
- match `go test` output
- discover USB ports only, this will ignore f.ex. bluetooth
- use physicmal path instead of cached GOROOT in function getGoroot
- add goroot for snap installs
* **compiler**
- `builder`: add support for `-opt=0`
- `builder`, `compiler`: compile and cache packages in parallel
- `builder`: run interp per package
- `builder`: cache C and assembly file outputs
- `builder`: add support for `-x` flag to print commands
- `builder`: add optsize attribute while building the package
- `builder`: run function passes per package
- `builder`: hard code Clang compiler
- `compiler`: do not use `llvm.GlobalContext()`
- `compiler`: remove SimpleDCE pass
- `compiler`: do not emit nil checks for `*ssa.Alloc` instructions
- `compiler`: merge `runtime.typecodeID` and runtime.typeInInterface
- `compiler`: do not check for impossible type asserts
- `compiler`: fix use of global context: `llvm.Int32Type()`
- `compiler`: add interface IR test
- `compiler`: fix lack of method name in interface matching
- `compiler`: fix "fragment covers entire variable" bug
- `compiler`: optimize string literals and globals
- `compiler`: decouple func lowering from interface type codes
- `compiler`: add function attributes to some runtime calls
- `compiler`: improve position information in error messages
- `cgo`: add support for CFLAGS in .c files
- `interp`: support GEP on fixed (MMIO) addresses
- `interp`: handle `(reflect.Type).Elem()`
- `interp`: add support for runtime.interfaceMethod
- `interp`: make toLLVMValue return an error instead of panicking
- `interp`: add support for switch statement
- `interp`: fix phi instruction
- `interp`: remove map support
- `interp`: support extractvalue/insertvalue with multiple operands
- `transform`: optimize string comparisons against ""
- `transform`: optimize `reflect.Type` `Implements()` method
- `transform`: fix bug in interface lowering when signatures are renamed
- `transform`: don't rely on struct name of `runtime.typecodeID`
- `transform`: use IPSCCP pass instead of the constant propagation pass
- `transform`: fix func lowering assertion failure
- `transform`: do not lower zero-sized alloc to alloca
- `transform`: split interface and reflect lowering
* **standard library**
- `runtime`: add dummy debug package
- `machine`: fix data shift/mask in newUSBSetup
- `machine`: make `machine.I2C0` and similar objects pointers
- `machine`: unify usbcdc code
- `machine`: refactor PWM support
- `machine`: avoid heap allocations in USB code
- `reflect`: let `reflect.Type` be of interface type
- `reflect`: implement a number of stub functions
- `reflect`: check for access in the `Interface` method call
- `reflect`: fix `AssignableTo` and `Implements` methods
- `reflect`: implement `Value.CanAddr`
- `reflect`: implement `Sizeof` and `Alignof` for func values
- `reflect`: implement `New` function
- `runtime`: implement command line arguments in hosted environments
- `runtime`: implement environment variables for Linux
- `runtime`: improve timers on nrf, and samd chips
* **targets**
- all: use -Qunused-arguments only for assembly files
- `atmega1280`: add PWM support
- `attiny`: remove dummy UART
- `atsamd21`: improve SPI
- `atsamd51`: fix PWM support in atsamd51p20
- `atsamd5x`: improve SPI
- `atsamd51`, `atsame5x`: unify samd51 and same5x
- `atsamd51`, `atsamd21`: fix `ADC.Get()` value at 8bit and 10bit
- `atsame5x`: add support for CAN
- `avr`: remove I2C stubs from attiny support
- `cortexm`: check for `arm-none-eabi-gdb` and `gdb-multiarch` commands
- `cortexm`: add `__isr_vector` symbol
- `cortexm`: disable FPU on Cortex-M4
- `cortexm`: clean up Cortex-M target files
- `fe310`: fix SPI read
- `gameboy-advance`: Fix RGBA color interpretation
- `nrf52833`: add PWM support
- `stm32l0`: use unified UART logic
- `stm32`: move f103 (bluepill) to common i2c code
- `stm32`: separate altfunc selection for UART Tx/Rx
- `stm32`: i2c implementation for F7, L5 and L4 MCUs
- `stm32`: make SPI CLK fast to fix data issue
- `stm32`: support SPI on L4 series
- `unix`: avoid possible heap allocation with `-opt=0`
- `unix`: use conservative GC by default
- `unix`: use the tasks scheduler instead of coroutines
- `wasi`: upgrade WASI version to wasi_snapshot_preview1
- `wasi`: darwin: support basic file io based on libc
- `wasm`: only export explicitly exported functions
- `wasm`: use WASI ABI for exit function
- `wasm`: scan globals conservatively
* **boards**
- `arduino-mega1280`: add support for the Arduino Mega 1280
- `arduino-nano-new`: Add Arduino Nano w/ New Bootloader target
- `atsame54-xpro`: add initial support this board
- `feather-m4-can`: add initial support for this board
- `grandcentral-m4`: add board support for Adafruit Grand Central M4 (SAMD51)
- `lgt92`: update to new UART structure
- `microbit`: remove LED constant
- `microbit-v2`: add support for S113 SoftDevice
- `nucleol432`: add support for this board
- `nucleo-l031k6`: add this board
- `pca10059`: initial support for this board
- `qtpy`: fix msd-volume-name
- `qtpy`: fix i2c setting
- `teensy40`: move txBuffer allocation to UART declaration
- `teensy40`: add UART0 as alias for UART1
0.17.0 0.17.0
--- ---
+4 -4
View File
@@ -1,8 +1,8 @@
# TinyGo base stage installs the most recent Go 1.17.x, LLVM 11 and the TinyGo compiler itself. # TinyGo base stage installs the most recent Go 1.15.x, LLVM 11 and the TinyGo compiler itself.
FROM golang:1.17 AS tinygo-base FROM golang:1.15 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/bullseye/ llvm-toolchain-bullseye-11 main" >> /etc/apt/sources.list && \ echo "deb http://apt.llvm.org/buster/ llvm-toolchain-buster-11 main" >> /etc/apt/sources.list && \
apt-get update && \ apt-get update && \
apt-get install -y llvm-11-dev libclang-11-dev lld-11 git apt-get install -y llvm-11-dev libclang-11-dev lld-11 git
@@ -15,7 +15,7 @@ RUN cd /tinygo/ && \
git submodule sync && \ git submodule sync && \
git submodule update --init --recursive --force git submodule update --init --recursive --force
COPY ./lib/picolibc-stdio.c /tinygo/lib/picolibc-stdio.c COPY ./lib/picolibc-include/* /tinygo/lib/picolibc-include/
RUN cd /tinygo/ && \ RUN cd /tinygo/ && \
go install /tinygo/ go install /tinygo/
+51 -142
View File
@@ -10,9 +10,9 @@ LLD_SRC ?= $(LLVM_PROJECTDIR)/lld
# Try to autodetect LLVM build tools. # Try to autodetect LLVM build tools.
detect = $(shell command -v $(1) 2> /dev/null && echo $(1)) detect = $(shell command -v $(1) 2> /dev/null && echo $(1))
CLANG ?= $(word 1,$(abspath $(call detect,llvm-build/bin/clang))$(call detect,clang-11)$(call detect,clang)) CLANG ?= $(word 1,$(abspath $(call detect,llvm-build/bin/clang))$(call detect,clang-11)$(call detect,clang-10)$(call detect,clang))
LLVM_AR ?= $(word 1,$(abspath $(call detect,llvm-build/bin/llvm-ar))$(call detect,llvm-ar-11)$(call detect,llvm-ar)) LLVM_AR ?= $(word 1,$(abspath $(call detect,llvm-build/bin/llvm-ar))$(call detect,llvm-ar-11)$(call detect,llvm-ar-10)$(call detect,llvm-ar))
LLVM_NM ?= $(word 1,$(abspath $(call detect,llvm-build/bin/llvm-nm))$(call detect,llvm-nm-11)$(call detect,llvm-nm)) LLVM_NM ?= $(word 1,$(abspath $(call detect,llvm-build/bin/llvm-nm))$(call detect,llvm-nm-11)$(call detect,llvm-nm-10)$(call detect,llvm-nm))
# Go binary and GOROOT to select # Go binary and GOROOT to select
GO ?= go GO ?= go
@@ -36,7 +36,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 gen-device-rp .PHONY: all tinygo test $(LLVM_BUILDDIR) llvm-source clean fmt gen-device gen-device-nrf gen-device-nxp 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
@@ -71,7 +71,7 @@ else
endif endif
# Libraries that should be linked in for the statically linked Clang. # Libraries that should be linked in for the statically linked Clang.
CLANG_LIB_NAMES = clangAnalysis clangAST clangASTMatchers clangBasic clangCodeGen clangCrossTU clangDriver clangDynamicASTMatchers clangEdit clangFormat clangFrontend clangFrontendTool clangHandleCXX clangHandleLLVM clangIndex clangLex clangParse clangRewrite clangRewriteFrontend clangSema clangSerialization clangTooling clangToolingASTDiff clangToolingCore clangToolingInclusions CLANG_LIB_NAMES = clangAnalysis clangARCMigrate clangAST clangASTMatchers clangBasic clangCodeGen clangCrossTU clangDriver clangDynamicASTMatchers clangEdit clangFormat clangFrontend clangFrontendTool clangHandleCXX clangHandleLLVM clangIndex clangLex clangParse clangRewrite clangRewriteFrontend clangSema clangSerialization clangStaticAnalyzerCheckers clangStaticAnalyzerCore clangStaticAnalyzerFrontend clangTooling clangToolingASTDiff clangToolingCore clangToolingInclusions
CLANG_LIBS = $(START_GROUP) $(addprefix -l,$(CLANG_LIB_NAMES)) $(END_GROUP) -lstdc++ CLANG_LIBS = $(START_GROUP) $(addprefix -l,$(CLANG_LIB_NAMES)) $(END_GROUP) -lstdc++
# Libraries that should be linked in for the statically linked LLD. # Libraries that should be linked in for the statically linked LLD.
@@ -100,14 +100,14 @@ endif
clean: clean:
@rm -rf build @rm -rf build
FMT_PATHS = ./*.go builder cgo compiler interp loader src/device/arm src/examples src/machine src/os src/reflect src/runtime src/sync src/syscall src/testing src/internal/reflectlite transform FMT_PATHS = ./*.go builder cgo compiler 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-kendryte gen-device-nxp gen-device-rp gen-device: gen-device-avr gen-device-esp gen-device-nrf gen-device-sam gen-device-sifive gen-device-kendryte gen-device-nxp
ifneq ($(STM32), 0) ifneq ($(STM32), 0)
gen-device: gen-device-stm32 gen-device: gen-device-stm32
endif endif
@@ -124,7 +124,6 @@ build/gen-device-svd: ./tools/gen-device-svd/*.go
gen-device-esp: build/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/ ./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/
./build/gen-device-svd -source=https://github.com/posborne/cmsis-svd/tree/master/data/Espressif -interrupts=software lib/cmsis-svd/data/Espressif/ src/device/esp/
GO111MODULE=off $(GO) fmt ./src/device/esp GO111MODULE=off $(GO) fmt ./src/device/esp
gen-device-nrf: build/gen-device-svd gen-device-nrf: build/gen-device-svd
@@ -151,19 +150,16 @@ gen-device-stm32: build/gen-device-svd
./build/gen-device-svd -source=https://github.com/tinygo-org/stm32-svd lib/stm32-svd/svd src/device/stm32/ ./build/gen-device-svd -source=https://github.com/tinygo-org/stm32-svd lib/stm32-svd/svd src/device/stm32/
GO111MODULE=off $(GO) fmt ./src/device/stm32 GO111MODULE=off $(GO) fmt ./src/device/stm32
gen-device-rp: build/gen-device-svd
./build/gen-device-svd -source=https://github.com/posborne/cmsis-svd/tree/master/data/RaspberryPi lib/cmsis-svd/data/RaspberryPi/ src/device/rp/
GO111MODULE=off $(GO) fmt ./src/device/rp
# Get LLVM sources. # Get LLVM sources.
$(LLVM_PROJECTDIR)/llvm: $(LLVM_PROJECTDIR)/README.md:
git clone -b xtensa_release_11.0.0 --depth=1 https://github.com/tinygo-org/llvm-project $(LLVM_PROJECTDIR) git clone -b xtensa_release_11.0.0 --depth=1 https://github.com/tinygo-org/llvm-project $(LLVM_PROJECTDIR)
llvm-source: $(LLVM_PROJECTDIR)/llvm 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_LIBXML2=OFF -DLLVM_ENABLE_PROJECTS="clang;lld" -DLLVM_TOOL_CLANG_TOOLS_EXTRA_BUILD=OFF -DCLANG_ENABLE_STATIC_ANALYZER=OFF -DCLANG_ENABLE_ARCMT=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;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_LIBXML2=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
@@ -175,7 +171,7 @@ $(LLVM_BUILDDIR): $(LLVM_BUILDDIR)/build.ninja
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 @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_CFLAGS="-O2 -g -DNDEBUG" 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.
@@ -184,44 +180,27 @@ tinygo:
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 -buildmode exe -o build/tinygo$(EXE) -tags byollvm -ldflags="-X main.gitSha1=`git rev-parse --short HEAD`" .
test: wasi-libc test: wasi-libc
CGO_CPPFLAGS="$(CGO_CPPFLAGS)" CGO_CXXFLAGS="$(CGO_CXXFLAGS)" CGO_LDFLAGS="$(CGO_LDFLAGS)" $(GO) test -v -timeout=20m -buildmode exe -tags byollvm ./builder ./cgo ./compileopts ./compiler ./interp ./transform . CGO_CPPFLAGS="$(CGO_CPPFLAGS)" CGO_CXXFLAGS="$(CGO_CXXFLAGS)" CGO_LDFLAGS="$(CGO_LDFLAGS)" $(GO) test -v -buildmode exe -tags byollvm ./builder ./cgo ./compileopts ./compiler ./interp ./transform .
TEST_PACKAGES = \
compress/bzip2 \
container/heap \
container/list \
container/ring \
crypto/des \
crypto/dsa \
crypto/md5 \
crypto/rc4 \
crypto/sha1 \
crypto/sha256 \
crypto/sha512 \
encoding \
encoding/ascii85 \
encoding/base32 \
encoding/hex \
hash \
hash/adler32 \
hash/fnv \
hash/crc64 \
html \
index/suffixarray \
math \
math/cmplx \
testing \
testing/iotest \
text/scanner \
text/scanner \
unicode \
unicode/utf8 \
# Test known-working standard library packages. # Test known-working standard library packages.
# TODO: parallelize, and only show failing tests (no implied -v flag). # TODO: do this in one command, parallelize, and only show failing tests (no
# implied -v flag).
.PHONY: tinygo-test .PHONY: tinygo-test
tinygo-test: tinygo-test:
$(TINYGO) test $(TEST_PACKAGES) $(TINYGO) test container/heap
$(TINYGO) test container/list
$(TINYGO) test container/ring
$(TINYGO) test crypto/des
$(TINYGO) test encoding/ascii85
$(TINYGO) test encoding/base32
$(TINYGO) test encoding/hex
$(TINYGO) test hash/adler32
$(TINYGO) test hash/fnv
$(TINYGO) test hash/crc64
$(TINYGO) test math
$(TINYGO) test math/cmplx
$(TINYGO) test text/scanner
$(TINYGO) test unicode/utf8
.PHONY: smoketest .PHONY: smoketest
smoketest: smoketest:
@@ -245,10 +224,10 @@ smoketest:
@$(MD5SUM) test.hex @$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=pca10040 examples/mcp3008 $(TINYGO) build -size short -o test.hex -target=pca10040 examples/mcp3008
@$(MD5SUM) test.hex @$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=pca10040 examples/memstats
@$(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=microbit-v2 examples/microbit-blink
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=pca10040 examples/pininterrupt $(TINYGO) build -size short -o test.hex -target=pca10040 examples/pininterrupt
@$(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
@@ -258,17 +237,17 @@ smoketest:
$(TINYGO) build -size short -o test.hex -target=pca10040 examples/test $(TINYGO) build -size short -o test.hex -target=pca10040 examples/test
@$(MD5SUM) test.hex @$(MD5SUM) test.hex
# test simulated boards on play.tinygo.org # test simulated boards on play.tinygo.org
$(TINYGO) build -size short -o test.wasm -tags=arduino examples/blinky1 $(TINYGO) build -o test.wasm -tags=arduino examples/blinky1
@$(MD5SUM) test.wasm @$(MD5SUM) test.wasm
$(TINYGO) build -size short -o test.wasm -tags=hifive1b examples/blinky1 $(TINYGO) build -o test.wasm -tags=hifive1b examples/blinky1
@$(MD5SUM) test.wasm @$(MD5SUM) test.wasm
$(TINYGO) build -size short -o test.wasm -tags=reelboard examples/blinky1 $(TINYGO) build -o test.wasm -tags=reelboard examples/blinky1
@$(MD5SUM) test.wasm @$(MD5SUM) test.wasm
$(TINYGO) build -size short -o test.wasm -tags=pca10040 examples/blinky2 $(TINYGO) build -o test.wasm -tags=pca10040 examples/blinky2
@$(MD5SUM) test.wasm @$(MD5SUM) test.wasm
$(TINYGO) build -size short -o test.wasm -tags=pca10056 examples/blinky2 $(TINYGO) build -o test.wasm -tags=pca10056 examples/blinky2
@$(MD5SUM) test.wasm @$(MD5SUM) test.wasm
$(TINYGO) build -size short -o test.wasm -tags=circuitplay_express examples/blinky1 $(TINYGO) build -o test.wasm -tags=circuitplay_express examples/blinky1
@$(MD5SUM) test.wasm @$(MD5SUM) test.wasm
# test all targets/boards # test all targets/boards
$(TINYGO) build -size short -o test.hex -target=pca10040-s132v6 examples/blinky1 $(TINYGO) build -size short -o test.hex -target=pca10040-s132v6 examples/blinky1
@@ -277,10 +256,6 @@ smoketest:
@$(MD5SUM) test.hex @$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=microbit-s110v8 examples/echo $(TINYGO) build -size short -o test.hex -target=microbit-s110v8 examples/echo
@$(MD5SUM) test.hex @$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=microbit-v2 examples/microbit-blink
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=microbit-v2-s113v7 examples/microbit-blink
@$(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
@@ -313,8 +288,6 @@ smoketest:
@$(MD5SUM) test.hex @$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.gba -target=gameboy-advance examples/gba-display $(TINYGO) build -size short -o test.gba -target=gameboy-advance examples/gba-display
@$(MD5SUM) test.gba @$(MD5SUM) test.gba
$(TINYGO) build -size short -o test.hex -target=grandcentral-m4 examples/blinky1
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=itsybitsy-m4 examples/blinky1 $(TINYGO) build -size short -o test.hex -target=itsybitsy-m4 examples/blinky1
@$(MD5SUM) test.hex @$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=feather-m4 examples/blinky1 $(TINYGO) build -size short -o test.hex -target=feather-m4 examples/blinky1
@@ -351,11 +324,9 @@ smoketest:
@$(MD5SUM) test.hex @$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=feather-nrf52840 examples/blinky1 $(TINYGO) build -size short -o test.hex -target=feather-nrf52840 examples/blinky1
@$(MD5SUM) test.hex @$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=feather-nrf52840-sense examples/blinky1
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=itsybitsy-nrf52840 examples/blinky1 $(TINYGO) build -size short -o test.hex -target=itsybitsy-nrf52840 examples/blinky1
@$(MD5SUM) test.hex @$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=qtpy examples/serial $(TINYGO) build -size short -o test.hex -target=qtpy examples/blinky1
@$(MD5SUM) test.hex @$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=teensy40 examples/blinky1 $(TINYGO) build -size short -o test.hex -target=teensy40 examples/blinky1
@$(MD5SUM) test.hex @$(MD5SUM) test.hex
@@ -363,26 +334,6 @@ smoketest:
@$(MD5SUM) test.hex @$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=p1am-100 examples/blinky1 $(TINYGO) build -size short -o test.hex -target=p1am-100 examples/blinky1
@$(MD5SUM) test.hex @$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=atsame54-xpro examples/blinky1
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=atsame54-xpro examples/can
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=feather-m4-can examples/blinky1
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=feather-m4-can examples/caninterrupt
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=arduino-nano33 examples/blinky1
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=arduino-mkrwifi1010 examples/blinky1
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=pico examples/blinky1
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=nano-33-ble examples/blinky1
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=nano-rp2040 examples/blinky1
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=feather-rp2040 examples/blinky1
@$(MD5SUM) test.hex
# test pwm # test pwm
$(TINYGO) build -size short -o test.hex -target=itsybitsy-m0 examples/pwm $(TINYGO) build -size short -o test.hex -target=itsybitsy-m0 examples/pwm
@$(MD5SUM) test.hex @$(MD5SUM) test.hex
@@ -390,6 +341,8 @@ smoketest:
@$(MD5SUM) test.hex @$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=feather-m4 examples/pwm $(TINYGO) build -size short -o test.hex -target=feather-m4 examples/pwm
@$(MD5SUM) test.hex @$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=pyportal examples/pwm
@$(MD5SUM) test.hex
ifneq ($(STM32), 0) ifneq ($(STM32), 0)
$(TINYGO) build -size short -o test.hex -target=bluepill examples/blinky1 $(TINYGO) build -size short -o test.hex -target=bluepill examples/blinky1
@$(MD5SUM) test.hex @$(MD5SUM) test.hex
@@ -401,8 +354,6 @@ ifneq ($(STM32), 0)
@$(MD5SUM) test.hex @$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=nucleo-f722ze examples/blinky1 $(TINYGO) build -size short -o test.hex -target=nucleo-f722ze examples/blinky1
@$(MD5SUM) test.hex @$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=nucleo-l031k6 examples/blinky1
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=nucleo-l432kc examples/blinky1 $(TINYGO) build -size short -o test.hex -target=nucleo-l432kc examples/blinky1
@$(MD5SUM) test.hex @$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=nucleo-l552ze examples/blinky1 $(TINYGO) build -size short -o test.hex -target=nucleo-l552ze examples/blinky1
@@ -413,8 +364,6 @@ ifneq ($(STM32), 0)
@$(MD5SUM) test.hex @$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=stm32f4disco-1 examples/blinky1 $(TINYGO) build -size short -o test.hex -target=stm32f4disco-1 examples/blinky1
@$(MD5SUM) test.hex @$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=stm32f4disco-1 examples/pwm
@$(MD5SUM) test.hex
endif endif
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
@@ -425,10 +374,6 @@ ifneq ($(AVR), 0)
@$(MD5SUM) test.hex @$(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-mega1280 examples/blinky1
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=arduino-mega1280 examples/pwm
@$(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
@$(MD5SUM) test.hex @$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=digispark examples/blinky1 $(TINYGO) build -size short -o test.hex -target=digispark examples/blinky1
@@ -441,34 +386,24 @@ ifneq ($(XTENSA), 0)
@$(MD5SUM) test.bin @$(MD5SUM) test.bin
$(TINYGO) build -size short -o test.bin -target=nodemcu examples/blinky1 $(TINYGO) build -size short -o test.bin -target=nodemcu examples/blinky1
@$(MD5SUM) test.bin @$(MD5SUM) test.bin
$(TINYGO) build -size short -o test.bin -target m5stack-core2 examples/serial
@$(MD5SUM) test.bin
endif endif
$(TINYGO) build -size short -o test.bin -target=esp32c3 examples/serial
@$(MD5SUM) test.bin
$(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=hifive1-qemu examples/serial $(TINYGO) build -size short -o test.hex -target=hifive1-qemu examples/serial
@$(MD5SUM) test.hex @$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=maixbit examples/blinky1 $(TINYGO) build -size short -o test.hex -target=maixbit examples/blinky1
@$(MD5SUM) test.hex @$(MD5SUM) test.hex
$(TINYGO) build -size short -o wasm.wasm -target=wasm examples/wasm/export $(TINYGO) build -o wasm.wasm -target=wasm examples/wasm/export
$(TINYGO) build -size short -o wasm.wasm -target=wasm examples/wasm/main $(TINYGO) build -o wasm.wasm -target=wasm examples/wasm/main
# test various compiler flags # test various compiler flags
$(TINYGO) build -size short -o test.hex -target=pca10040 -gc=none -scheduler=none examples/blinky1 $(TINYGO) build -size short -o test.hex -target=pca10040 -gc=none -scheduler=none examples/blinky1
@$(MD5SUM) test.hex @$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=pca10040 -opt=1 examples/blinky1 $(TINYGO) build -size short -o test.hex -target=pca10040 -opt=1 examples/blinky1
@$(MD5SUM) test.hex @$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=pca10040 -serial=none examples/echo
@$(MD5SUM) test.hex
$(TINYGO) build -o test.nro -target=nintendoswitch examples/serial $(TINYGO) build -o test.nro -target=nintendoswitch examples/serial
@$(MD5SUM) test.nro @$(MD5SUM) test.nro
$(TINYGO) build -size short -o test.hex -target=pca10040 -opt=0 ./testdata/stdlib.go $(TINYGO) build -size short -o test.hex -target=pca10040 -opt=0 ./testdata/stdlib.go
@$(MD5SUM) test.hex @$(MD5SUM) test.hex
GOOS=linux GOARCH=arm $(TINYGO) build -size short -o test.elf ./testdata/cgo
ifneq ($(OS),Windows_NT)
$(TINYGO) build -o test.elf -gc=leaking -scheduler=none examples/serial
endif
wasmtest: wasmtest:
@@ -479,16 +414,12 @@ build/release: tinygo gen-device wasi-libc
@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
@mkdir -p build/release/tinygo/lib/compiler-rt/lib @mkdir -p build/release/tinygo/lib/compiler-rt/lib
@mkdir -p build/release/tinygo/lib/musl/arch
@mkdir -p build/release/tinygo/lib/musl/crt
@mkdir -p build/release/tinygo/lib/musl/src
@mkdir -p build/release/tinygo/lib/nrfx @mkdir -p build/release/tinygo/lib/nrfx
@mkdir -p build/release/tinygo/lib/picolibc/newlib/libc @mkdir -p build/release/tinygo/lib/picolibc/newlib/libc
@mkdir -p build/release/tinygo/lib/picolibc/newlib/libm
@mkdir -p build/release/tinygo/lib/wasi-libc @mkdir -p build/release/tinygo/lib/wasi-libc
@mkdir -p build/release/tinygo/pkg/armv6m-unknown-unknown-eabi @mkdir -p build/release/tinygo/pkg/armv6m-none-eabi
@mkdir -p build/release/tinygo/pkg/armv7m-unknown-unknown-eabi @mkdir -p build/release/tinygo/pkg/armv7m-none-eabi
@mkdir -p build/release/tinygo/pkg/armv7em-unknown-unknown-eabi @mkdir -p build/release/tinygo/pkg/armv7em-none-eabi
@echo copying source files @echo copying source files
@cp -p build/tinygo$(EXE) build/release/tinygo/bin @cp -p build/tinygo$(EXE) build/release/tinygo/bin
@cp -p $(abspath $(CLANG_SRC))/lib/Headers/*.h build/release/tinygo/lib/clang/include @cp -p $(abspath $(CLANG_SRC))/lib/Headers/*.h build/release/tinygo/lib/clang/include
@@ -497,44 +428,22 @@ build/release: tinygo gen-device wasi-libc
@cp -rp lib/compiler-rt/lib/builtins build/release/tinygo/lib/compiler-rt/lib @cp -rp lib/compiler-rt/lib/builtins build/release/tinygo/lib/compiler-rt/lib
@cp -rp lib/compiler-rt/LICENSE.TXT build/release/tinygo/lib/compiler-rt @cp -rp lib/compiler-rt/LICENSE.TXT build/release/tinygo/lib/compiler-rt
@cp -rp lib/compiler-rt/README.txt build/release/tinygo/lib/compiler-rt @cp -rp lib/compiler-rt/README.txt build/release/tinygo/lib/compiler-rt
@cp -rp lib/musl/arch/aarch64 build/release/tinygo/lib/musl/arch
@cp -rp lib/musl/arch/arm build/release/tinygo/lib/musl/arch
@cp -rp lib/musl/arch/generic build/release/tinygo/lib/musl/arch
@cp -rp lib/musl/arch/i386 build/release/tinygo/lib/musl/arch
@cp -rp lib/musl/arch/x86_64 build/release/tinygo/lib/musl/arch
@cp -rp lib/musl/crt/crt1.c build/release/tinygo/lib/musl/crt
@cp -rp lib/musl/COPYRIGHT build/release/tinygo/lib/musl
@cp -rp lib/musl/include build/release/tinygo/lib/musl
@cp -rp lib/musl/src/env build/release/tinygo/lib/musl/src
@cp -rp lib/musl/src/errno build/release/tinygo/lib/musl/src
@cp -rp lib/musl/src/exit build/release/tinygo/lib/musl/src
@cp -rp lib/musl/src/include build/release/tinygo/lib/musl/src
@cp -rp lib/musl/src/internal build/release/tinygo/lib/musl/src
@cp -rp lib/musl/src/malloc build/release/tinygo/lib/musl/src
@cp -rp lib/musl/src/mman build/release/tinygo/lib/musl/src
@cp -rp lib/musl/src/signal build/release/tinygo/lib/musl/src
@cp -rp lib/musl/src/stdio build/release/tinygo/lib/musl/src
@cp -rp lib/musl/src/string build/release/tinygo/lib/musl/src
@cp -rp lib/musl/src/thread build/release/tinygo/lib/musl/src
@cp -rp lib/musl/src/time build/release/tinygo/lib/musl/src
@cp -rp lib/musl/src/unistd build/release/tinygo/lib/musl/src
@cp -rp lib/nrfx/* build/release/tinygo/lib/nrfx @cp -rp lib/nrfx/* build/release/tinygo/lib/nrfx
@cp -rp lib/picolibc/newlib/libc/ctype build/release/tinygo/lib/picolibc/newlib/libc @cp -rp lib/picolibc/newlib/libc/ctype build/release/tinygo/lib/picolibc/newlib/libc
@cp -rp lib/picolibc/newlib/libc/include build/release/tinygo/lib/picolibc/newlib/libc @cp -rp lib/picolibc/newlib/libc/include build/release/tinygo/lib/picolibc/newlib/libc
@cp -rp lib/picolibc/newlib/libc/locale build/release/tinygo/lib/picolibc/newlib/libc @cp -rp lib/picolibc/newlib/libc/locale build/release/tinygo/lib/picolibc/newlib/libc
@cp -rp lib/picolibc/newlib/libc/string build/release/tinygo/lib/picolibc/newlib/libc @cp -rp lib/picolibc/newlib/libc/string build/release/tinygo/lib/picolibc/newlib/libc
@cp -rp lib/picolibc/newlib/libc/tinystdio build/release/tinygo/lib/picolibc/newlib/libc @cp -rp lib/picolibc/newlib/libc/tinystdio build/release/tinygo/lib/picolibc/newlib/libc
@cp -rp lib/picolibc/newlib/libm/common build/release/tinygo/lib/picolibc/newlib/libm @cp -rp lib/picolibc-include build/release/tinygo/lib
@cp -rp lib/picolibc-stdio.c build/release/tinygo/lib
@cp -rp lib/wasi-libc/sysroot build/release/tinygo/lib/wasi-libc/sysroot @cp -rp lib/wasi-libc/sysroot build/release/tinygo/lib/wasi-libc/sysroot
@cp -rp src build/release/tinygo/src @cp -rp src build/release/tinygo/src
@cp -rp targets build/release/tinygo/targets @cp -rp targets build/release/tinygo/targets
./build/tinygo build-library -target=armv6m-unknown-unknown-eabi -o build/release/tinygo/pkg/armv6m-unknown-unknown-eabi/compiler-rt compiler-rt ./build/tinygo build-library -target=armv6m-none-eabi -o build/release/tinygo/pkg/armv6m-none-eabi/compiler-rt.a compiler-rt
./build/tinygo build-library -target=armv7m-unknown-unknown-eabi -o build/release/tinygo/pkg/armv7m-unknown-unknown-eabi/compiler-rt compiler-rt ./build/tinygo build-library -target=armv7m-none-eabi -o build/release/tinygo/pkg/armv7m-none-eabi/compiler-rt.a compiler-rt
./build/tinygo build-library -target=armv7em-unknown-unknown-eabi -o build/release/tinygo/pkg/armv7em-unknown-unknown-eabi/compiler-rt compiler-rt ./build/tinygo build-library -target=armv7em-none-eabi -o build/release/tinygo/pkg/armv7em-none-eabi/compiler-rt.a compiler-rt
./build/tinygo build-library -target=armv6m-unknown-unknown-eabi -o build/release/tinygo/pkg/armv6m-unknown-unknown-eabi/picolibc 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-unknown-unknown-eabi -o build/release/tinygo/pkg/armv7m-unknown-unknown-eabi/picolibc 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-unknown-unknown-eabi -o build/release/tinygo/pkg/armv7em-unknown-unknown-eabi/picolibc picolibc ./build/tinygo build-library -target=armv7em-none-eabi -o build/release/tinygo/pkg/armv7em-none-eabi/picolibc.a picolibc
release: build/release release: build/release
tar -czf build/release.tar.gz -C build/release tinygo tar -czf build/release.tar.gz -C build/release tinygo
+2 -18
View File
@@ -43,19 +43,15 @@ 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 71 microcontroller boards are currently supported: The following 55 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](https://www.adafruit.com/product/4500) * [Adafruit CLUE](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 M4 CAN](https://www.adafruit.com/product/4759)
* [Adafruit Feather nRF52840 Express](https://www.adafruit.com/product/4062) * [Adafruit Feather nRF52840 Express](https://www.adafruit.com/product/4062)
* [Adafruit Feather nRF52840 Sense](https://www.adafruit.com/product/4516)
* [Adafruit Feather RP2040](https://www.adafruit.com/product/4884)
* [Adafruit Feather STM32F405 Express](https://www.adafruit.com/product/4382) * [Adafruit Feather STM32F405 Express](https://www.adafruit.com/product/4382)
* [Adafruit Grand Central M4](https://www.adafruit.com/product/4064)
* [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 ItsyBitsy nRF52840](https://www.adafruit.com/product/4481)
@@ -66,15 +62,10 @@ The following 71 microcontroller boards are currently supported:
* [Adafruit PyPortal](https://www.adafruit.com/product/4116) * [Adafruit PyPortal](https://www.adafruit.com/product/4116)
* [Adafruit QT Py](https://www.adafruit.com/product/4600) * [Adafruit QT Py](https://www.adafruit.com/product/4600)
* [Adafruit Trinket M0](https://www.adafruit.com/product/3500) * [Adafruit Trinket M0](https://www.adafruit.com/product/3500)
* [Arduino Mega 1280](https://www.arduino.cc/en/Main/arduinoBoardMega/)
* [Arduino Mega 2560](https://store.arduino.cc/arduino-mega-2560-rev3) * [Arduino Mega 2560](https://store.arduino.cc/arduino-mega-2560-rev3)
* [Arduino MKR1000](https://store.arduino.cc/arduino-mkr1000-wifi) * [Arduino MKR1000](https://store.arduino.cc/arduino-mkr1000-wifi)
* [Arduino MKR WiFi 1010](https://store.arduino.cc/usa/mkr-wifi-1010)
* [Arduino Nano](https://store.arduino.cc/arduino-nano) * [Arduino Nano](https://store.arduino.cc/arduino-nano)
* [Arduino Nano 33 BLE](https://store.arduino.cc/nano-33-ble) * [Arduino Nano33 IoT](https://store.arduino.cc/nano-33-iot)
* [Arduino Nano 33 BLE Sense](https://store.arduino.cc/nano-33-ble-sense)
* [Arduino Nano 33 IoT](https://store.arduino.cc/nano-33-iot)
* [Arduino Nano RP2040 Connect](https://store.arduino.cc/nano-rp2040-connect)
* [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) * [Arduino Zero](https://store.arduino.cc/usa/arduino-zero)
* [BBC micro:bit](https://microbit.org/) * [BBC micro:bit](https://microbit.org/)
@@ -84,16 +75,13 @@ The following 71 microcontroller boards are currently supported:
* [ESP32](https://www.espressif.com/en/products/socs/esp32) * [ESP32](https://www.espressif.com/en/products/socs/esp32)
* [ESP8266](https://www.espressif.com/en/products/socs/esp8266) * [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)
* [M5Stack Core2](https://shop.m5stack.com/products/m5stack-core2-esp32-iot-development-kit)
* [Makerdiary nRF52840-MDK](https://wiki.makerdiary.com/nrf52840-mdk/) * [Makerdiary nRF52840-MDK](https://wiki.makerdiary.com/nrf52840-mdk/)
* [Makerdiary nRF52840-MDK USB Dongle](https://wiki.makerdiary.com/nrf52840-mdk-usb-dongle/) * [Makerdiary nRF52840-MDK USB Dongle](https://wiki.makerdiary.com/nrf52840-mdk-usb-dongle/)
* [Microchip SAM E54 Xplained Pro](https://www.microchip.com/developmenttools/productdetails/atsame54-xpro)
* [nice!nano](https://docs.nicekeyboards.com/#/nice!nano/) * [nice!nano](https://docs.nicekeyboards.com/#/nice!nano/)
* [Nintendo Switch](https://www.nintendo.com/switch/) * [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)
* [Nordic Semiconductor pca10059](https://www.nordicsemi.com/Software-and-tools/Development-Kits/nRF52840-Dongle)
* [Particle Argon](https://docs.particle.io/datasheets/wi-fi/argon-datasheet/) * [Particle Argon](https://docs.particle.io/datasheets/wi-fi/argon-datasheet/)
* [Particle Boron](https://docs.particle.io/datasheets/cellular/boron-datasheet/) * [Particle Boron](https://docs.particle.io/datasheets/cellular/boron-datasheet/)
* [Particle Xenon](https://docs.particle.io/datasheets/discontinued/xenon-datasheet/) * [Particle Xenon](https://docs.particle.io/datasheets/discontinued/xenon-datasheet/)
@@ -102,16 +90,12 @@ The following 71 microcontroller boards are currently supported:
* [PJRC Teensy 3.6](https://www.pjrc.com/store/teensy36.html) * [PJRC Teensy 3.6](https://www.pjrc.com/store/teensy36.html)
* [PJRC Teensy 4.0](https://www.pjrc.com/store/teensy40.html) * [PJRC Teensy 4.0](https://www.pjrc.com/store/teensy40.html)
* [ProductivityOpen P1AM-100](https://facts-engineering.github.io/modules/P1AM-100/P1AM-100.html) * [ProductivityOpen P1AM-100](https://facts-engineering.github.io/modules/P1AM-100/P1AM-100.html)
* [Raspberry Pi Pico](https://www.raspberrypi.org/products/raspberry-pi-pico/)
* [Raytac MDBT50Q-RX Dongle (with TinyUF2 bootloader)](https://www.adafruit.com/product/5199)
* [Seeed Wio Terminal](https://www.seeedstudio.com/Wio-Terminal-p-4509.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 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) * [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 "Nucleo" F722ZE](https://www.st.com/en/evaluation-tools/nucleo-f722ze.html) * [ST Micro "Nucleo" F722ZE](https://www.st.com/en/evaluation-tools/nucleo-f722ze.html)
* [ST Micro "Nucleo" L031K6](https://www.st.com/ja/evaluation-tools/nucleo-l031k6.html)
* [ST Micro "Nucleo" L432KC](https://www.st.com/ja/evaluation-tools/nucleo-l432kc.html)
* [ST Micro "Nucleo" L552ZE](https://www.st.com/en/evaluation-tools/nucleo-l552ze-q.html) * [ST Micro "Nucleo" L552ZE](https://www.st.com/en/evaluation-tools/nucleo-l552ze-q.html)
* [ST Micro STM32F103XX "Bluepill"](https://stm32-base.org/boards/STM32F103C8T6-Blue-Pill) * [ST Micro STM32F103XX "Bluepill"](https://stm32-base.org/boards/STM32F103C8T6-Blue-Pill)
* [ST Micro STM32F407 "Discovery"](https://www.st.com/en/evaluation-tools/stm32f4discovery.html) * [ST Micro STM32F407 "Discovery"](https://www.st.com/en/evaluation-tools/stm32f4discovery.html)
+88
View File
@@ -0,0 +1,88 @@
# Avoid lengthy LLVM rebuilds on each newly pushed branch. Pull requests will
# be built anyway.
trigger:
- release
- dev
jobs:
- job: Build
timeoutInMinutes: 240 # 4h
pool:
vmImage: 'VS2017-Win2016'
steps:
- task: GoTool@0
inputs:
version: '1.16'
- checkout: self
fetchDepth: 1
- task: Cache@2
displayName: Cache LLVM source
inputs:
key: llvm-source-11-windows-v1
path: llvm-project
- task: Bash@3
displayName: Download LLVM source
inputs:
targetType: inline
script: make llvm-source
- task: CacheBeta@0
displayName: Cache LLVM build
inputs:
key: llvm-build-11-windows-v4
path: llvm-build
- task: Bash@3
displayName: Build LLVM
inputs:
targetType: inline
script: |
if [ ! -f llvm-build/lib/liblldELF.a ]
then
# install dependencies
choco install ninja
# hack ninja to use fewer jobs
echo -e 'C:\\ProgramData\\Chocolatey\\bin\\ninja -j4 %*' > /usr/bin/ninja.bat
# build!
make llvm-build
fi
- task: Bash@3
displayName: Install QEMU
inputs:
targetType: inline
script: choco install qemu --version=2020.06.12
- task: CacheBeta@0
displayName: Cache wasi-libc sysroot
inputs:
key: wasi-libc-sysroot-v4
path: lib/wasi-libc/sysroot
- task: Bash@3
displayName: Build wasi-libc
inputs:
targetType: inline
script: PATH=/usr/bin:$PATH make wasi-libc
- task: Bash@3
displayName: Test TinyGo
inputs:
targetType: inline
script: |
export PATH="$PATH:./llvm-build/bin:/c/Program Files/qemu"
unset GOROOT
make test
- task: Bash@3
displayName: Build TinyGo release tarball
inputs:
targetType: inline
script: |
export PATH="$PATH:./llvm-build/bin:/c/Program Files/qemu"
unset GOROOT
make build/release -j4
- publish: $(System.DefaultWorkingDirectory)/build/release/tinygo
displayName: Publish zip as artifact
artifact: tinygo
- task: Bash@3
displayName: Smoke tests
inputs:
targetType: inline
script: |
export PATH="$PATH:./llvm-build/bin:/c/Program Files/qemu"
unset GOROOT
make smoketest TINYGO=build/tinygo AVR=0 XTENSA=0
+26 -26
View File
@@ -5,7 +5,6 @@ import (
"debug/elf" "debug/elf"
"encoding/binary" "encoding/binary"
"errors" "errors"
"fmt"
"io" "io"
"os" "os"
"path/filepath" "path/filepath"
@@ -18,12 +17,17 @@ import (
// given as a parameter. It is equivalent to the following command: // given as a parameter. It is equivalent to the following command:
// //
// ar -rcs <archivePath> <objs...> // ar -rcs <archivePath> <objs...>
func makeArchive(arfile *os.File, objs []string) error { func makeArchive(archivePath string, objs []string) error {
// Open the archive file. // Open the archive file.
arwriter := ar.NewWriter(arfile) arfile, err := os.Create(archivePath)
err := arwriter.WriteGlobalHeader()
if err != nil { if err != nil {
return &os.PathError{Op: "write ar header", Path: arfile.Name(), Err: err} return err
}
defer arfile.Close()
arwriter := ar.NewWriter(arfile)
err = arwriter.WriteGlobalHeader()
if err != nil {
return &os.PathError{"write ar header", archivePath, err}
} }
// Open all object files and read the symbols for the symbol table. // Open all object files and read the symbols for the symbol table.
@@ -31,17 +35,21 @@ func makeArchive(arfile *os.File, objs []string) error {
name string // symbol name name string // symbol name
fileIndex int // index into objfiles fileIndex int // index into objfiles
}{} }{}
archiveOffsets := make([]int32, len(objs)) objfiles := make([]struct {
file *os.File
archiveOffset int32
}, len(objs))
for i, objpath := range objs { for i, objpath := range objs {
objfile, err := os.Open(objpath) objfile, err := os.Open(objpath)
if err != nil { if err != nil {
return err return err
} }
objfiles[i].file = objfile
// Read the symbols and add them to the symbol table. // Read the symbols and add them to the symbol table.
dbg, err := elf.NewFile(objfile) dbg, err := elf.NewFile(objfile)
if err != nil { if err != nil {
return fmt.Errorf("failed to open file %s: %w", objpath, err) return err
} }
symbols, err := dbg.Symbols() symbols, err := dbg.Symbols()
if err != nil { if err != nil {
@@ -53,8 +61,9 @@ func makeArchive(arfile *os.File, objs []string) error {
// Don't include local symbols (STB_LOCAL). // Don't include local symbols (STB_LOCAL).
continue continue
} }
if elf.ST_TYPE(symbol.Info) != elf.STT_FUNC && elf.ST_TYPE(symbol.Info) != elf.STT_OBJECT { if elf.ST_TYPE(symbol.Info) != elf.STT_FUNC {
// Not a function. // Not a function.
// TODO: perhaps globals variables should also be included?
continue continue
} }
// Include in archive. // Include in archive.
@@ -63,10 +72,6 @@ func makeArchive(arfile *os.File, objs []string) error {
fileIndex int fileIndex int
}{symbol.Name, i}) }{symbol.Name, i})
} }
// Close file, to avoid issues with too many open files (especially on
// MacOS X).
objfile.Close()
} }
// Create the symbol table buffer. // Create the symbol table buffer.
@@ -120,12 +125,7 @@ func makeArchive(arfile *os.File, objs []string) error {
} }
// Add all object files to the archive. // Add all object files to the archive.
for i, objpath := range objs { for i, objfile := range objfiles {
objfile, err := os.Open(objpath)
if err != nil {
return err
}
// Store the start index, for when we'll update the symbol table with // Store the start index, for when we'll update the symbol table with
// the correct file start indices. // the correct file start indices.
offset, err := arfile.Seek(0, os.SEEK_CUR) offset, err := arfile.Seek(0, os.SEEK_CUR)
@@ -133,17 +133,17 @@ func makeArchive(arfile *os.File, objs []string) error {
return err return err
} }
if int64(int32(offset)) != offset { if int64(int32(offset)) != offset {
return errors.New("large archives (4GB+) not supported: " + arfile.Name()) return errors.New("large archives (4GB+) not supported: " + archivePath)
} }
archiveOffsets[i] = int32(offset) objfiles[i].archiveOffset = int32(offset)
// Write the file header. // Write the file header.
st, err := objfile.Stat() st, err := objfile.file.Stat()
if err != nil { if err != nil {
return err return err
} }
err = arwriter.WriteHeader(&ar.Header{ err = arwriter.WriteHeader(&ar.Header{
Name: filepath.Base(objfile.Name()), Name: filepath.Base(objfile.file.Name()),
ModTime: time.Unix(0, 0), ModTime: time.Unix(0, 0),
Uid: 0, Uid: 0,
Gid: 0, Gid: 0,
@@ -155,22 +155,22 @@ func makeArchive(arfile *os.File, objs []string) error {
} }
// Copy the file contents into the archive. // Copy the file contents into the archive.
n, err := io.Copy(arwriter, objfile) n, err := io.Copy(arwriter, objfile.file)
if err != nil { if err != nil {
return err return err
} }
if n != st.Size() { if n != st.Size() {
return errors.New("file modified during ar creation: " + arfile.Name()) return errors.New("file modified during ar creation: " + archivePath)
} }
// File is not needed anymore. // File is not needed anymore.
objfile.Close() objfile.file.Close()
} }
// Create symbol indices. // Create symbol indices.
indicesBuf := &bytes.Buffer{} indicesBuf := &bytes.Buffer{}
for _, sym := range symbolTable { for _, sym := range symbolTable {
err = binary.Write(indicesBuf, binary.BigEndian, archiveOffsets[sym.fileIndex]) err = binary.Write(indicesBuf, binary.BigEndian, objfiles[sym.fileIndex].archiveOffset)
if err != nil { if err != nil {
return err return err
} }
+124 -379
View File
@@ -12,16 +12,13 @@ import (
"errors" "errors"
"fmt" "fmt"
"go/types" "go/types"
"hash/crc32"
"io/ioutil" "io/ioutil"
"math/bits"
"os" "os"
"path/filepath" "path/filepath"
"runtime" "runtime"
"sort" "sort"
"strings" "strings"
"github.com/tinygo-org/tinygo/cgo"
"github.com/tinygo-org/tinygo/compileopts" "github.com/tinygo-org/tinygo/compileopts"
"github.com/tinygo-org/tinygo/compiler" "github.com/tinygo-org/tinygo/compiler"
"github.com/tinygo-org/tinygo/goenv" "github.com/tinygo-org/tinygo/goenv"
@@ -42,11 +39,6 @@ type BuildResult struct {
// The directory of the main package. This is useful for testing as the test // 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. // binary must be run in the directory of the tested package.
MainDir string MainDir string
// ImportPath is the import path of the main package. This is useful for
// correctly printing test results: the import path isn't always the same as
// the path listed on the command line.
ImportPath string
} }
// packageAction is the struct that is serialized to JSON and hashed, to work as // packageAction is the struct that is serialized to JSON and hashed, to work as
@@ -60,18 +52,14 @@ type BuildResult struct {
// key, avoiding the need for recompiling all dependencies when only the // key, avoiding the need for recompiling all dependencies when only the
// implementation of an imported package changes. // implementation of an imported package changes.
type packageAction struct { type packageAction struct {
ImportPath string ImportPath string
CGoVersion int // cgo.Version CompilerVersion int // compiler.Version
CompilerVersion int // compiler.Version InterpVersion int // interp.Version
InterpVersion int // interp.Version LLVMVersion string
LLVMVersion string Config *compiler.Config
Config *compiler.Config CFlags []string
CFlags []string FileHashes map[string]string // hash of every file that's part of the package
FileHashes map[string]string // hash of every file that's part of the package Imports map[string]string // map from imported package to action ID hash
Imports map[string]string // map from imported package to action ID hash
OptLevel int // LLVM optimization level (0-3)
SizeLevel int // LLVM optimization for size level (0-2)
UndefinedGlobals []string // globals that are left as external globals (no initializer)
} }
// 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
@@ -88,38 +76,6 @@ func Build(pkgName, outpath string, config *compileopts.Config, action func(Buil
} }
defer os.RemoveAll(dir) defer os.RemoveAll(dir)
// Check for a libc dependency.
// As a side effect, this also creates the headers for the given libc, if
// the libc needs them.
root := goenv.Get("TINYGOROOT")
var libcDependencies []*compileJob
switch config.Target.Libc {
case "musl":
job, err := Musl.load(config, dir)
if err != nil {
return err
}
libcDependencies = append(libcDependencies, dummyCompileJob(filepath.Join(filepath.Dir(job.result), "crt1.o")))
libcDependencies = append(libcDependencies, job)
case "picolibc":
libcJob, err := Picolibc.load(config, dir)
if err != nil {
return err
}
libcDependencies = append(libcDependencies, libcJob)
case "wasi-libc":
path := filepath.Join(root, "lib/wasi-libc/sysroot/lib/wasm32-wasi/libc.a")
if _, err := os.Stat(path); os.IsNotExist(err) {
return errors.New("could not find wasi-libc, perhaps you need to run `make wasi-libc`?")
}
libcDependencies = append(libcDependencies, dummyCompileJob(path))
case "":
// no library specified, so nothing to do
default:
return fmt.Errorf("unknown libc: %s", config.Target.Libc)
}
optLevel, sizeLevel, _ := config.OptLevels()
compilerConfig := &compiler.Config{ compilerConfig := &compiler.Config{
Triple: config.Triple(), Triple: config.Triple(),
CPU: config.CPU(), CPU: config.CPU(),
@@ -128,15 +84,13 @@ func Build(pkgName, outpath string, config *compileopts.Config, action func(Buil
GOARCH: config.GOARCH(), GOARCH: config.GOARCH(),
CodeModel: config.CodeModel(), CodeModel: config.CodeModel(),
RelocationModel: config.RelocationModel(), RelocationModel: config.RelocationModel(),
SizeLevel: sizeLevel,
Scheduler: config.Scheduler(), Scheduler: config.Scheduler(),
FuncImplementation: config.FuncImplementation(), FuncImplementation: config.FuncImplementation(),
AutomaticStackSize: config.AutomaticStackSize(), AutomaticStackSize: config.AutomaticStackSize(),
DefaultStackSize: config.Target.DefaultStackSize, DefaultStackSize: config.Target.DefaultStackSize,
NeedsStackObjects: config.NeedsStackObjects(), NeedsStackObjects: config.NeedsStackObjects(),
Debug: true, Debug: config.Debug(),
LLVMFeatures: config.LLVMFeatures(),
} }
// Load the target machine, which is the LLVM object that contains all // Load the target machine, which is the LLVM object that contains all
@@ -159,6 +113,11 @@ func Build(pkgName, outpath string, config *compileopts.Config, action func(Buil
return err return err
} }
// The slice of jobs that orchestrates most of the build.
// This is somewhat like an in-memory Makefile with each job being a
// Makefile target.
var jobs []*compileJob
// Create the *ssa.Program. This does not yet build the entire SSA of the // Create the *ssa.Program. This does not yet build the entire SSA of the
// program so it's pretty fast and doesn't need to be parallelized. // program so it's pretty fast and doesn't need to be parallelized.
program := lprogram.LoadSSA() program := lprogram.LoadSSA()
@@ -171,27 +130,17 @@ func Build(pkgName, outpath string, config *compileopts.Config, action func(Buil
for _, pkg := range lprogram.Sorted() { for _, pkg := range lprogram.Sorted() {
pkg := pkg // necessary to avoid a race condition pkg := pkg // necessary to avoid a race condition
var undefinedGlobals []string
for name := range config.Options.GlobalValues[pkg.Pkg.Path()] {
undefinedGlobals = append(undefinedGlobals, name)
}
sort.Strings(undefinedGlobals)
// Create a cache key: a hash from the action ID below that contains all // Create a cache key: a hash from the action ID below that contains all
// the parameters for the build. // the parameters for the build.
actionID := packageAction{ actionID := packageAction{
ImportPath: pkg.ImportPath, ImportPath: pkg.ImportPath,
CGoVersion: cgo.Version, CompilerVersion: compiler.Version,
CompilerVersion: compiler.Version, InterpVersion: interp.Version,
InterpVersion: interp.Version, LLVMVersion: llvm.Version,
LLVMVersion: llvm.Version, Config: compilerConfig,
Config: compilerConfig, CFlags: pkg.CFlags,
CFlags: pkg.CFlags, FileHashes: make(map[string]string, len(pkg.FileHashes)),
FileHashes: make(map[string]string, len(pkg.FileHashes)), Imports: make(map[string]string, len(pkg.Pkg.Imports())),
Imports: make(map[string]string, len(pkg.Pkg.Imports())),
OptLevel: optLevel,
SizeLevel: sizeLevel,
UndefinedGlobals: undefinedGlobals,
} }
for filePath, hash := range pkg.FileHashes { for filePath, hash := range pkg.FileHashes {
actionID.FileHashes[filePath] = hex.EncodeToString(hash) actionID.FileHashes[filePath] = hex.EncodeToString(hash)
@@ -242,69 +191,6 @@ func Build(pkgName, outpath string, config *compileopts.Config, action func(Buil
return errors.New("verification error after compiling package " + pkg.ImportPath) return errors.New("verification error after compiling package " + pkg.ImportPath)
} }
// Load bitcode of CGo headers and join the modules together.
// This may seem vulnerable to cache problems, but this is not
// the case: the Go code that was just compiled already tracks
// all C files that are read and hashes them.
// These headers could be compiled in parallel but the benefit
// is so small that it's probably not worth parallelizing.
// Packages are compiled independently anyway.
for _, cgoHeader := range pkg.CGoHeaders {
// Store the header text in a temporary file.
f, err := ioutil.TempFile(dir, "cgosnippet-*.c")
if err != nil {
return err
}
_, err = f.Write([]byte(cgoHeader))
if err != nil {
return err
}
f.Close()
// Compile the code (if there is any) to bitcode.
flags := append([]string{"-c", "-emit-llvm", "-o", f.Name() + ".bc", f.Name()}, pkg.CFlags...)
if config.Options.PrintCommands != nil {
config.Options.PrintCommands("clang", flags...)
}
err = runCCompiler(flags...)
if err != nil {
return &commandError{"failed to build CGo header", "", err}
}
// Load and link the bitcode.
// This makes it possible to optimize the functions defined
// in the header together with the Go code. In particular,
// this allows inlining. It also ensures there is only one
// file per package to cache.
headerMod, err := mod.Context().ParseBitcodeFile(f.Name() + ".bc")
if err != nil {
return fmt.Errorf("failed to load bitcode file: %w", err)
}
err = llvm.LinkModules(mod, headerMod)
if err != nil {
return fmt.Errorf("failed to link module: %w", err)
}
}
// Erase all globals that are part of the undefinedGlobals list.
// This list comes from the -ldflags="-X pkg.foo=val" option.
// Instead of setting the value directly in the AST (which would
// mean the value, which may be a secret, is stored in the build
// cache), the global itself is left external (undefined) and is
// only set at the end of the compilation.
for _, name := range undefinedGlobals {
globalName := pkg.Pkg.Path() + "." + name
global := mod.NamedGlobal(globalName)
if global.IsNil() {
return errors.New("global not found: " + globalName)
}
name := global.Name()
newGlobal := llvm.AddGlobal(mod, global.Type().ElementType(), name+".tmp")
global.ReplaceAllUsesWith(newGlobal)
global.EraseFromParentAsGlobal()
newGlobal.SetName(name)
}
// Try to interpret package initializers at compile time. // Try to interpret package initializers at compile time.
// It may only be possible to do this partially, in which case // It may only be possible to do this partially, in which case
// it is completed after all IR files are linked. // it is completed after all IR files are linked.
@@ -320,28 +206,6 @@ func Build(pkgName, outpath string, config *compileopts.Config, action func(Buil
return errors.New("verification error after interpreting " + pkgInit.Name()) return errors.New("verification error after interpreting " + pkgInit.Name())
} }
// Run function passes for each function in the module.
// These passes are intended to be run on each function right
// after they're created to reduce IR size (and maybe also for
// cache locality to improve performance), but for now they're
// run here for each function in turn. Maybe this can be
// improved in the future.
builder := llvm.NewPassManagerBuilder()
defer builder.Dispose()
builder.SetOptLevel(optLevel)
builder.SetSizeLevel(sizeLevel)
funcPasses := llvm.NewFunctionPassManagerForModule(mod)
defer funcPasses.Dispose()
builder.PopulateFunc(funcPasses)
funcPasses.InitializeFunc()
for fn := mod.FirstFunction(); !fn.IsNil(); fn = llvm.NextFunction(fn) {
if fn.IsDeclaration() {
continue
}
funcPasses.RunFunc(fn)
}
funcPasses.FinalizeFunc()
// Serialize the LLVM module as a bitcode file. // Serialize the LLVM module as a bitcode file.
// Write to a temporary path that is renamed to the destination // Write to a temporary path that is renamed to the destination
// file to avoid race conditions with other TinyGo invocatiosn // file to avoid race conditions with other TinyGo invocatiosn
@@ -375,6 +239,7 @@ func Build(pkgName, outpath string, config *compileopts.Config, action func(Buil
return os.Rename(f.Name(), bitcodePath) return os.Rename(f.Name(), bitcodePath)
}, },
} }
jobs = append(jobs, job)
packageJobs = append(packageJobs, job) packageJobs = append(packageJobs, job)
} }
@@ -405,7 +270,6 @@ func Build(pkgName, outpath string, config *compileopts.Config, action func(Buil
llvmInitFn := mod.NamedFunction("runtime.initAll") llvmInitFn := mod.NamedFunction("runtime.initAll")
llvmInitFn.SetLinkage(llvm.InternalLinkage) llvmInitFn.SetLinkage(llvm.InternalLinkage)
llvmInitFn.SetUnnamedAddr(true) llvmInitFn.SetUnnamedAddr(true)
transform.AddStandardAttributes(llvmInitFn, config)
llvmInitFn.Param(0).SetName("context") llvmInitFn.Param(0).SetName("context")
llvmInitFn.Param(1).SetName("parentHandle") llvmInitFn.Param(1).SetName("parentHandle")
block := mod.Context().AddBasicBlock(llvmInitFn, "entry") block := mod.Context().AddBasicBlock(llvmInitFn, "entry")
@@ -466,13 +330,14 @@ func Build(pkgName, outpath string, config *compileopts.Config, action func(Buil
return nil return nil
}, },
} }
jobs = append(jobs, programJob)
// Check whether we only need to create an object file. // Check whether we only need to create an object file.
// If so, we don't need to link anything and will be finished quickly. // If so, we don't need to link anything and will be finished quickly.
outext := filepath.Ext(outpath) outext := filepath.Ext(outpath)
if outext == ".o" || outext == ".bc" || outext == ".ll" { if outext == ".o" || outext == ".bc" || outext == ".ll" {
// Run jobs to produce the LLVM module. // Run jobs to produce the LLVM module.
err := runJobs(programJob, config.Options.Parallelism) err := runJobs(jobs)
if err != nil { if err != nil {
return err return err
} }
@@ -513,6 +378,7 @@ func Build(pkgName, outpath string, config *compileopts.Config, action func(Buil
return ioutil.WriteFile(objfile, llvmBuf.Bytes(), 0666) return ioutil.WriteFile(objfile, llvmBuf.Bytes(), 0666)
}, },
} }
jobs = append(jobs, outputObjectFileJob)
// Prepare link command. // Prepare link command.
linkerDependencies := []*compileJob{outputObjectFileJob} linkerDependencies := []*compileJob{outputObjectFileJob}
@@ -523,13 +389,39 @@ func Build(pkgName, outpath string, config *compileopts.Config, action func(Buil
// Add compiler-rt dependency if needed. Usually this is a simple load from // Add compiler-rt dependency if needed. Usually this is a simple load from
// a cache. // a cache.
if config.Target.RTLib == "compiler-rt" { if config.Target.RTLib == "compiler-rt" {
job, err := CompilerRT.load(config, dir) job, err := CompilerRT.load(config.Triple(), config.CPU(), dir)
if err != nil { if err != nil {
return err return err
} }
jobs = append(jobs, job.dependencies...)
jobs = append(jobs, job)
linkerDependencies = append(linkerDependencies, job) linkerDependencies = append(linkerDependencies, job)
} }
// Add libc dependency if needed.
root := goenv.Get("TINYGOROOT")
switch config.Target.Libc {
case "picolibc":
job, err := Picolibc.load(config.Triple(), config.CPU(), dir)
if err != nil {
return err
}
// The library needs to be compiled (cache miss).
jobs = append(jobs, job.dependencies...)
jobs = append(jobs, job)
linkerDependencies = append(linkerDependencies, job)
case "wasi-libc":
path := filepath.Join(root, "lib/wasi-libc/sysroot/lib/wasm32-wasi/libc.a")
if _, err := os.Stat(path); os.IsNotExist(err) {
return errors.New("could not find wasi-libc, perhaps you need to run `make wasi-libc`?")
}
ldflags = append(ldflags, path)
case "":
// no library specified, so nothing to do
default:
return fmt.Errorf("unknown libc: %s", config.Target.Libc)
}
// Add jobs to compile extra files. These files are in C or assembly and // Add jobs to compile extra files. These files are in C or assembly and
// contain things like the interrupt vector table and low level operations // contain things like the interrupt vector table and low level operations
// such as stack switching. // such as stack switching.
@@ -538,11 +430,12 @@ func Build(pkgName, outpath string, config *compileopts.Config, action func(Buil
job := &compileJob{ job := &compileJob{
description: "compile extra file " + path, description: "compile extra file " + path,
run: func(job *compileJob) error { run: func(job *compileJob) error {
result, err := compileAndCacheCFile(abspath, dir, config.CFlags(), config.Options.PrintCommands) result, err := compileAndCacheCFile(abspath, dir, config.CFlags(), config)
job.result = result job.result = result
return err return err
}, },
} }
jobs = append(jobs, job)
linkerDependencies = append(linkerDependencies, job) linkerDependencies = append(linkerDependencies, job)
} }
@@ -556,11 +449,12 @@ func Build(pkgName, outpath string, config *compileopts.Config, action func(Buil
job := &compileJob{ job := &compileJob{
description: "compile CGo file " + abspath, description: "compile CGo file " + abspath,
run: func(job *compileJob) error { run: func(job *compileJob) error {
result, err := compileAndCacheCFile(abspath, dir, pkg.CFlags, config.Options.PrintCommands) result, err := compileAndCacheCFile(abspath, dir, pkg.CFlags, config)
job.result = result job.result = result
return err return err
}, },
} }
jobs = append(jobs, job)
linkerDependencies = append(linkerDependencies, job) linkerDependencies = append(linkerDependencies, job)
} }
} }
@@ -571,48 +465,9 @@ func Build(pkgName, outpath string, config *compileopts.Config, action func(Buil
ldflags = append(ldflags, lprogram.LDFlags...) ldflags = append(ldflags, lprogram.LDFlags...)
} }
// Add libc dependencies, if they exist.
linkerDependencies = append(linkerDependencies, libcDependencies...)
// Strip debug information with -no-debug.
if !config.Debug() {
for _, tag := range config.BuildTags() {
if tag == "baremetal" {
// Don't use -no-debug on baremetal targets. It makes no sense:
// the debug information isn't flashed to the device anyway.
return fmt.Errorf("stripping debug information is unnecessary for baremetal targets")
}
}
if config.Target.Linker == "wasm-ld" {
// Don't just strip debug information, also compress relocations
// while we're at it. Relocations can only be compressed when debug
// information is stripped.
ldflags = append(ldflags, "--strip-debug", "--compress-relocations")
} else if config.Target.Linker == "ld.lld" {
// ld.lld is also used on Linux.
ldflags = append(ldflags, "--strip-debug")
} else {
switch config.GOOS() {
case "linux":
// Either real linux or an embedded system (like AVR) that
// pretends to be Linux. It's a ELF linker wrapped by GCC in any
// case (not ld.lld - that case is handled above).
ldflags = append(ldflags, "-Wl,--strip-debug")
case "darwin":
// MacOS (darwin) doesn't have a linker flag to strip debug
// information. Apple expects you to use the strip command
// instead.
return errors.New("cannot remove debug information: MacOS doesn't suppor this linker flag")
default:
// Other OSes may have different flags.
return errors.New("cannot remove debug information: unknown OS: " + config.GOOS())
}
}
}
// Create a linker job, which links all object files together and does some // Create a linker job, which links all object files together and does some
// extra stuff that can only be done after linking. // extra stuff that can only be done after linking.
linkJob := &compileJob{ jobs = append(jobs, &compileJob{
description: "link", description: "link",
dependencies: linkerDependencies, dependencies: linkerDependencies,
run: func(job *compileJob) error { run: func(job *compileJob) error {
@@ -622,8 +477,8 @@ func Build(pkgName, outpath string, config *compileopts.Config, action func(Buil
} }
ldflags = append(ldflags, dependency.result) ldflags = append(ldflags, dependency.result)
} }
if config.Options.PrintCommands != nil { if config.Options.PrintCommands {
config.Options.PrintCommands(config.Target.Linker, ldflags...) fmt.Printf("%s %s\n", config.Target.Linker, strings.Join(ldflags, " "))
} }
err = link(config.Target.Linker, ldflags...) err = link(config.Target.Linker, ldflags...)
if err != nil { if err != nil {
@@ -641,8 +496,6 @@ func Build(pkgName, outpath string, config *compileopts.Config, action func(Buil
return err return err
} }
} }
// Apply ELF patches
if config.AutomaticStackSize() { if config.AutomaticStackSize() {
// Modify the .tinygo_stacksizes section that contains a stack size // Modify the .tinygo_stacksizes section that contains a stack size
// for each goroutine. // for each goroutine.
@@ -651,39 +504,23 @@ func Build(pkgName, outpath string, config *compileopts.Config, action func(Buil
return fmt.Errorf("could not modify stack sizes: %w", err) return fmt.Errorf("could not modify stack sizes: %w", err)
} }
} }
if config.RP2040BootPatch() {
// Patch the second stage bootloader CRC into the .boot2 section
err = patchRP2040BootCRC(executable)
if err != nil {
return fmt.Errorf("could not patch RP2040 second stage boot loader: %w", err)
}
}
// Print code size if requested.
if config.Options.PrintSizes == "short" || config.Options.PrintSizes == "full" { if config.Options.PrintSizes == "short" || config.Options.PrintSizes == "full" {
packagePathMap := make(map[string]string, len(lprogram.Packages)) sizes, err := loadProgramSize(executable)
for _, pkg := range lprogram.Sorted() {
packagePathMap[pkg.OriginalDir()] = pkg.Pkg.Path()
}
sizes, err := loadProgramSize(executable, packagePathMap)
if err != nil { if err != nil {
return err return err
} }
if config.Options.PrintSizes == "short" { if config.Options.PrintSizes == "short" {
fmt.Printf(" code data bss | flash ram\n") fmt.Printf(" code data bss | flash ram\n")
fmt.Printf("%7d %7d %7d | %7d %7d\n", sizes.Code+sizes.ROData, sizes.Data, sizes.BSS, sizes.Flash(), sizes.RAM()) fmt.Printf("%7d %7d %7d | %7d %7d\n", sizes.Code, sizes.Data, sizes.BSS, sizes.Code+sizes.Data, sizes.Data+sizes.BSS)
} else { } else {
if !config.Debug() {
fmt.Println("warning: data incomplete, remove the -no-debug flag for more detail")
}
fmt.Printf(" code rodata data bss | flash ram | package\n") fmt.Printf(" code rodata data bss | flash ram | package\n")
fmt.Printf("------------------------------- | --------------- | -------\n")
for _, name := range sizes.sortedPackageNames() { for _, name := range sizes.sortedPackageNames() {
pkgSize := sizes.Packages[name] pkgSize := sizes.Packages[name]
fmt.Printf("%7d %7d %7d %7d | %7d %7d | %s\n", pkgSize.Code, pkgSize.ROData, pkgSize.Data, pkgSize.BSS, pkgSize.Flash(), pkgSize.RAM(), name) fmt.Printf("%7d %7d %7d %7d | %7d %7d | %s\n", pkgSize.Code, pkgSize.ROData, pkgSize.Data, pkgSize.BSS, pkgSize.Flash(), pkgSize.RAM(), name)
} }
fmt.Printf("------------------------------- | --------------- | -------\n") fmt.Printf("%7d %7d %7d %7d | %7d %7d | (sum)\n", sizes.Sum.Code, sizes.Sum.ROData, sizes.Sum.Data, sizes.Sum.BSS, sizes.Sum.Flash(), sizes.Sum.RAM())
fmt.Printf("%7d %7d %7d %7d | %7d %7d | total\n", sizes.Code, sizes.ROData, sizes.Data, sizes.BSS, sizes.Code+sizes.ROData+sizes.Data, sizes.Data+sizes.BSS) fmt.Printf("%7d - %7d %7d | %7d %7d | (all)\n", sizes.Code, sizes.Data, sizes.BSS, sizes.Code+sizes.Data, sizes.Data+sizes.BSS)
} }
} }
@@ -694,12 +531,12 @@ func Build(pkgName, outpath string, config *compileopts.Config, action func(Buil
return nil return nil
}, },
} })
// Run all jobs to compile and link the program. // Run all jobs to compile and link the program.
// Do this now (instead of after elf-to-hex and similar conversions) as it // Do this now (instead of after elf-to-hex and similar conversions) as it
// is simpler and cannot be parallelized. // is simpler and cannot be parallelized.
err = runJobs(linkJob, config.Options.Parallelism) err = runJobs(jobs)
if err != nil { if err != nil {
return err return err
} }
@@ -724,7 +561,7 @@ func Build(pkgName, outpath string, config *compileopts.Config, action func(Buil
if err != nil { if err != nil {
return err return err
} }
case "esp32", "esp32c3", "esp8266": case "esp32", "esp8266":
// Special format for the ESP family of chips (parsed by the ROM // Special format for the ESP family of chips (parsed by the ROM
// bootloader). // bootloader).
tmppath = filepath.Join(dir, "main"+outext) tmppath = filepath.Join(dir, "main"+outext)
@@ -732,25 +569,12 @@ func Build(pkgName, outpath string, config *compileopts.Config, action func(Buil
if err != nil { if err != nil {
return err return err
} }
case "nrf-dfu":
// special format for nrfutil for Nordic chips
tmphexpath := filepath.Join(dir, "main.hex")
err := objcopy(executable, tmphexpath, "hex")
if err != nil {
return err
}
tmppath = filepath.Join(dir, "main"+outext)
err = makeDFUFirmwareImage(config.Options, tmphexpath, tmppath)
if err != nil {
return err
}
default: default:
return fmt.Errorf("unknown output binary format: %s", outputBinaryFormat) return fmt.Errorf("unknown output binary format: %s", outputBinaryFormat)
} }
return action(BuildResult{ return action(BuildResult{
Binary: tmppath, Binary: tmppath,
MainDir: lprogram.MainPkg().Dir, MainDir: lprogram.MainPkg().Dir,
ImportPath: lprogram.MainPkg().ImportPath,
}) })
} }
@@ -779,19 +603,13 @@ func optimizeProgram(mod llvm.Module, config *compileopts.Config) error {
transform.ApplyFunctionSections(mod) // -ffunction-sections transform.ApplyFunctionSections(mod) // -ffunction-sections
} }
// Insert values from -ldflags="-X ..." into the IR.
err = setGlobalValues(mod, config.Options.GlobalValues)
if err != nil {
return err
}
// Browsers cannot handle external functions that have type i64 because it // Browsers cannot handle external functions that have type i64 because it
// cannot be represented exactly in JavaScript (JS only has doubles). To // cannot be represented exactly in JavaScript (JS only has doubles). To
// keep functions interoperable, pass int64 types as pointers to // keep functions interoperable, pass int64 types as pointers to
// stack-allocated values. // stack-allocated values.
// Use -wasm-abi=generic to disable this behaviour. // Use -wasm-abi=generic to disable this behaviour.
if config.WasmAbi() == "js" { if config.WasmAbi() == "js" {
err := transform.ExternalInt64AsPtr(mod, config) err := transform.ExternalInt64AsPtr(mod)
if err != nil { if err != nil {
return err return err
} }
@@ -799,8 +617,21 @@ func optimizeProgram(mod llvm.Module, config *compileopts.Config) error {
// Optimization levels here are roughly the same as Clang, but probably not // Optimization levels here are roughly the same as Clang, but probably not
// exactly. // exactly.
optLevel, sizeLevel, inlinerThreshold := config.OptLevels() var errs []error
errs := transform.Optimize(mod, config, optLevel, sizeLevel, inlinerThreshold) switch config.Options.Opt {
case "none", "0":
errs = transform.Optimize(mod, config, 0, 0, 0) // -O0
case "1":
errs = transform.Optimize(mod, config, 1, 0, 0) // -O1
case "2":
errs = transform.Optimize(mod, config, 2, 0, 225) // -O2
case "s":
errs = transform.Optimize(mod, config, 2, 1, 225) // -Os
case "z":
errs = transform.Optimize(mod, config, 2, 2, 5) // -Oz, default
default:
return errors.New("unknown optimization level: -opt=" + config.Options.Opt)
}
if len(errs) > 0 { if len(errs) > 0 {
return newMultiError(errs) return newMultiError(errs)
} }
@@ -822,71 +653,6 @@ func optimizeProgram(mod llvm.Module, config *compileopts.Config) error {
return nil return nil
} }
// setGlobalValues sets the global values from the -ldflags="-X ..." compiler
// option in the given module. An error may be returned if the global is not of
// the expected type.
func setGlobalValues(mod llvm.Module, globals map[string]map[string]string) error {
var pkgPaths []string
for pkgPath := range globals {
pkgPaths = append(pkgPaths, pkgPath)
}
sort.Strings(pkgPaths)
for _, pkgPath := range pkgPaths {
pkg := globals[pkgPath]
var names []string
for name := range pkg {
names = append(names, name)
}
sort.Strings(names)
for _, name := range names {
value := pkg[name]
globalName := pkgPath + "." + name
global := mod.NamedGlobal(globalName)
if global.IsNil() || !global.Initializer().IsNil() {
// The global either does not exist (optimized away?) or has
// some value, in which case it has already been initialized at
// package init time.
continue
}
// A strin is a {ptr, len} pair. We need these types to build the
// initializer.
initializerType := global.Type().ElementType()
if initializerType.TypeKind() != llvm.StructTypeKind || initializerType.StructName() == "" {
return fmt.Errorf("%s: not a string", globalName)
}
elementTypes := initializerType.StructElementTypes()
if len(elementTypes) != 2 {
return fmt.Errorf("%s: not a string", globalName)
}
// Create a buffer for the string contents.
bufInitializer := mod.Context().ConstString(value, false)
buf := llvm.AddGlobal(mod, bufInitializer.Type(), ".string")
buf.SetInitializer(bufInitializer)
buf.SetAlignment(1)
buf.SetUnnamedAddr(true)
buf.SetLinkage(llvm.PrivateLinkage)
// Create the string value, which is a {ptr, len} pair.
zero := llvm.ConstInt(mod.Context().Int32Type(), 0, false)
ptr := llvm.ConstGEP(buf, []llvm.Value{zero, zero})
if ptr.Type() != elementTypes[0] {
return fmt.Errorf("%s: not a string", globalName)
}
length := llvm.ConstInt(elementTypes[1], uint64(len(value)), false)
initializer := llvm.ConstNamedStruct(initializerType, []llvm.Value{
ptr,
length,
})
// Set the initializer. No initializer should be set at this point.
global.SetInitializer(initializer)
}
}
return nil
}
// functionStackSizes keeps stack size information about a single function // functionStackSizes keeps stack size information about a single function
// (usually a goroutine). // (usually a goroutine).
type functionStackSize struct { type functionStackSize struct {
@@ -1013,7 +779,30 @@ func determineStackSizes(mod llvm.Module, executable string) ([]string, map[stri
// stack size information. Before this modification, all stack sizes in the // stack size information. Before this modification, all stack sizes in the
// section assume the default stack size (which is relatively big). // section assume the default stack size (which is relatively big).
func modifyStackSizes(executable string, stackSizeLoads []string, stackSizes map[string]functionStackSize) error { func modifyStackSizes(executable string, stackSizeLoads []string, stackSizes map[string]functionStackSize) error {
data, fileHeader, err := getElfSectionData(executable, ".tinygo_stacksizes") 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 { if err != nil {
return err return err
} }
@@ -1035,19 +824,15 @@ func modifyStackSizes(executable string, stackSizeLoads []string, stackSizes map
if fn.stackSizeType == stacksize.Bounded { if fn.stackSizeType == stacksize.Bounded {
stackSize := uint32(fn.stackSize) stackSize := uint32(fn.stackSize)
// Add stack size used by interrupts. // Adding 4 for the stack canary. Even though the size may be
switch fileHeader.Machine { // automatically determined, stack overflow checking is still
case elf.EM_ARM: // important as the stack size cannot be determined for all
if stackSize%8 != 0 { // goroutines.
// If the stack isn't a multiple of 8, it means the leaf stackSize += 4
// function with the biggest stack depth doesn't have an aligned
// stack. If the STKALIGN flag is set (which it is by default)
// the interrupt controller will forcibly align the stack before
// storing in-use registers. This will thus overwrite one word
// past the end of the stack (off-by-one).
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 // On Cortex-M (assumed here), this stack size is 8 words or 32
// bytes. This is only to store the registers that the interrupt // bytes. This is only to store the registers that the interrupt
// may modify, the interrupt will switch to the interrupt stack // may modify, the interrupt will switch to the interrupt stack
@@ -1055,14 +840,6 @@ func modifyStackSizes(executable string, stackSizeLoads []string, stackSizes map
// Some background: // Some background:
// https://interrupt.memfault.com/blog/cortex-m-rtos-context-switching // https://interrupt.memfault.com/blog/cortex-m-rtos-context-switching
stackSize += 32 stackSize += 32
// Adding 4 for the stack canary, and another 4 to keep the
// stack aligned. 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 += 8
default:
return fmt.Errorf("unknown architecture: %s", fileHeader.Machine.String())
} }
// Finally write the stack size to the binary. // Finally write the stack size to the binary.
@@ -1070,7 +847,13 @@ func modifyStackSizes(executable string, stackSizeLoads []string, stackSizes map
} }
} }
return replaceElfSection(executable, ".tinygo_stacksizes", data) // 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 // printStacks prints the maximum stack depth for functions that are started as
@@ -1102,41 +885,3 @@ func printStacks(calculatedStacks []string, stackSizes map[string]functionStackS
} }
} }
} }
// RP2040 second stage bootloader CRC32 calculation
//
// Spec: https://datasheets.raspberrypi.org/rp2040/rp2040-datasheet.pdf
// Section: 2.8.1.3.1. Checksum
func patchRP2040BootCRC(executable string) error {
bytes, _, err := getElfSectionData(executable, ".boot2")
if err != nil {
return err
}
if len(bytes) != 256 {
return fmt.Errorf("rp2040 .boot2 section must be exactly 256 bytes")
}
// From the 'official' RP2040 checksum script:
//
// Our bootrom CRC32 is slightly bass-ackward but it's
// best to work around for now (FIXME)
// 100% worth it to save two Thumb instructions
revBytes := make([]byte, len(bytes))
for i := range bytes {
revBytes[i] = bits.Reverse8(bytes[i])
}
// crc32.Update does an initial negate and negates the
// result, so to meet RP2040 spec, pass 0x0 as initial
// hash and negate returned value.
//
// Note: checksum is over 252 bytes (256 - 4)
hash := bits.Reverse32(crc32.Update(0x0, crc32.IEEETable, revBytes[:252]) ^ 0xFFFFFFFF)
// Write the CRC to the end of the bootloader.
binary.LittleEndian.PutUint32(bytes[252:], hash)
// Update the .boot2 section to included the CRC
return replaceElfSection(executable, ".boot2", bytes)
}
+105
View File
@@ -0,0 +1,105 @@
package builder
import (
"io"
"os"
"path/filepath"
"time"
"github.com/tinygo-org/tinygo/goenv"
)
// Return the newest timestamp of all the file paths passed in. Used to check
// for stale caches.
func cacheTimestamp(paths []string) (time.Time, error) {
var timestamp time.Time
for _, path := range paths {
st, err := os.Stat(path)
if err != nil {
return time.Time{}, err
}
if timestamp.IsZero() {
timestamp = st.ModTime()
} else if timestamp.Before(st.ModTime()) {
timestamp = st.ModTime()
}
}
return timestamp, nil
}
// Try to load a given file from the cache. Return "", nil if no cached file can
// be found (or the file is stale), return the absolute path if there is a cache
// and return an error on I/O errors.
func cacheLoad(name string, sourceFiles []string) (string, error) {
cachepath := filepath.Join(goenv.Get("GOCACHE"), name)
cacheStat, err := os.Stat(cachepath)
if os.IsNotExist(err) {
return "", nil // does not exist
} else if err != nil {
return "", err // cannot stat cache file
}
sourceTimestamp, err := cacheTimestamp(sourceFiles)
if err != nil {
return "", err // cannot stat source files
}
if cacheStat.ModTime().After(sourceTimestamp) {
return cachepath, nil
} else {
os.Remove(cachepath)
// stale cache
return "", nil
}
}
// Store the file located at tmppath in the cache with the given name. The
// tmppath may or may not be gone afterwards.
func cacheStore(tmppath, name string, sourceFiles []string) (string, error) {
// get the last modified time
if len(sourceFiles) == 0 {
panic("cache: no source files")
}
// TODO: check the config key
dir := goenv.Get("GOCACHE")
err := os.MkdirAll(dir, 0777)
if err != nil {
return "", err
}
cachepath := filepath.Join(dir, name)
err = copyFile(tmppath, cachepath)
if err != nil {
return "", err
}
return cachepath, nil
}
// copyFile copies the given file from src to dst. It can copy over
// a possibly already existing file at the destination.
func copyFile(src, dst string) error {
inf, err := os.Open(src)
if err != nil {
return err
}
defer inf.Close()
outpath := dst + ".tmp"
outf, err := os.Create(outpath)
if err != nil {
return err
}
_, err = io.Copy(outf, inf)
if err != nil {
os.Remove(outpath)
return err
}
err = outf.Close()
if err != nil {
return err
}
return os.Rename(dst+".tmp", dst)
}
-146
View File
@@ -1,146 +0,0 @@
package builder
import (
"fmt"
"io/ioutil"
"os"
"path/filepath"
"testing"
"github.com/tinygo-org/tinygo/compileopts"
"github.com/tinygo-org/tinygo/goenv"
"tinygo.org/x/go-llvm"
)
// Test whether the Clang generated "target-cpu" attribute matches the CPU
// property in TinyGo target files.
func TestClangAttributes(t *testing.T) {
var targetNames = []string{
// Please keep this list sorted!
"atmega328p",
"atmega1280",
"atmega1284p",
"atmega2560",
"attiny85",
"cortex-m0",
"cortex-m0plus",
"cortex-m3",
//"cortex-m33", // TODO: broken in LLVM 11, fixed in https://reviews.llvm.org/D90305
"cortex-m4",
"cortex-m7",
"esp32c3",
"fe310",
"gameboy-advance",
"k210",
"nintendoswitch",
"riscv-qemu",
"wasi",
"wasm",
}
if hasBuiltinTools {
// hasBuiltinTools is set when TinyGo is statically linked with LLVM,
// which also implies it was built with Xtensa support.
targetNames = append(targetNames, "esp32", "esp8266")
}
for _, targetName := range targetNames {
targetName := targetName
t.Run(targetName, func(t *testing.T) {
testClangAttributes(t, &compileopts.Options{Target: targetName})
})
}
for _, options := range []*compileopts.Options{
{GOOS: "linux", GOARCH: "386"},
{GOOS: "linux", GOARCH: "amd64"},
{GOOS: "linux", GOARCH: "arm"},
{GOOS: "linux", GOARCH: "arm64"},
{GOOS: "darwin", GOARCH: "amd64"},
{GOOS: "darwin", GOARCH: "arm64"},
} {
t.Run("GOOS="+options.GOOS+",GOARCH="+options.GOARCH, func(t *testing.T) {
testClangAttributes(t, options)
})
}
}
func testClangAttributes(t *testing.T, options *compileopts.Options) {
testDir := t.TempDir()
clangHeaderPath := getClangHeaderPath(goenv.Get("TINYGOROOT"))
ctx := llvm.NewContext()
defer ctx.Dispose()
target, err := compileopts.LoadTarget(options)
if err != nil {
t.Fatalf("could not load target: %s", err)
}
config := compileopts.Config{
Options: options,
Target: target,
ClangHeaders: clangHeaderPath,
}
// Create a very simple C input file.
srcpath := filepath.Join(testDir, "test.c")
err = ioutil.WriteFile(srcpath, []byte("int add(int a, int b) { return a + b; }"), 0o666)
if err != nil {
t.Fatalf("could not write target file %s: %s", srcpath, err)
}
// Compile this file using Clang.
outpath := filepath.Join(testDir, "test.bc")
flags := append([]string{"-c", "-emit-llvm", "-o", outpath, srcpath}, config.CFlags()...)
if config.GOOS() == "darwin" {
// Silence some warnings that happen when testing GOOS=darwin on
// something other than MacOS.
flags = append(flags, "-Wno-missing-sysroot", "-Wno-incompatible-sysroot")
}
err = runCCompiler(flags...)
if err != nil {
t.Fatalf("failed to compile %s: %s", srcpath, err)
}
// Read the resulting LLVM bitcode.
mod, err := ctx.ParseBitcodeFile(outpath)
if err != nil {
t.Fatalf("could not parse bitcode file %s: %s", outpath, err)
}
defer mod.Dispose()
// Check whether the LLVM target matches.
if mod.Target() != config.Triple() {
t.Errorf("target has LLVM triple %#v but Clang makes it LLVM triple %#v", config.Triple(), mod.Target())
}
// Check the "target-cpu" string attribute of the add function.
add := mod.NamedFunction("add")
var cpu string
cpuAttr := add.GetStringAttributeAtIndex(-1, "target-cpu")
if !cpuAttr.IsNil() {
cpu = cpuAttr.GetStringValue()
}
if cpu != config.CPU() {
t.Errorf("target has CPU %#v but Clang makes it CPU %#v", config.CPU(), cpu)
}
}
// This TestMain is necessary because TinyGo may also be invoked to run certain
// LLVM tools in a separate process. Not capturing these invocations would lead
// to recursive tests.
func TestMain(m *testing.M) {
if len(os.Args) >= 2 {
switch os.Args[1] {
case "clang", "ld.lld", "wasm-ld":
// Invoke a specific tool.
err := RunTool(os.Args[1], os.Args[2:]...)
if err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
os.Exit(0)
}
}
// Run normal tests.
os.Exit(m.Run())
}
+3 -5
View File
@@ -157,12 +157,10 @@ var aeabiBuiltins = []string{
// //
// For more information, see: https://compiler-rt.llvm.org/ // For more information, see: https://compiler-rt.llvm.org/
var CompilerRT = Library{ var CompilerRT = Library{
name: "compiler-rt", name: "compiler-rt",
cflags: func(target, headerPath string) []string { cflags: func() []string { return []string{"-Werror", "-Wall", "-std=c11", "-nostdlibinc"} },
return []string{"-Werror", "-Wall", "-std=c11", "-nostdlibinc"}
},
sourceDir: "lib/compiler-rt/lib/builtins", sourceDir: "lib/compiler-rt/lib/builtins",
librarySources: func(target string) []string { sources: func(target string) []string {
builtins := append([]string{}, genericBuiltins...) // copy genericBuiltins builtins := append([]string{}, genericBuiltins...) // copy genericBuiltins
if strings.HasPrefix(target, "arm") || strings.HasPrefix(target, "thumb") { if strings.HasPrefix(target, "arm") || strings.HasPrefix(target, "thumb") {
builtins = append(builtins, aeabiBuiltins...) builtins = append(builtins, aeabiBuiltins...)
+8 -16
View File
@@ -17,6 +17,7 @@ import (
"strings" "strings"
"unicode" "unicode"
"github.com/tinygo-org/tinygo/compileopts"
"github.com/tinygo-org/tinygo/goenv" "github.com/tinygo-org/tinygo/goenv"
"tinygo.org/x/go-llvm" "tinygo.org/x/go-llvm"
) )
@@ -56,7 +57,7 @@ import (
// depfile but without invalidating its name. For this reason, the depfile is // depfile but without invalidating its name. For this reason, the depfile is
// written on each new compilation (even when it seems unnecessary). However, it // written on each new compilation (even when it seems unnecessary). However, it
// could in rare cases lead to a stale file fetched from the cache. // could in rare cases lead to a stale file fetched from the cache.
func compileAndCacheCFile(abspath, tmpdir string, cflags []string, printCommands func(string, ...string)) (string, error) { func compileAndCacheCFile(abspath, tmpdir string, cflags []string, config *compileopts.Config) (string, error) {
// Hash input file. // Hash input file.
fileHash, err := hashFile(abspath) fileHash, err := hashFile(abspath)
if err != nil { if err != nil {
@@ -67,12 +68,14 @@ func compileAndCacheCFile(abspath, tmpdir string, cflags []string, printCommands
buf, err := json.Marshal(struct { buf, err := json.Marshal(struct {
Path string Path string
Hash string Hash string
Compiler string
Flags []string Flags []string
LLVMVersion string LLVMVersion string
}{ }{
Path: abspath, Path: abspath,
Hash: fileHash, Hash: fileHash,
Flags: cflags, Compiler: config.Target.Compiler,
Flags: config.CFlags(),
LLVMVersion: llvm.Version, LLVMVersion: llvm.Version,
}) })
if err != nil { if err != nil {
@@ -121,17 +124,10 @@ func compileAndCacheCFile(abspath, tmpdir string, cflags []string, printCommands
flags := append([]string{}, cflags...) // copy cflags flags := append([]string{}, cflags...) // copy cflags
flags = append(flags, "-MD", "-MV", "-MTdeps", "-MF", depTmpFile.Name()) // autogenerate dependencies flags = append(flags, "-MD", "-MV", "-MTdeps", "-MF", depTmpFile.Name()) // autogenerate dependencies
flags = append(flags, "-c", "-o", objTmpFile.Name(), abspath) flags = append(flags, "-c", "-o", objTmpFile.Name(), abspath)
if strings.ToLower(filepath.Ext(abspath)) == ".s" { if config.Options.PrintCommands {
// If this is an assembly file (.s or .S, lowercase or uppercase), then fmt.Printf("%s %s\n", config.Target.Compiler, strings.Join(flags, " "))
// we'll need to add -Qunused-arguments because many parameters are
// relevant to C, not assembly. And with -Werror, having meaningless
// flags (for the assembler) is a compiler error.
flags = append(flags, "-Qunused-arguments")
} }
if printCommands != nil { err = runCCompiler(config.Target.Compiler, flags...)
printCommands("clang", flags...)
}
err = runCCompiler(flags...)
if err != nil { if err != nil {
return "", &commandError{"failed to build", abspath, err} return "", &commandError{"failed to build", abspath, err}
} }
@@ -155,10 +151,6 @@ func compileAndCacheCFile(abspath, tmpdir string, cflags []string, printCommands
// Write dependencies file. // Write dependencies file.
f, err := ioutil.TempFile(filepath.Dir(depfileCachePath), depfileName) f, err := ioutil.TempFile(filepath.Dir(depfileCachePath), depfileName)
if err != nil {
return "", err
}
buf, err = json.MarshalIndent(dependencySlice, "", "\t") buf, err = json.MarshalIndent(dependencySlice, "", "\t")
if err != nil { if err != nil {
panic(err) // shouldn't happen panic(err) // shouldn't happen
+11 -25
View File
@@ -21,7 +21,6 @@ func init() {
commands["clang"] = []string{"clang-" + llvmMajor} commands["clang"] = []string{"clang-" + llvmMajor}
commands["ld.lld"] = []string{"ld.lld-" + llvmMajor, "ld.lld"} commands["ld.lld"] = []string{"ld.lld-" + llvmMajor, "ld.lld"}
commands["wasm-ld"] = []string{"wasm-ld-" + llvmMajor, "wasm-ld"} commands["wasm-ld"] = []string{"wasm-ld-" + llvmMajor, "wasm-ld"}
commands["lldb"] = []string{"lldb-" + llvmMajor, "lldb"}
// Add the path to a Homebrew-installed LLVM for ease of use (no need to // Add the path to a Homebrew-installed LLVM for ease of use (no need to
// manually set $PATH). // manually set $PATH).
if runtime.GOOS == "darwin" { if runtime.GOOS == "darwin" {
@@ -29,7 +28,6 @@ func init() {
commands["clang"] = append(commands["clang"], prefix+"clang-"+llvmMajor) commands["clang"] = append(commands["clang"], prefix+"clang-"+llvmMajor)
commands["ld.lld"] = append(commands["ld.lld"], prefix+"ld.lld") commands["ld.lld"] = append(commands["ld.lld"], prefix+"ld.lld")
commands["wasm-ld"] = append(commands["wasm-ld"], prefix+"wasm-ld") commands["wasm-ld"] = append(commands["wasm-ld"], prefix+"wasm-ld")
commands["lldb"] = append(commands["lldb"], prefix+"lldb")
} }
// Add the path for when LLVM was installed with the installer from // Add the path for when LLVM was installed with the installer from
// llvm.org, which by default doesn't add LLVM to the $PATH environment // llvm.org, which by default doesn't add LLVM to the $PATH environment
@@ -38,7 +36,6 @@ func init() {
commands["clang"] = append(commands["clang"], "clang", "C:\\Program Files\\LLVM\\bin\\clang.exe") commands["clang"] = append(commands["clang"], "clang", "C:\\Program Files\\LLVM\\bin\\clang.exe")
commands["ld.lld"] = append(commands["ld.lld"], "lld", "C:\\Program Files\\LLVM\\bin\\lld.exe") commands["ld.lld"] = append(commands["ld.lld"], "lld", "C:\\Program Files\\LLVM\\bin\\lld.exe")
commands["wasm-ld"] = append(commands["wasm-ld"], "C:\\Program Files\\LLVM\\bin\\wasm-ld.exe") commands["wasm-ld"] = append(commands["wasm-ld"], "C:\\Program Files\\LLVM\\bin\\wasm-ld.exe")
commands["lldb"] = append(commands["lldb"], "C:\\Program Files\\LLVM\\bin\\lldb.exe")
} }
// Add the path to LLVM installed from ports. // Add the path to LLVM installed from ports.
if runtime.GOOS == "freebsd" { if runtime.GOOS == "freebsd" {
@@ -46,34 +43,23 @@ func init() {
commands["clang"] = append(commands["clang"], prefix+"clang-"+llvmMajor) commands["clang"] = append(commands["clang"], prefix+"clang-"+llvmMajor)
commands["ld.lld"] = append(commands["ld.lld"], prefix+"ld.lld") commands["ld.lld"] = append(commands["ld.lld"], prefix+"ld.lld")
commands["wasm-ld"] = append(commands["wasm-ld"], prefix+"wasm-ld") commands["wasm-ld"] = append(commands["wasm-ld"], prefix+"wasm-ld")
commands["lldb"] = append(commands["lldb"], prefix+"lldb")
} }
} }
// LookupCommand looks up the executable name for a given LLVM tool such as func execCommand(cmdNames []string, args ...string) error {
// clang or wasm-ld. It returns the (relative) command that can be used to for _, cmdName := range cmdNames {
// invoke the tool or an error if it could not be found. cmd := exec.Command(cmdName, args...)
func LookupCommand(name string) (string, error) { cmd.Stdout = os.Stdout
for _, cmdName := range commands[name] { cmd.Stderr = os.Stderr
_, err := exec.LookPath(cmdName) err := cmd.Run()
if err != nil { if err != nil {
if errors.Unwrap(err) == exec.ErrNotFound { if err, ok := err.(*exec.Error); ok && (err.Err == exec.ErrNotFound || err.Err.Error() == "file does not exist") {
// this command was not found, try the next
continue continue
} }
return cmdName, err return err
} }
return cmdName, nil return nil
} }
return "", errors.New("%#v: none of these commands were found in your $PATH: " + strings.Join(commands[name], " ")) return errors.New("none of these commands were found in your $PATH: " + strings.Join(cmdNames, " "))
}
func execCommand(name string, args ...string) error {
name, err := LookupCommand(name)
if err != nil {
return err
}
cmd := exec.Command(name, args...)
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
return cmd.Run()
} }
+3 -13
View File
@@ -13,32 +13,22 @@ import (
// uses the currently active GOPATH (from the goenv package) to determine the Go // uses the currently active GOPATH (from the goenv package) to determine the Go
// version to use. // version to use.
func NewConfig(options *compileopts.Options) (*compileopts.Config, error) { func NewConfig(options *compileopts.Options) (*compileopts.Config, error) {
spec, err := compileopts.LoadTarget(options) spec, err := compileopts.LoadTarget(options.Target)
if err != nil { if err != nil {
return nil, err return nil, err
} }
if options.OpenOCDCommands != nil {
// Override the OpenOCDCommands from the target spec if specified on
// the command-line
spec.OpenOCDCommands = options.OpenOCDCommands
}
goroot := goenv.Get("GOROOT") goroot := goenv.Get("GOROOT")
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 := goenv.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 < 15 || minor > 17 { if major != 1 || minor < 13 || minor > 16 {
return nil, fmt.Errorf("requires go version 1.15 through 1.17, got go%d.%d", major, minor) return nil, fmt.Errorf("requires go version 1.13 through 1.16, got go%d.%d", major, minor)
} }
clangHeaderPath := getClangHeaderPath(goenv.Get("TINYGOROOT")) clangHeaderPath := getClangHeaderPath(goenv.Get("TINYGOROOT"))
return &compileopts.Config{ return &compileopts.Config{
Options: options, Options: options,
Target: spec, Target: spec,
-57
View File
@@ -1,57 +0,0 @@
package builder
import (
"debug/elf"
"fmt"
"os"
)
func getElfSectionData(executable string, sectionName string) ([]byte, elf.FileHeader, error) {
elfFile, err := elf.Open(executable)
if err != nil {
return nil, elf.FileHeader{}, err
}
defer elfFile.Close()
section := elfFile.Section(sectionName)
if section == nil {
return nil, elf.FileHeader{}, fmt.Errorf("could not find %s section", sectionName)
}
data, err := section.Data()
return data, elfFile.FileHeader, err
}
func replaceElfSection(executable string, sectionName string, data []byte) error {
fp, err := os.OpenFile(executable, os.O_RDWR, 0)
if err != nil {
return err
}
defer fp.Close()
elfFile, err := elf.Open(executable)
if err != nil {
return err
}
defer elfFile.Close()
section := elfFile.Section(sectionName)
if section == nil {
return fmt.Errorf("could not find %s section", sectionName)
}
// Implicitly check for compressed sections
if section.Size != section.FileSize {
return fmt.Errorf("expected section %s to have identical size and file size, got %d and %d", sectionName, section.Size, section.FileSize)
}
// Only permit complete replacement of section
if section.Size != uint64(len(data)) {
return fmt.Errorf("expected section %s to have size %d, was actually %d", sectionName, len(data), section.Size)
}
// Write the replacement section data
_, err = fp.WriteAt(data, int64(section.Offset))
return err
}
+6 -19
View File
@@ -78,21 +78,11 @@ func makeESPFirmareImage(infile, outfile, format string) error {
// An added benefit is that we don't need to check for errors all the time. // An added benefit is that we don't need to check for errors all the time.
outf := &bytes.Buffer{} outf := &bytes.Buffer{}
// Chip IDs. Source:
// https://github.com/espressif/esp-idf/blob/v4.3/components/bootloader_support/include/esp_app_format.h#L22
chip_id := map[string]uint16{
"esp32": 0x0000,
"esp32c3": 0x0005,
}[format]
// Image header. // Image header.
switch format { switch format {
case "esp32", "esp32c3": case "esp32":
// Header format: // Header format:
// https://github.com/espressif/esp-idf/blob/v4.3/components/bootloader_support/include/esp_app_format.h#L71 // https://github.com/espressif/esp-idf/blob/8fbb63c2/components/bootloader_support/include/esp_image_format.h#L58
// Note: not adding a SHA256 hash as the binary is modified by
// esptool.py while flashing and therefore the hash won't be valid
// anymore.
binary.Write(outf, binary.LittleEndian, struct { binary.Write(outf, binary.LittleEndian, struct {
magic uint8 magic uint8
segment_count uint8 segment_count uint8
@@ -101,18 +91,15 @@ func makeESPFirmareImage(infile, outfile, format string) error {
entry_addr uint32 entry_addr uint32
wp_pin uint8 wp_pin uint8
spi_pin_drv [3]uint8 spi_pin_drv [3]uint8
chip_id uint16 reserved [11]uint8
min_chip_rev uint8
reserved [8]uint8
hash_appended bool hash_appended bool
}{ }{
magic: 0xE9, magic: 0xE9,
segment_count: byte(len(segments)), segment_count: byte(len(segments)),
spi_mode: 2, // ESP_IMAGE_SPI_MODE_DIO spi_mode: 0, // irrelevant, replaced by esptool when flashing
spi_speed_size: 0x1f, // ESP_IMAGE_SPI_SPEED_80M, ESP_IMAGE_FLASH_SIZE_2MB spi_speed_size: 0, // spi_speed, spi_size: replaced by esptool when flashing
entry_addr: uint32(inf.Entry), entry_addr: uint32(inf.Entry),
wp_pin: 0xEE, // disable WP pin wp_pin: 0xEE, // disable WP pin
chip_id: chip_id,
hash_appended: true, // add a SHA256 hash hash_appended: true, // add a SHA256 hash
}) })
case "esp8266": case "esp8266":
@@ -155,7 +142,7 @@ func makeESPFirmareImage(infile, outfile, format string) error {
outf.Write(make([]byte, 15-outf.Len()%16)) outf.Write(make([]byte, 15-outf.Len()%16))
outf.WriteByte(checksum) outf.WriteByte(checksum)
if format != "esp8266" { if format == "esp32" {
// SHA256 hash (to protect against image corruption, not for security). // SHA256 hash (to protect against image corruption, not for security).
hash := sha256.Sum256(outf.Bytes()) hash := sha256.Sum256(outf.Bytes())
outf.Write(hash[:]) outf.Write(hash[:])
+8 -34
View File
@@ -65,45 +65,19 @@ func (job *compileJob) readyToRun() bool {
return true return true
} }
// runJobs runs the indicated job and all its dependencies. For every job, all // runJobs runs all the jobs indicated in the jobs slice and returns the error
// the dependencies are run first. It returns the error of the first job that // of the first job that fails to run.
// fails. // It runs all jobs in the order of the slice, as long as all dependencies have
// It runs all jobs in the order of the dependencies slice, depth-first. // already run. Therefore, if some jobs are preferred to run before others, they
// Therefore, if some jobs are preferred to run before others, they should be // should be ordered as such in this slice.
// ordered as such in the job dependencies. func runJobs(jobs []*compileJob) error {
func runJobs(job *compileJob, parallelism int) error {
if parallelism == 0 {
// Have a default, if the parallelism isn't set. This is useful for
// tests.
parallelism = runtime.NumCPU()
}
if parallelism < 1 {
return fmt.Errorf("-p flag must be at least 1, provided -p=%d", parallelism)
}
// Create a slice of jobs to run, where all dependencies are run in order.
jobs := []*compileJob{}
addedJobs := map[*compileJob]struct{}{}
var addJobs func(*compileJob)
addJobs = func(job *compileJob) {
if _, ok := addedJobs[job]; ok {
return
}
for _, dep := range job.dependencies {
addJobs(dep)
}
jobs = append(jobs, job)
addedJobs[job] = struct{}{}
}
addJobs(job)
// Create channels to communicate with the workers. // Create channels to communicate with the workers.
doneChan := make(chan *compileJob) doneChan := make(chan *compileJob)
workerChan := make(chan *compileJob) workerChan := make(chan *compileJob)
defer close(workerChan) defer close(workerChan)
// Start a number of workers. // Start a number of workers.
for i := 0; i < parallelism; i++ { for i := 0; i < runtime.NumCPU(); i++ {
if jobRunnerDebug { if jobRunnerDebug {
fmt.Println("## starting worker", i) fmt.Println("## starting worker", i)
} }
@@ -118,7 +92,7 @@ func runJobs(job *compileJob, parallelism int) error {
for { for {
// If there are free workers, try starting a new job (if one is // If there are free workers, try starting a new job (if one is
// available). If it succeeds, try again to fill the entire worker pool. // available). If it succeeds, try again to fill the entire worker pool.
if numRunningJobs < parallelism { if numRunningJobs < runtime.NumCPU() {
jobToRun := nextJob(jobs) jobToRun := nextJob(jobs)
if jobToRun != nil { if jobToRun != nil {
// Start job. // Start job.
+52 -114
View File
@@ -1,12 +1,10 @@
package builder package builder
import ( import (
"io/ioutil"
"os" "os"
"path/filepath" "path/filepath"
"strings" "strings"
"github.com/tinygo-org/tinygo/compileopts"
"github.com/tinygo-org/tinygo/goenv" "github.com/tinygo-org/tinygo/goenv"
) )
@@ -16,87 +14,72 @@ type Library struct {
// The library name, such as compiler-rt or picolibc. // The library name, such as compiler-rt or picolibc.
name string name string
// makeHeaders creates a header include dir for the library cflags func() []string
makeHeaders func(target, includeDir string) error
// cflags returns the C flags specific to this library
cflags func(target, headerPath string) []string
// The source directory, relative to TINYGOROOT. // The source directory, relative to TINYGOROOT.
sourceDir string sourceDir string
// The source files, relative to sourceDir. // The source files, relative to sourceDir.
librarySources func(target string) []string sources func(target string) []string
}
// The source code for the crt1.o file, relative to sourceDir. // fullPath returns the full path to the source directory.
crt1Source string func (l *Library) fullPath() string {
return filepath.Join(goenv.Get("TINYGOROOT"), l.sourceDir)
}
// sourcePaths returns a slice with the full paths to the source files.
func (l *Library) sourcePaths(target string) []string {
sources := l.sources(target)
paths := make([]string, len(sources))
for i, name := range sources {
paths[i] = filepath.Join(l.fullPath(), name)
}
return paths
} }
// Load the library archive, possibly generating and caching it if needed. // Load the library archive, possibly generating and caching it if needed.
// The resulting directory may be stored in the provided tmpdir, which is // The resulting file is stored in the provided tmpdir, which is expected to be
// expected to be removed after the Load call. // removed after the Load call.
func (l *Library) Load(config *compileopts.Config, tmpdir string) (dir string, err error) { func (l *Library) Load(target, tmpdir string) (path string, err error) {
job, err := l.load(config, tmpdir) job, err := l.load(target, "", tmpdir)
if err != nil { if err != nil {
return "", err return "", err
} }
err = runJobs(job, config.Options.Parallelism) jobs := append([]*compileJob{job}, job.dependencies...)
return filepath.Dir(job.result), err err = runJobs(jobs)
return job.result, err
} }
// load returns a compile job to build this library file for the given target // load returns a compile job to build this library file for the given target
// and CPU. It may return a dummy compileJob if the library build is already // and CPU. It may return a dummy compileJob if the library build is already
// cached. The path is stored as job.result but is only valid after the job has // cached. The path is stored as job.result but is only valid if the job and
// been run. // job.dependencies have been run.
// The provided tmpdir will be used to store intermediary files and possibly the // The provided tmpdir will be used to store intermediary files and possibly the
// output archive file, it is expected to be removed after use. // output archive file, it is expected to be removed after use.
// As a side effect, this call creates the library header files if they didn't func (l *Library) load(target, cpu, tmpdir string) (job *compileJob, err error) {
// exist yet. // Try to load a precompiled library.
func (l *Library) load(config *compileopts.Config, tmpdir string) (job *compileJob, err error) { precompiledPath := filepath.Join(goenv.Get("TINYGOROOT"), "pkg", target, l.name+".a")
outdir, precompiled := config.LibcPath(l.name) if _, err := os.Stat(precompiledPath); err == nil {
archiveFilePath := filepath.Join(outdir, "lib.a")
if precompiled {
// Found a precompiled library for this OS/architecture. Return the path // Found a precompiled library for this OS/architecture. Return the path
// directly. // directly.
return dummyCompileJob(archiveFilePath), nil return dummyCompileJob(precompiledPath), nil
}
var outfile string
if cpu != "" {
outfile = l.name + "-" + target + "-" + cpu + ".a"
} else {
outfile = l.name + "-" + target + ".a"
} }
// Try to fetch this library from the cache. // Try to fetch this library from the cache.
if _, err := os.Stat(archiveFilePath); err == nil { if path, err := cacheLoad(outfile, l.sourcePaths(target)); path != "" || err != nil {
return dummyCompileJob(archiveFilePath), nil // Cache hit.
return dummyCompileJob(path), nil
} }
// Cache miss, build it now. // Cache miss, build it now.
// Create the destination directory where the components of this library
// (lib.a file, include directory) are placed.
outname := filepath.Base(outdir)
err = os.MkdirAll(filepath.Join(goenv.Get("GOCACHE"), outname), 0o777)
if err != nil {
// Could not create directory (and not because it already exists).
return nil, err
}
// Make headers if needed.
headerPath := filepath.Join(outdir, "include")
target := config.Triple()
if l.makeHeaders != nil {
if _, err = os.Stat(headerPath); err != nil {
temporaryHeaderPath, err := ioutil.TempDir(outdir, "include.tmp*")
if err != nil {
return nil, err
}
defer os.RemoveAll(temporaryHeaderPath)
err = l.makeHeaders(target, temporaryHeaderPath)
if err != nil {
return nil, err
}
err = os.Rename(temporaryHeaderPath, headerPath)
if err != nil {
return nil, err
}
}
}
remapDir := filepath.Join(os.TempDir(), "tinygo-"+l.name) remapDir := filepath.Join(os.TempDir(), "tinygo-"+l.name)
dir := filepath.Join(tmpdir, "build-lib-"+l.name) dir := filepath.Join(tmpdir, "build-lib-"+l.name)
err = os.Mkdir(dir, 0777) err = os.Mkdir(dir, 0777)
@@ -108,16 +91,9 @@ func (l *Library) load(config *compileopts.Config, tmpdir string) (job *compileJ
// Note: -fdebug-prefix-map is necessary to make the output archive // Note: -fdebug-prefix-map is necessary to make the output archive
// reproducible. Otherwise the temporary directory is stored in the archive // reproducible. Otherwise the temporary directory is stored in the archive
// itself, which varies each run. // itself, which varies each run.
args := append(l.cflags(target, headerPath), "-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)
cpu := config.CPU()
if cpu != "" { if cpu != "" {
// X86 has deprecated the -mcpu flag, so we need to use -march instead. args = append(args, "-mcpu="+cpu)
// However, ARM has not done this.
if strings.HasPrefix(target, "i386") || strings.HasPrefix(target, "x86_64") {
args = append(args, "-march="+cpu)
} else {
args = append(args, "-mcpu="+cpu)
}
} }
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", "-mfloat-abi=soft")
@@ -132,39 +108,27 @@ func (l *Library) load(config *compileopts.Config, tmpdir string) (job *compileJ
// Create job to put all the object files in a single archive. This archive // Create job to put all the object files in a single archive. This archive
// file is the (static) library file. // file is the (static) library file.
var objs []string var objs []string
arpath := filepath.Join(dir, l.name+".a")
job = &compileJob{ job = &compileJob{
description: "ar " + l.name + "/lib.a", description: "ar " + l.name + ".a",
result: filepath.Join(goenv.Get("GOCACHE"), outname, "lib.a"), result: arpath,
run: func(*compileJob) error { run: func(*compileJob) error {
// Create an archive of all object files. // Create an archive of all object files.
f, err := ioutil.TempFile(outdir, "libc.a.tmp*") err := makeArchive(arpath, objs)
if err != nil {
return err
}
err = makeArchive(f, objs)
if err != nil {
return err
}
err = f.Close()
if err != nil { if err != nil {
return err return err
} }
// Store this archive in the cache. // Store this archive in the cache.
return os.Rename(f.Name(), archiveFilePath) _, err = cacheStore(arpath, outfile, l.sourcePaths(target))
return err
}, },
} }
// Create jobs to compile all sources. These jobs are depended upon by the // Create jobs to compile all sources. These jobs are depended upon by the
// archive job above, so must be run first. // archive job above, so must be run first.
for _, path := range l.librarySources(target) { for _, srcpath := range l.sourcePaths(target) {
// Strip leading "../" parts off the path. srcpath := srcpath // avoid concurrency issues by redefining inside the loop
cleanpath := path objpath := filepath.Join(dir, filepath.Base(srcpath)+".o")
for strings.HasPrefix(cleanpath, "../") {
cleanpath = cleanpath[3:]
}
srcpath := filepath.Join(goenv.Get("TINYGOROOT"), l.sourceDir, path)
objpath := filepath.Join(dir, cleanpath+".o")
os.MkdirAll(filepath.Dir(objpath), 0o777)
objs = append(objs, objpath) objs = append(objs, objpath)
job.dependencies = append(job.dependencies, &compileJob{ job.dependencies = append(job.dependencies, &compileJob{
description: "compile " + srcpath, description: "compile " + srcpath,
@@ -172,7 +136,7 @@ func (l *Library) load(config *compileopts.Config, tmpdir string) (job *compileJ
var compileArgs []string var compileArgs []string
compileArgs = append(compileArgs, args...) compileArgs = append(compileArgs, args...)
compileArgs = append(compileArgs, "-o", objpath, srcpath) compileArgs = append(compileArgs, "-o", objpath, srcpath)
err := runCCompiler(compileArgs...) err := runCCompiler("clang", compileArgs...)
if err != nil { if err != nil {
return &commandError{"failed to build", srcpath, err} return &commandError{"failed to build", srcpath, err}
} }
@@ -181,31 +145,5 @@ func (l *Library) load(config *compileopts.Config, tmpdir string) (job *compileJ
}) })
} }
// Create crt1.o job, if needed.
// Add this as a (fake) dependency to the ar file so it gets compiled.
// (It could be done in parallel with creating the ar file, but it probably
// won't make much of a difference in speed).
if l.crt1Source != "" {
srcpath := filepath.Join(goenv.Get("TINYGOROOT"), l.sourceDir, l.crt1Source)
job.dependencies = append(job.dependencies, &compileJob{
description: "compile " + srcpath,
run: func(*compileJob) error {
var compileArgs []string
compileArgs = append(compileArgs, args...)
tmpfile, err := ioutil.TempFile(outdir, "crt1.o.tmp*")
if err != nil {
return err
}
tmpfile.Close()
compileArgs = append(compileArgs, "-o", tmpfile.Name(), srcpath)
err = runCCompiler(compileArgs...)
if err != nil {
return &commandError{"failed to build", srcpath, err}
}
return os.Rename(tmpfile.Name(), filepath.Join(outdir, "crt1.o"))
},
})
}
return job, nil return job, nil
} }
-162
View File
@@ -1,162 +0,0 @@
package builder
import (
"bytes"
"fmt"
"io/ioutil"
"os"
"path/filepath"
"regexp"
"strings"
"github.com/tinygo-org/tinygo/compileopts"
"github.com/tinygo-org/tinygo/goenv"
)
var Musl = Library{
name: "musl",
makeHeaders: func(target, includeDir string) error {
bits := filepath.Join(includeDir, "bits")
err := os.Mkdir(bits, 0777)
if err != nil {
return err
}
arch := compileopts.MuslArchitecture(target)
muslDir := filepath.Join(goenv.Get("TINYGOROOT"), "lib", "musl")
// Create the file alltypes.h.
f, err := os.Create(filepath.Join(bits, "alltypes.h"))
if err != nil {
return err
}
infiles := []string{
filepath.Join(muslDir, "arch", arch, "bits", "alltypes.h.in"),
filepath.Join(muslDir, "include", "alltypes.h.in"),
}
for _, infile := range infiles {
data, err := ioutil.ReadFile(infile)
if err != nil {
return err
}
lines := strings.Split(string(data), "\n")
for _, line := range lines {
if strings.HasPrefix(line, "TYPEDEF ") {
matches := regexp.MustCompile(`TYPEDEF (.*) ([^ ]*);`).FindStringSubmatch(line)
value := matches[1]
name := matches[2]
line = fmt.Sprintf("#if defined(__NEED_%s) && !defined(__DEFINED_%s)\ntypedef %s %s;\n#define __DEFINED_%s\n#endif\n", name, name, value, name, name)
}
if strings.HasPrefix(line, "STRUCT ") {
matches := regexp.MustCompile(`STRUCT * ([^ ]*) (.*);`).FindStringSubmatch(line)
name := matches[1]
value := matches[2]
line = fmt.Sprintf("#if defined(__NEED_struct_%s) && !defined(__DEFINED_struct_%s)\nstruct %s %s;\n#define __DEFINED_struct_%s\n#endif\n", name, name, name, value, name)
}
f.WriteString(line + "\n")
}
}
f.Close()
// Create the file syscall.h.
f, err = os.Create(filepath.Join(bits, "syscall.h"))
if err != nil {
return err
}
data, err := ioutil.ReadFile(filepath.Join(muslDir, "arch", arch, "bits", "syscall.h.in"))
if err != nil {
return err
}
_, err = f.Write(bytes.ReplaceAll(data, []byte("__NR_"), []byte("SYS_")))
if err != nil {
return err
}
f.Close()
return nil
},
cflags: func(target, headerPath string) []string {
arch := compileopts.MuslArchitecture(target)
muslDir := filepath.Join(goenv.Get("TINYGOROOT"), "lib/musl")
return []string{
"-std=c99", // same as in musl
"-D_XOPEN_SOURCE=700", // same as in musl
// Musl triggers some warnings and we don't want to show any
// warnings while compiling (only errors or silence), so disable
// specific warnings that are triggered in musl.
"-Werror",
"-Wno-logical-op-parentheses",
"-Wno-bitwise-op-parentheses",
"-Wno-shift-op-parentheses",
"-Wno-ignored-attributes",
"-Wno-string-plus-int",
"-Qunused-arguments",
// Select include dirs. Don't include standard library includes
// (that would introduce host dependencies and other complications),
// but do include all the include directories expected by musl.
"-nostdlibinc",
"-I" + muslDir + "/arch/" + arch,
"-I" + muslDir + "/arch/generic",
"-I" + muslDir + "/src/include",
"-I" + muslDir + "/src/internal",
"-I" + headerPath,
"-I" + muslDir + "/include",
}
},
sourceDir: "lib/musl/src",
librarySources: func(target string) []string {
arch := compileopts.MuslArchitecture(target)
globs := []string{
"env/*.c",
"errno/*.c",
"exit/*.c",
"internal/defsysinfo.c",
"internal/libc.c",
"internal/syscall_ret.c",
"internal/vdso.c",
"malloc/*.c",
"mman/*.c",
"signal/*.c",
"stdio/*.c",
"string/*.c",
"thread/" + arch + "/*.s",
"thread/" + arch + "/*.c",
"thread/*.c",
"time/*.c",
"unistd/*.c",
}
var sources []string
seenSources := map[string]struct{}{}
basepath := goenv.Get("TINYGOROOT") + "/lib/musl/src/"
for _, pattern := range globs {
matches, err := filepath.Glob(basepath + pattern)
if err != nil {
// From the documentation:
// > Glob ignores file system errors such as I/O errors reading
// > directories. The only possible returned error is
// > ErrBadPattern, when pattern is malformed.
// So the only possible error is when the (statically defined)
// pattern is wrong. In other words, a programming bug.
panic("could not glob source dirs: " + err.Error())
}
for _, match := range matches {
relpath, err := filepath.Rel(basepath, match)
if err != nil {
// Not sure if this is even possible.
panic(err)
}
// Make sure architecture specific files override generic files.
id := strings.ReplaceAll(relpath, "/"+arch+"/", "/")
if _, ok := seenSources[id]; ok {
// Already seen this file, skipping this (generic) file.
continue
}
seenSources[id] = struct{}{}
sources = append(sources, relpath)
}
}
return sources
},
crt1Source: "../crt/crt1.c", // lib/musl/crt/crt1.c
}
-27
View File
@@ -1,27 +0,0 @@
package builder
import (
"fmt"
"io/ioutil"
"os/exec"
"github.com/tinygo-org/tinygo/compileopts"
)
// https://infocenter.nordicsemi.com/index.jsp?topic=%2Fug_nrfutil%2FUG%2Fnrfutil%2Fnrfutil_intro.html
func makeDFUFirmwareImage(options *compileopts.Options, infile, outfile string) error {
cmdLine := []string{"nrfutil", "pkg", "generate", "--hw-version", "52", "--sd-req", "0x0", "--debug-mode", "--application", infile, outfile}
if options.PrintCommands != nil {
options.PrintCommands(cmdLine[0], cmdLine[1:]...)
}
cmd := exec.Command(cmdLine[0], cmdLine[1:]...)
cmd.Stdout = ioutil.Discard
err := cmd.Run()
if err != nil {
return fmt.Errorf("could not run nrfutil pkg generate: %w", err)
}
return nil
}
+3 -121
View File
@@ -1,7 +1,6 @@
package builder package builder
import ( import (
"os"
"path/filepath" "path/filepath"
"github.com/tinygo-org/tinygo/goenv" "github.com/tinygo-org/tinygo/goenv"
@@ -11,134 +10,17 @@ import (
// based on newlib. // based on newlib.
var Picolibc = Library{ var Picolibc = Library{
name: "picolibc", name: "picolibc",
makeHeaders: func(target, includeDir string) error { cflags: func() []string {
f, err := os.Create(filepath.Join(includeDir, "picolibc.h"))
if err != nil {
return err
}
return f.Close()
},
cflags: func(target, headerPath string) []string {
picolibcDir := filepath.Join(goenv.Get("TINYGOROOT"), "lib/picolibc/newlib/libc") picolibcDir := filepath.Join(goenv.Get("TINYGOROOT"), "lib/picolibc/newlib/libc")
return []string{ return []string{"-Werror", "-Wall", "-std=gnu11", "-D_COMPILING_NEWLIB", "-nostdlibinc", "-Xclang", "-internal-isystem", "-Xclang", picolibcDir + "/include", "-I" + picolibcDir + "/tinystdio", "-I" + goenv.Get("TINYGOROOT") + "/lib/picolibc-include"}
"-Werror",
"-Wall",
"-std=gnu11",
"-D_COMPILING_NEWLIB",
"-DHAVE_ALIAS_ATTRIBUTE",
"-DTINY_STDIO",
"-nostdlibinc",
"-Xclang", "-internal-isystem", "-Xclang", picolibcDir + "/include",
"-I" + picolibcDir + "/tinystdio",
"-I" + headerPath,
}
}, },
sourceDir: "lib/picolibc/newlib/libc", sourceDir: "lib/picolibc/newlib/libc",
librarySources: func(target string) []string { sources: func(target string) []string {
return picolibcSources return picolibcSources
}, },
} }
var picolibcSources = []string{ var picolibcSources = []string{
"../../../picolibc-stdio.c",
"tinystdio/asprintf.c",
"tinystdio/atod_engine.c",
"tinystdio/atod_ryu.c",
"tinystdio/atof_engine.c",
"tinystdio/atof_ryu.c",
//"tinystdio/atold_engine.c", // have_long_double and not long_double_equals_double
"tinystdio/clearerr.c",
"tinystdio/compare_exchange.c",
"tinystdio/dtoa_data.c",
"tinystdio/dtoa_engine.c",
"tinystdio/dtoa_ryu.c",
"tinystdio/ecvtbuf.c",
"tinystdio/ecvt.c",
"tinystdio/ecvt_data.c",
"tinystdio/ecvtfbuf.c",
"tinystdio/ecvtf.c",
"tinystdio/ecvtf_data.c",
"tinystdio/exchange.c",
//"tinystdio/fclose.c", // posix-io
"tinystdio/fcvtbuf.c",
"tinystdio/fcvt.c",
"tinystdio/fcvtfbuf.c",
"tinystdio/fcvtf.c",
"tinystdio/fdevopen.c",
//"tinystdio/fdopen.c", // posix-io
"tinystdio/feof.c",
"tinystdio/ferror.c",
"tinystdio/fflush.c",
"tinystdio/fgetc.c",
"tinystdio/fgets.c",
"tinystdio/fileno.c",
"tinystdio/filestrget.c",
"tinystdio/filestrputalloc.c",
"tinystdio/filestrput.c",
//"tinystdio/fopen.c", // posix-io
"tinystdio/fprintf.c",
"tinystdio/fputc.c",
"tinystdio/fputs.c",
"tinystdio/fread.c",
"tinystdio/fscanf.c",
"tinystdio/fseek.c",
"tinystdio/ftell.c",
"tinystdio/ftoa_data.c",
"tinystdio/ftoa_engine.c",
"tinystdio/ftoa_ryu.c",
"tinystdio/fwrite.c",
"tinystdio/gcvtbuf.c",
"tinystdio/gcvt.c",
"tinystdio/gcvtfbuf.c",
"tinystdio/gcvtf.c",
"tinystdio/getchar.c",
"tinystdio/gets.c",
"tinystdio/matchcaseprefix.c",
"tinystdio/perror.c",
//"tinystdio/posixiob.c", // posix-io
//"tinystdio/posixio.c", // posix-io
"tinystdio/printf.c",
"tinystdio/putchar.c",
"tinystdio/puts.c",
"tinystdio/ryu_divpow2.c",
"tinystdio/ryu_log10.c",
"tinystdio/ryu_log2pow5.c",
"tinystdio/ryu_pow5bits.c",
"tinystdio/ryu_table.c",
"tinystdio/ryu_umul128.c",
"tinystdio/scanf.c",
"tinystdio/setbuf.c",
"tinystdio/setvbuf.c",
//"tinystdio/sflags.c", // posix-io
"tinystdio/snprintf.c",
"tinystdio/snprintfd.c",
"tinystdio/snprintff.c",
"tinystdio/sprintf.c",
"tinystdio/sprintfd.c",
"tinystdio/sprintff.c",
"tinystdio/sscanf.c",
"tinystdio/strfromd.c",
"tinystdio/strfromf.c",
"tinystdio/strtod.c",
"tinystdio/strtod_l.c",
"tinystdio/strtof.c",
//"tinystdio/strtold.c", // have_long_double and not long_double_equals_double
//"tinystdio/strtold_l.c", // have_long_double and not long_double_equals_double
"tinystdio/ungetc.c",
"tinystdio/vasprintf.c",
"tinystdio/vfiprintf.c",
"tinystdio/vfiscanf.c",
"tinystdio/vfprintf.c",
"tinystdio/vfprintff.c",
"tinystdio/vfscanf.c",
"tinystdio/vfscanff.c",
"tinystdio/vprintf.c",
"tinystdio/vscanf.c",
"tinystdio/vsnprintf.c",
"tinystdio/vsprintf.c",
"tinystdio/vsscanf.c",
"string/bcmp.c", "string/bcmp.c",
"string/bcopy.c", "string/bcopy.c",
"string/bzero.c", "string/bzero.c",
+103 -518
View File
@@ -1,30 +1,16 @@
package builder package builder
import ( import (
"bytes"
"debug/dwarf"
"debug/elf" "debug/elf"
"encoding/binary"
"fmt"
"io"
"os"
"path/filepath"
"regexp"
"sort" "sort"
"strings" "strings"
"github.com/aykevl/go-wasm"
"github.com/tinygo-org/tinygo/goenv"
) )
// Set to true to print extra debug logs.
const sizesDebug = false
// programSize contains size statistics per package of a compiled program. // programSize contains size statistics per package of a compiled program.
type programSize struct { type programSize struct {
Packages map[string]packageSize Packages map[string]*packageSize
Sum *packageSize
Code uint64 Code uint64
ROData uint64
Data uint64 Data uint64
BSS uint64 BSS uint64
} }
@@ -40,16 +26,6 @@ func (ps *programSize) sortedPackageNames() []string {
return names return names
} }
// Flash usage in regular microcontrollers.
func (ps *programSize) Flash() uint64 {
return ps.Code + ps.ROData + ps.Data
}
// Static RAM usage in regular microcontrollers.
func (ps *programSize) RAM() uint64 {
return ps.Data + ps.BSS
}
// packageSize contains the size of a package, calculated from the linked object // packageSize contains the size of a package, calculated from the linked object
// file. // file.
type packageSize struct { type packageSize struct {
@@ -69,520 +45,129 @@ func (ps *packageSize) RAM() uint64 {
return ps.Data + ps.BSS return ps.Data + ps.BSS
} }
// A mapping of a single chunk of code or data to a file path. type symbolList []elf.Symbol
type addressLine struct {
Address uint64 func (l symbolList) Len() int {
Length uint64 // length of this chunk return len(l)
File string // file path as stored in DWARF
IsVariable bool // true if this is a variable (or constant), false if it is code
} }
// Sections defined in the input file. This struct defines them in a func (l symbolList) Less(i, j int) bool {
// filetype-agnostic way but roughly follow the ELF types (.text, .data, .bss, bind_i := elf.ST_BIND(l[i].Info)
// etc). bind_j := elf.ST_BIND(l[j].Info)
type memorySection struct { if l[i].Value == l[j].Value && bind_i != elf.STB_WEAK && bind_j == elf.STB_WEAK {
Type memoryType // sort weak symbols after non-weak symbols
Address uint64 return true
Size uint64
}
type memoryType int
const (
memoryCode memoryType = iota + 1
memoryData
memoryROData
memoryBSS
memoryStack
)
// Regular expressions to match particular symbol names. These are not stored as
// DWARF variables because they have no mapping to source code global variables.
var (
// Various globals that aren't a variable but nonetheless need to be stored
// somewhere:
// alloc: heap allocations during init interpretation
// pack: data created when storing a constant in an interface for example
// string: buffer behind strings
packageSymbolRegexp = regexp.MustCompile(`\$(alloc|pack|string)(\.[0-9]+)?$`)
// Reflect sidetables. Created by the reflect lowering pass.
// See src/reflect/sidetables.go.
reflectDataRegexp = regexp.MustCompile(`^reflect\.[a-zA-Z]+Sidetable$`)
)
// readProgramSizeFromDWARF reads the source location for each line of code and
// each variable in the program, as far as this is stored in the DWARF debug
// information.
func readProgramSizeFromDWARF(data *dwarf.Data, codeOffset uint64) ([]addressLine, error) {
r := data.Reader()
var lines []*dwarf.LineFile
var addresses []addressLine
for {
e, err := r.Next()
if err != nil {
return nil, err
}
if e == nil {
break
}
switch e.Tag {
case dwarf.TagCompileUnit:
// Found a compile unit.
// We can read the .debug_line section using it, which contains a
// mapping for most instructions to their file/line/column - even
// for inlined functions!
lr, err := data.LineReader(e)
if err != nil {
return nil, err
}
lines = lr.Files()
var lineEntry = dwarf.LineEntry{
EndSequence: true,
}
// Line tables are organized as sequences of line entries until an
// end sequence. A single line table can contain multiple such
// sequences. The last line entry is an EndSequence to indicate the
// end.
for {
// Read the next .debug_line entry.
prevLineEntry := lineEntry
err := lr.Next(&lineEntry)
if err != nil {
if err == io.EOF {
break
}
return nil, err
}
if prevLineEntry.EndSequence && lineEntry.Address == 0 {
// Tombstone value. This symbol has been removed, for
// example by the --gc-sections linker flag. It is still
// here in the debug information because the linker can't
// just remove this reference.
// Read until the next EndSequence so that this sequence is
// skipped.
// For more details, see (among others):
// https://reviews.llvm.org/D84825
for {
err := lr.Next(&lineEntry)
if err != nil {
return nil, err
}
if lineEntry.EndSequence {
break
}
}
}
if !prevLineEntry.EndSequence {
// The chunk describes the code from prevLineEntry to
// lineEntry.
line := addressLine{
Address: prevLineEntry.Address + codeOffset,
Length: lineEntry.Address - prevLineEntry.Address,
File: prevLineEntry.File.Name,
}
if line.Length != 0 {
addresses = append(addresses, line)
}
}
}
case dwarf.TagVariable:
// Global variable (or constant). Most of these are not actually
// stored in the binary, because they have been optimized out. Only
// the ones with a location are still present.
r.SkipChildren()
file := e.AttrField(dwarf.AttrDeclFile)
location := e.AttrField(dwarf.AttrLocation)
globalType := e.AttrField(dwarf.AttrType)
if file == nil || location == nil || globalType == nil {
// Doesn't contain the requested information.
continue
}
// Try to parse the location. While this could in theory be a very
// complex expression, usually it's just a DW_OP_addr opcode
// followed by an address.
locationCode := location.Val.([]uint8)
if locationCode[0] != 3 { // DW_OP_addr
continue
}
var addr uint64
switch len(locationCode) {
case 1 + 2:
addr = uint64(binary.LittleEndian.Uint16(locationCode[1:]))
case 1 + 4:
addr = uint64(binary.LittleEndian.Uint32(locationCode[1:]))
case 1 + 8:
addr = binary.LittleEndian.Uint64(locationCode[1:])
default:
continue // unknown address
}
// Parse the type of the global variable, which (importantly)
// contains the variable size. We're not interested in the type,
// only in the size.
typ, err := data.Type(globalType.Val.(dwarf.Offset))
if err != nil {
return nil, err
}
addresses = append(addresses, addressLine{
Address: addr,
Length: uint64(typ.Size()),
File: lines[file.Val.(int64)].Name,
IsVariable: true,
})
default:
r.SkipChildren()
}
} }
return addresses, nil return l[i].Value < l[j].Value
}
func (l symbolList) Swap(i, j int) {
l[i], l[j] = l[j], l[i]
} }
// loadProgramSize calculate a program/data size breakdown of each package for a // loadProgramSize calculate a program/data size breakdown of each package for a
// given ELF file. // given ELF file.
// If the file doesn't contain DWARF debug information, the returned program func loadProgramSize(path string) (*programSize, error) {
// size will still have valid summaries but won't have complete size information file, err := elf.Open(path)
// per package.
func loadProgramSize(path string, packagePathMap map[string]string) (*programSize, error) {
// Open the binary file.
f, err := os.Open(path)
if err != nil { if err != nil {
return nil, err return nil, err
} }
defer f.Close() defer file.Close()
// This stores all chunks of addresses found in the binary. var sumCode uint64
var addresses []addressLine var sumData uint64
var sumBSS uint64
// Load the binary file, which could be in a number of file formats. for _, section := range file.Sections {
var sections []memorySection if section.Flags&elf.SHF_ALLOC == 0 {
if file, err := elf.NewFile(f); err == nil { continue
// Read DWARF information. The error is intentionally ignored.
data, _ := file.DWARF()
if data != nil {
addresses, err = readProgramSizeFromDWARF(data, 0)
if err != nil {
// However, _do_ report an error here. Something must have gone
// wrong while trying to parse DWARF data.
return nil, err
}
} }
if section.Type != elf.SHT_PROGBITS && section.Type != elf.SHT_NOBITS {
// Read the ELF symbols for some more chunks of location information. continue
// Some globals (such as strings) aren't stored in the DWARF debug
// information and therefore need to be obtained in a different way.
allSymbols, err := file.Symbols()
if err != nil {
return nil, err
} }
for _, symbol := range allSymbols { if section.Name == ".stack" {
symType := elf.ST_TYPE(symbol.Info) // HACK: this works around a bug in ld.lld from LLVM 10. The linker
if symbol.Size == 0 { // marks sections with no input symbols (such as is the case for the
continue // .stack section) as SHT_PROGBITS instead of SHT_NOBITS. While it
} // doesn't affect the generated binaries (.hex and .bin), it does
if symType != elf.STT_FUNC && symType != elf.STT_OBJECT && symType != elf.STT_NOTYPE { // affect the reported size.
continue // https://bugs.llvm.org/show_bug.cgi?id=45336
} // https://reviews.llvm.org/D76981
section := file.Sections[symbol.Section] // It has been merged in master, but it has not (yet) been
if section.Flags&elf.SHF_ALLOC == 0 { // backported to the LLVM 10 release branch.
continue sumBSS += section.Size
} } else if section.Type == elf.SHT_NOBITS {
if packageSymbolRegexp.MatchString(symbol.Name) || reflectDataRegexp.MatchString(symbol.Name) { sumBSS += section.Size
addresses = append(addresses, addressLine{ } else if section.Flags&elf.SHF_EXECINSTR != 0 {
Address: symbol.Value, sumCode += section.Size
Length: symbol.Size, } else if section.Flags&elf.SHF_WRITE != 0 {
File: symbol.Name, sumData += section.Size
IsVariable: true,
})
}
} }
}
// Load allocated sections. allSymbols, err := file.Symbols()
for _, section := range file.Sections { if err != nil {
if section.Flags&elf.SHF_ALLOC == 0 { return nil, err
continue }
} symbols := make([]elf.Symbol, 0, len(allSymbols))
if section.Type == elf.SHT_NOBITS { for _, symbol := range allSymbols {
if section.Name == ".stack" { symType := elf.ST_TYPE(symbol.Info)
// TinyGo emits stack sections on microcontroller using the if symbol.Size == 0 {
// ".stack" name. continue
// This is a bit ugly, but I don't think there is a way to }
// mark the stack section in a linker script. if symType != elf.STT_FUNC && symType != elf.STT_OBJECT && symType != elf.STT_NOTYPE {
sections = append(sections, memorySection{ continue
Address: section.Addr, }
Size: section.Size, if symbol.Section >= elf.SectionIndex(len(file.Sections)) {
Type: memoryStack, continue
}) }
section := file.Sections[symbol.Section]
if section.Flags&elf.SHF_ALLOC == 0 {
continue
}
symbols = append(symbols, symbol)
}
sort.Sort(symbolList(symbols))
sizes := map[string]*packageSize{}
var lastSymbolValue uint64
for _, symbol := range symbols {
symType := elf.ST_TYPE(symbol.Info)
//bind := elf.ST_BIND(symbol.Info)
section := file.Sections[symbol.Section]
pkgName := "(bootstrap)"
symName := strings.TrimLeft(symbol.Name, "(*")
dot := strings.IndexByte(symName, '.')
if dot > 0 {
pkgName = symName[:dot]
}
pkgSize := sizes[pkgName]
if pkgSize == nil {
pkgSize = &packageSize{}
sizes[pkgName] = pkgSize
}
if lastSymbolValue != symbol.Value || lastSymbolValue == 0 {
if symType == elf.STT_FUNC {
pkgSize.Code += symbol.Size
} else if section.Flags&elf.SHF_WRITE != 0 {
if section.Type == elf.SHT_NOBITS {
pkgSize.BSS += symbol.Size
} else { } else {
// Regular .bss section. pkgSize.Data += symbol.Size
sections = append(sections, memorySection{
Address: section.Addr,
Size: section.Size,
Type: memoryBSS,
})
} }
} else if section.Type == elf.SHT_PROGBITS && section.Flags&elf.SHF_EXECINSTR != 0 { } else {
// .text pkgSize.ROData += symbol.Size
sections = append(sections, memorySection{
Address: section.Addr,
Size: section.Size,
Type: memoryCode,
})
} else if section.Type == elf.SHT_PROGBITS && section.Flags&elf.SHF_WRITE != 0 {
// .data
sections = append(sections, memorySection{
Address: section.Addr,
Size: section.Size,
Type: memoryData,
})
} else if section.Type == elf.SHT_PROGBITS {
// .rodata
sections = append(sections, memorySection{
Address: section.Addr,
Size: section.Size,
Type: memoryROData,
})
} }
} }
} else if file, err := wasm.Parse(f); err == nil { lastSymbolValue = symbol.Value
// File is in WebAssembly format.
// Put code at a very high address, so that it won't conflict with the
// data in the memory section.
const codeOffset = 0x8000_0000_0000_0000
// Read DWARF information. The error is intentionally ignored.
data, err := file.DWARF()
if data != nil {
addresses, err = readProgramSizeFromDWARF(data, codeOffset)
if err != nil {
// However, _do_ report an error here. Something must have gone
// wrong while trying to parse DWARF data.
return nil, err
}
}
var linearMemorySize uint64
for _, section := range file.Sections {
switch section := section.(type) {
case *wasm.SectionCode:
sections = append(sections, memorySection{
Address: codeOffset,
Size: uint64(section.Size()),
Type: memoryCode,
})
case *wasm.SectionMemory:
// This value is used when processing *wasm.SectionData (which
// always comes after *wasm.SectionMemory).
linearMemorySize = uint64(section.Entries[0].Limits.Initial) * 64 * 1024
case *wasm.SectionData:
// Data sections contain initial values for linear memory.
// First load the list of data sections, and sort them by
// address for easier processing.
var dataSections []memorySection
for _, entry := range section.Entries {
address, err := wasm.Eval(bytes.NewBuffer(entry.Offset))
if err != nil {
return nil, fmt.Errorf("could not parse data section address: %w", err)
}
dataSections = append(dataSections, memorySection{
Address: uint64(address[0].(int32)),
Size: uint64(len(entry.Data)),
Type: memoryData,
})
}
sort.Slice(dataSections, func(i, j int) bool {
return dataSections[i].Address < dataSections[j].Address
})
// And now add all data sections for linear memory.
// Parts that are in the slice of data sections are added as
// memoryData, and parts that are not are added as memoryBSS.
addr := uint64(0)
for _, section := range dataSections {
if addr < section.Address {
sections = append(sections, memorySection{
Address: addr,
Size: section.Address - addr,
Type: memoryBSS,
})
}
if addr > section.Address {
// This might be allowed, I'm not sure.
// It certainly doesn't make a lot of sense.
return nil, fmt.Errorf("overlapping data section")
}
// addr == section.Address
sections = append(sections, section)
addr = section.Address + section.Size
}
if addr < linearMemorySize {
sections = append(sections, memorySection{
Address: addr,
Size: linearMemorySize - addr,
Type: memoryBSS,
})
}
}
}
} else {
return nil, fmt.Errorf("could not parse file: %w", err)
} }
// Sort the slice of address chunks by address, so that we can iterate sum := &packageSize{}
// through it to calculate section sizes.
sort.Slice(addresses, func(i, j int) bool {
if addresses[i].Address == addresses[j].Address {
// Very rarely, there might be duplicate addresses.
// If that happens, sort the largest chunks first.
return addresses[i].Length > addresses[j].Length
}
return addresses[i].Address < addresses[j].Address
})
// Now finally determine the binary/RAM size usage per package by going
// through each allocated section.
sizes := make(map[string]packageSize)
for _, section := range sections {
switch section.Type {
case memoryCode:
readSection(section, addresses, func(path string, size uint64, isVariable bool) {
field := sizes[path]
if isVariable {
field.ROData += size
} else {
field.Code += size
}
sizes[path] = field
}, packagePathMap)
case memoryROData:
readSection(section, addresses, func(path string, size uint64, isVariable bool) {
field := sizes[path]
field.ROData += size
sizes[path] = field
}, packagePathMap)
case memoryData:
readSection(section, addresses, func(path string, size uint64, isVariable bool) {
field := sizes[path]
field.Data += size
sizes[path] = field
}, packagePathMap)
case memoryBSS:
readSection(section, addresses, func(path string, size uint64, isVariable bool) {
field := sizes[path]
field.BSS += size
sizes[path] = field
}, packagePathMap)
case memoryStack:
// We store the C stack as a pseudo-package.
sizes["C stack"] = packageSize{
BSS: section.Size,
}
}
}
// ...and summarize the results.
program := &programSize{
Packages: sizes,
}
for _, pkg := range sizes { for _, pkg := range sizes {
program.Code += pkg.Code sum.Code += pkg.Code
program.ROData += pkg.ROData sum.ROData += pkg.ROData
program.Data += pkg.Data sum.Data += pkg.Data
program.BSS += pkg.BSS sum.BSS += pkg.BSS
} }
return program, nil
}
// readSection determines for each byte in this section to which package it return &programSize{Packages: sizes, Code: sumCode, Data: sumData, BSS: sumBSS, Sum: sum}, nil
// belongs. It reports this usage through the addSize callback.
func readSection(section memorySection, addresses []addressLine, addSize func(string, uint64, bool), packagePathMap map[string]string) {
// The addr variable tracks at which address we are while going through this
// section. We start at the beginning.
addr := section.Address
sectionEnd := section.Address + section.Size
for _, line := range addresses {
if line.Address < section.Address || line.Address+line.Length >= sectionEnd {
// Check that this line is entirely within the section.
// Don't bother dealing with line entries that cross sections (that
// seems rather unlikely anyway).
continue
}
if addr < line.Address {
// There is a gap: there is a space between the current and the
// previous line entry.
addSize("(unknown)", line.Address-addr, false)
if sizesDebug {
fmt.Printf("%08x..%08x %4d: unknown (gap)\n", addr, line.Address, line.Address-addr)
}
}
if addr > line.Address+line.Length {
// The current line is already covered by a previous line entry.
// Simply skip it.
continue
}
// At this point, addr falls within the current line (probably at the
// start).
length := line.Length
if addr > line.Address {
// There is some overlap: the previous line entry already covered
// part of this line entry. So reduce the length to add to the
// remaining bit of the line entry.
length = line.Length - (addr - line.Address)
}
// Finally, mark this chunk of memory as used by the given package.
addSize(findPackagePath(line.File, packagePathMap), length, line.IsVariable)
addr = line.Address + line.Length
}
if addr < sectionEnd {
// There is a gap at the end of the section.
addSize("(unknown)", sectionEnd-addr, false)
if sizesDebug {
fmt.Printf("%08x..%08x %4d: unknown (end)\n", addr, sectionEnd, sectionEnd-addr)
}
}
}
// findPackagePath returns the Go package (or a pseudo package) for the given
// path. It uses some heuristics, for example for some C libraries.
func findPackagePath(path string, packagePathMap map[string]string) string {
// Check whether this path is part of one of the compiled packages.
packagePath, ok := packagePathMap[filepath.Dir(path)]
if !ok {
if strings.HasPrefix(path, filepath.Join(goenv.Get("TINYGOROOT"), "lib")) {
// Emit C libraries (in the lib subdirectory of TinyGo) as a single
// package, with a "C" prefix. For example: "C compiler-rt" for the
// compiler runtime library from LLVM.
packagePath = "C " + strings.Split(strings.TrimPrefix(path, filepath.Join(goenv.Get("TINYGOROOT"), "lib")), string(os.PathSeparator))[1]
} else if packageSymbolRegexp.MatchString(path) {
// Parse symbol names like main$alloc or runtime$string.
packagePath = path[:strings.LastIndex(path, "$")]
} else if reflectDataRegexp.MatchString(path) {
// Parse symbol names like reflect.structTypesSidetable.
packagePath = "Go reflect data"
} else if path == "<Go interface assert>" {
// Interface type assert, generated by the interface lowering pass.
packagePath = "Go interface assert"
} else if path == "<Go interface method>" {
// Interface method wrapper (switch over all concrete types),
// generated by the interface lowering pass.
packagePath = "Go interface method"
} else if path == "<stdin>" {
// This can happen when the source code (in Go) doesn't have a
// source file and uses "-" as the location. Somewhere this is
// converted to "<stdin>".
// Convert this back to the "-" string. Eventually, this should be
// fixed in the compiler.
packagePath = "-"
} else {
// This is some other path. Not sure what it is, so just emit its directory.
packagePath = filepath.Dir(path) // fallback
}
}
return packagePath
} }
+15 -6
View File
@@ -9,8 +9,8 @@ import (
) )
// runCCompiler invokes a C compiler with the given arguments. // runCCompiler invokes a C compiler with the given arguments.
func runCCompiler(flags ...string) error { func runCCompiler(command string, flags ...string) error {
if hasBuiltinTools { if hasBuiltinTools && command == "clang" {
// Compile this with the internal Clang compiler. // Compile this with the internal Clang compiler.
headerPath := getClangHeaderPath(goenv.Get("TINYGOROOT")) headerPath := getClangHeaderPath(goenv.Get("TINYGOROOT"))
if headerPath == "" { if headerPath == "" {
@@ -23,8 +23,17 @@ func runCCompiler(flags ...string) error {
return cmd.Run() return cmd.Run()
} }
// Compile this with an external invocation of the Clang compiler. // Running some other compiler. Maybe it has been defined in the
return execCommand("clang", flags...) // commands map (unlikely).
if cmdNames, ok := commands[command]; ok {
return execCommand(cmdNames, flags...)
}
// Alternatively, run the compiler directly.
cmd := exec.Command(command, flags...)
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
return cmd.Run()
} }
// link invokes a linker with the given name and flags. // link invokes a linker with the given name and flags.
@@ -38,8 +47,8 @@ func link(linker string, flags ...string) error {
} }
// Fall back to external command. // Fall back to external command.
if _, ok := commands[linker]; ok { if cmdNames, ok := commands[linker]; ok {
return execCommand(linker, flags...) return execCommand(cmdNames, flags...)
} }
cmd := exec.Command(linker, flags...) cmd := exec.Command(linker, flags...)
+1 -3
View File
@@ -141,13 +141,11 @@ func split(input []byte, limit int) [][]byte {
var block []byte var block []byte
output := make([][]byte, 0, len(input)/limit+1) output := make([][]byte, 0, len(input)/limit+1)
for len(input) >= limit { for len(input) >= limit {
// add all blocks
block, input = input[:limit], input[limit:] block, input = input[:limit], input[limit:]
output = append(output, block) output = append(output, block)
} }
if len(input) > 0 { if len(input) > 0 {
// add remaining block (that isn't full sized) output = append(output, input[:len(input)])
output = append(output, input)
} }
return output return output
} }
+148 -168
View File
@@ -25,19 +25,12 @@ import (
"golang.org/x/tools/go/ast/astutil" "golang.org/x/tools/go/ast/astutil"
) )
// Version of the cgo package. It must be incremented whenever the cgo package
// is changed in a way that affects the output so that cached package builds
// will be invalidated.
// This version is independent of the TinyGo version number.
const Version = 1 // last change: run libclang once per Go file
// cgoPackage holds all CGo-related information of a package. // cgoPackage holds all CGo-related information of a package.
type cgoPackage struct { type cgoPackage struct {
generated *ast.File generated *ast.File
generatedPos token.Pos generatedPos token.Pos
errors []error errors []error
currentDir string // current working directory dir string
packageDir string // full path to the package to process
fset *token.FileSet fset *token.FileSet
tokenFiles map[string]*token.File tokenFiles map[string]*token.File
missingSymbols map[string]struct{} missingSymbols map[string]struct{}
@@ -131,17 +124,17 @@ var cgoAliases = map[string]string{
// builtinAliases are handled specially because they only exist on the Go side // builtinAliases are handled specially because they only exist on the Go side
// of CGo, not on the CGo side (they're prefixed with "_Cgo_" there). // of CGo, not on the CGo side (they're prefixed with "_Cgo_" there).
var builtinAliases = map[string]struct{}{ var builtinAliases = map[string]struct{}{
"char": {}, "char": struct{}{},
"schar": {}, "schar": struct{}{},
"uchar": {}, "uchar": struct{}{},
"short": {}, "short": struct{}{},
"ushort": {}, "ushort": struct{}{},
"int": {}, "int": struct{}{},
"uint": {}, "uint": struct{}{},
"long": {}, "long": struct{}{},
"ulong": {}, "ulong": struct{}{},
"longlong": {}, "longlong": struct{}{},
"ulonglong": {}, "ulonglong": struct{}{},
} }
// cgoTypes lists some C types with ambiguous sizes that must be retrieved // cgoTypes lists some C types with ambiguous sizes that must be retrieved
@@ -165,13 +158,12 @@ typedef unsigned long long _Cgo_ulonglong;
// Process extracts `import "C"` statements from the AST, parses the comment // Process extracts `import "C"` statements from the AST, parses the comment
// with libclang, and modifies the AST to use this information. It returns a // with libclang, and modifies the AST to use this information. It returns a
// 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, the CGo header snippets that should be compiled (for inline // files, the CFLAGS and LDFLAGS found in #cgo lines, and a map of file hashes
// functions), the CFLAGS and LDFLAGS found in #cgo lines, and a map of file // of the accessed C header files. If there is one or more error, it returns
// hashes of the accessed C header files. If there is one or more error, it // these in the []error slice but still modifies the AST.
// returns these in the []error slice but still modifies the AST. func Process(files []*ast.File, dir string, fset *token.FileSet, cflags []string) (*ast.File, []string, []string, map[string][]byte, []error) {
func Process(files []*ast.File, dir string, fset *token.FileSet, cflags []string) (*ast.File, []string, []string, []string, map[string][]byte, []error) {
p := &cgoPackage{ p := &cgoPackage{
currentDir: dir, dir: dir,
fset: fset, fset: fset,
tokenFiles: map[string]*token.File{}, tokenFiles: map[string]*token.File{},
missingSymbols: map[string]struct{}{}, missingSymbols: map[string]struct{}{},
@@ -192,14 +184,14 @@ 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, nil, nil, nil, []error{ return nil, nil, nil, 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
}, },
} }
} }
p.packageDir = filepath.Dir(packagePath) packagePath = filepath.Dir(packagePath)
// Construct a new in-memory AST for CGo declarations of this package. // Construct a new in-memory AST for CGo declarations of this package.
unsafeImport := &ast.ImportSpec{ unsafeImport := &ast.ImportSpec{
@@ -232,7 +224,7 @@ func Process(files []*ast.File, dir string, fset *token.FileSet, cflags []string
Specs: []ast.Spec{ Specs: []ast.Spec{
&ast.ValueSpec{ &ast.ValueSpec{
Names: []*ast.Ident{ Names: []*ast.Ident{
{ &ast.Ident{
Name: "_", Name: "_",
Obj: &ast.Object{ Obj: &ast.Object{
Kind: ast.Var, Kind: ast.Var,
@@ -263,10 +255,9 @@ func Process(files []*ast.File, dir string, fset *token.FileSet, cflags []string
p.missingSymbols["_Cgo_"+name] = struct{}{} p.missingSymbols["_Cgo_"+name] = struct{}{}
} }
// Find `import "C"` C fragments in the file. // Find `import "C"` statements in the file.
cgoHeaders := make([]string, len(files)) // combined CGo header fragment for each file var statements []*ast.GenDecl
for i, f := range files { for _, f := range files {
var cgoHeader string
for i := 0; i < len(f.Decls); i++ { for i := 0; i < len(f.Decls); i++ {
decl := f.Decls[i] decl := f.Decls[i]
genDecl, ok := decl.(*ast.GenDecl) genDecl, ok := decl.(*ast.GenDecl)
@@ -290,40 +281,105 @@ func Process(files []*ast.File, dir string, fset *token.FileSet, cflags []string
continue continue
} }
// Found a CGo statement.
statements = append(statements, genDecl)
// Remove this import declaration. // Remove this import declaration.
f.Decls = append(f.Decls[:i], f.Decls[i+1:]...) f.Decls = append(f.Decls[:i], f.Decls[i+1:]...)
i-- i--
if genDecl.Doc == nil {
continue
}
// Iterate through all parts of the CGo header. Note that every //
// line is a new comment.
position := fset.Position(genDecl.Doc.Pos())
fragment := fmt.Sprintf("# %d %#v\n", position.Line, position.Filename)
for _, comment := range genDecl.Doc.List {
// Find all #cgo lines, extract and use their contents, and
// replace the lines with spaces (to preserve locations).
c := p.parseCGoPreprocessorLines(comment.Text, comment.Slash)
// Change the comment (which is still in Go syntax, with // and
// /* */ ) to a regular string by replacing the start/end
// markers of comments with spaces.
// It is similar to the Text() method but differs in that it
// doesn't strip anything and tries to keep all offsets correct
// by adding spaces and newlines where necessary.
if c[1] == '/' { /* comment */
c = " " + c[2:]
} else { // comment
c = " " + c[2:len(c)-2]
}
fragment += c + "\n"
}
cgoHeader += fragment
} }
cgoHeaders[i] = cgoHeader // Print the AST, for debugging.
//ast.Print(fset, f)
}
// Find all #cgo lines.
for _, genDecl := range statements {
if genDecl.Doc == nil {
continue
}
for _, comment := range genDecl.Doc.List {
for {
// Extract the #cgo line, and replace it with spaces.
// Replacing with spaces makes sure that error locations are
// still correct, while not interfering with parsing in any way.
lineStart := strings.Index(comment.Text, "#cgo ")
if lineStart < 0 {
break
}
lineLen := strings.IndexByte(comment.Text[lineStart:], '\n')
if lineLen < 0 {
lineLen = len(comment.Text) - lineStart
}
lineEnd := lineStart + lineLen
line := comment.Text[lineStart:lineEnd]
spaces := make([]byte, len(line))
for i := range spaces {
spaces[i] = ' '
}
lenBefore := len(comment.Text)
comment.Text = comment.Text[:lineStart] + string(spaces) + comment.Text[lineEnd:]
if len(comment.Text) != lenBefore {
println(lenBefore, len(comment.Text))
panic("length of preamble changed!")
}
// Get the text before the colon in the #cgo directive.
colon := strings.IndexByte(line, ':')
if colon < 0 {
p.addErrorAfter(comment.Slash, comment.Text[:lineStart], "missing colon in #cgo line")
continue
}
// Extract the fields before the colon. These fields are a list
// of build tags and the C environment variable.
fields := strings.Fields(line[4:colon])
if len(fields) == 0 {
p.addErrorAfter(comment.Slash, comment.Text[:lineStart+colon-1], "invalid #cgo line")
continue
}
if len(fields) > 1 {
p.addErrorAfter(comment.Slash, comment.Text[:lineStart+5], "not implemented: build constraints in #cgo line")
continue
}
name := fields[len(fields)-1]
value := line[colon+1:]
switch name {
case "CFLAGS":
flags, err := shlex.Split(value)
if err != nil {
// TODO: find the exact location where the error happened.
p.addErrorAfter(comment.Slash, comment.Text[:lineStart+colon+1], "failed to parse flags in #cgo line: "+err.Error())
continue
}
if err := checkCompilerFlags(name, flags); err != nil {
p.addErrorAfter(comment.Slash, comment.Text[:lineStart+colon+1], err.Error())
continue
}
makePathsAbsolute(flags, packagePath)
p.cflags = append(p.cflags, flags...)
case "LDFLAGS":
flags, err := shlex.Split(value)
if err != nil {
// TODO: find the exact location where the error happened.
p.addErrorAfter(comment.Slash, comment.Text[:lineStart+colon+1], "failed to parse flags in #cgo line: "+err.Error())
continue
}
if err := checkLinkerFlags(name, flags); err != nil {
p.addErrorAfter(comment.Slash, comment.Text[:lineStart+colon+1], err.Error())
continue
}
makePathsAbsolute(flags, packagePath)
p.ldflags = append(p.ldflags, flags...)
default:
startPos := strings.LastIndex(line[4:colon], name) + 4
p.addErrorAfter(comment.Slash, comment.Text[:lineStart+startPos], "invalid #cgo line: "+name)
continue
}
}
}
} }
// Define CFlags that will be used while parsing the package. // Define CFlags that will be used while parsing the package.
@@ -333,9 +389,16 @@ func Process(files []*ast.File, dir string, fset *token.FileSet, cflags []string
cflagsForCGo := append([]string{"-D_FORTIFY_SOURCE=0"}, cflags...) cflagsForCGo := append([]string{"-D_FORTIFY_SOURCE=0"}, cflags...)
cflagsForCGo = append(cflagsForCGo, p.cflags...) cflagsForCGo = append(cflagsForCGo, p.cflags...)
// Process CGo imports for each file. // Process all CGo imports.
for i, f := range files { for _, genDecl := range statements {
p.parseFragment(cgoHeaders[i]+cgoTypes, cflagsForCGo, filepath.Base(fset.File(f.Pos()).Name())) cgoComment := genDecl.Doc.Text()
pos := genDecl.Pos()
if genDecl.Doc != nil {
pos = genDecl.Doc.Pos()
}
position := fset.PositionFor(pos, true)
p.parseFragment(cgoComment+cgoTypes, cflagsForCGo, position.Filename, position.Line)
} }
// Declare functions found by libclang. // Declare functions found by libclang.
@@ -370,18 +433,18 @@ 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, cgoHeaders, p.cflags, p.ldflags, p.visitedFiles, p.errors return p.generated, p.cflags, p.ldflags, p.visitedFiles, p.errors
} }
// makePathsAbsolute converts some common path compiler flags (-I, -L) from // makePathsAbsolute converts some common path compiler flags (-I, -L) from
// relative flags into absolute flags, if they are relative. This is necessary // relative flags into absolute flags, if they are relative. This is necessary
// because the C compiler is usually not invoked from the package path. // because the C compiler is usually not invoked from the package path.
func (p *cgoPackage) makePathsAbsolute(args []string) { func makePathsAbsolute(args []string, packagePath string) {
nextIsPath := false nextIsPath := false
for i, arg := range args { for i, arg := range args {
if nextIsPath { if nextIsPath {
if !filepath.IsAbs(arg) { if !filepath.IsAbs(arg) {
args[i] = filepath.Join(p.packageDir, arg) args[i] = filepath.Join(packagePath, arg)
} }
} }
if arg == "-I" || arg == "-L" { if arg == "-I" || arg == "-L" {
@@ -391,95 +454,12 @@ func (p *cgoPackage) makePathsAbsolute(args []string) {
if strings.HasPrefix(arg, "-I") || strings.HasPrefix(arg, "-L") { if strings.HasPrefix(arg, "-I") || strings.HasPrefix(arg, "-L") {
path := arg[2:] path := arg[2:]
if !filepath.IsAbs(path) { if !filepath.IsAbs(path) {
args[i] = arg[:2] + filepath.Join(p.packageDir, path) args[i] = arg[:2] + filepath.Join(packagePath, path)
} }
} }
} }
} }
// parseCGoPreprocessorLines reads #cgo pseudo-preprocessor lines in the source
// text (import "C" fragment), stores their information such as CFLAGS, and
// returns the same text but with those #cgo lines replaced by spaces (to keep
// position offsets the same).
func (p *cgoPackage) parseCGoPreprocessorLines(text string, pos token.Pos) string {
for {
// Extract the #cgo line, and replace it with spaces.
// Replacing with spaces makes sure that error locations are
// still correct, while not interfering with parsing in any way.
lineStart := strings.Index(text, "#cgo ")
if lineStart < 0 {
break
}
lineLen := strings.IndexByte(text[lineStart:], '\n')
if lineLen < 0 {
lineLen = len(text) - lineStart
}
lineEnd := lineStart + lineLen
line := text[lineStart:lineEnd]
spaces := make([]byte, len(line))
for i := range spaces {
spaces[i] = ' '
}
text = text[:lineStart] + string(spaces) + text[lineEnd:]
// Get the text before the colon in the #cgo directive.
colon := strings.IndexByte(line, ':')
if colon < 0 {
p.addErrorAfter(pos, text[:lineStart], "missing colon in #cgo line")
continue
}
// Extract the fields before the colon. These fields are a list
// of build tags and the C environment variable.
fields := strings.Fields(line[4:colon])
if len(fields) == 0 {
p.addErrorAfter(pos, text[:lineStart+colon-1], "invalid #cgo line")
continue
}
if len(fields) > 1 {
p.addErrorAfter(pos, text[:lineStart+5], "not implemented: build constraints in #cgo line")
continue
}
name := fields[len(fields)-1]
value := line[colon+1:]
switch name {
case "CFLAGS":
flags, err := shlex.Split(value)
if err != nil {
// TODO: find the exact location where the error happened.
p.addErrorAfter(pos, text[:lineStart+colon+1], "failed to parse flags in #cgo line: "+err.Error())
continue
}
if err := checkCompilerFlags(name, flags); err != nil {
p.addErrorAfter(pos, text[:lineStart+colon+1], err.Error())
continue
}
p.makePathsAbsolute(flags)
p.cflags = append(p.cflags, flags...)
case "LDFLAGS":
flags, err := shlex.Split(value)
if err != nil {
// TODO: find the exact location where the error happened.
p.addErrorAfter(pos, text[:lineStart+colon+1], "failed to parse flags in #cgo line: "+err.Error())
continue
}
if err := checkLinkerFlags(name, flags); err != nil {
p.addErrorAfter(pos, text[:lineStart+colon+1], err.Error())
continue
}
p.makePathsAbsolute(flags)
p.ldflags = append(p.ldflags, flags...)
default:
startPos := strings.LastIndex(line[4:colon], name) + 4
p.addErrorAfter(pos, text[:lineStart+startPos], "invalid #cgo line: "+name)
continue
}
}
return text
}
// addFuncDecls adds the C function declarations found by libclang in the // addFuncDecls adds the C function declarations found by libclang in the
// comment above the `import "C"` statement. // comment above the `import "C"` statement.
func (p *cgoPackage) addFuncDecls() { func (p *cgoPackage) addFuncDecls() {
@@ -514,7 +494,7 @@ func (p *cgoPackage) addFuncDecls() {
if fn.variadic { if fn.variadic {
decl.Doc = &ast.CommentGroup{ decl.Doc = &ast.CommentGroup{
List: []*ast.Comment{ List: []*ast.Comment{
{ &ast.Comment{
Slash: fn.pos, Slash: fn.pos,
Text: "//go:variadic", Text: "//go:variadic",
}, },
@@ -525,7 +505,7 @@ func (p *cgoPackage) addFuncDecls() {
for i, arg := range fn.args { for i, arg := range fn.args {
args[i] = &ast.Field{ args[i] = &ast.Field{
Names: []*ast.Ident{ Names: []*ast.Ident{
{ &ast.Ident{
NamePos: fn.pos, NamePos: fn.pos,
Name: arg.name, Name: arg.name,
Obj: &ast.Object{ Obj: &ast.Object{
@@ -573,7 +553,7 @@ func (p *cgoPackage) addFuncPtrDecls() {
Name: "C." + name + "$funcaddr", Name: "C." + name + "$funcaddr",
} }
valueSpec := &ast.ValueSpec{ valueSpec := &ast.ValueSpec{
Names: []*ast.Ident{{ Names: []*ast.Ident{&ast.Ident{
NamePos: fn.pos, NamePos: fn.pos,
Name: "C." + name + "$funcaddr", Name: "C." + name + "$funcaddr",
Obj: obj, Obj: obj,
@@ -623,7 +603,7 @@ func (p *cgoPackage) addConstDecls() {
Name: "C." + name, Name: "C." + name,
} }
valueSpec := &ast.ValueSpec{ valueSpec := &ast.ValueSpec{
Names: []*ast.Ident{{ Names: []*ast.Ident{&ast.Ident{
NamePos: constVal.pos, NamePos: constVal.pos,
Name: "C." + name, Name: "C." + name,
Obj: obj, Obj: obj,
@@ -664,7 +644,7 @@ func (p *cgoPackage) addVarDecls() {
Name: "C." + name, Name: "C." + name,
} }
valueSpec := &ast.ValueSpec{ valueSpec := &ast.ValueSpec{
Names: []*ast.Ident{{ Names: []*ast.Ident{&ast.Ident{
NamePos: global.pos, NamePos: global.pos,
Name: "C." + name, Name: "C." + name,
Obj: obj, Obj: obj,
@@ -865,9 +845,9 @@ func (p *cgoPackage) makeUnionField(typ *elaboratedTypeInfo) *ast.StructType {
Struct: typ.typeExpr.Struct, Struct: typ.typeExpr.Struct,
Fields: &ast.FieldList{ Fields: &ast.FieldList{
Opening: typ.typeExpr.Fields.Opening, Opening: typ.typeExpr.Fields.Opening,
List: []*ast.Field{{ List: []*ast.Field{&ast.Field{
Names: []*ast.Ident{ Names: []*ast.Ident{
{ &ast.Ident{
NamePos: typ.typeExpr.Fields.Opening, NamePos: typ.typeExpr.Fields.Opening,
Name: "$union", Name: "$union",
}, },
@@ -948,9 +928,9 @@ func (p *cgoPackage) createUnionAccessor(field *ast.Field, typeName string) {
Recv: &ast.FieldList{ Recv: &ast.FieldList{
Opening: pos, Opening: pos,
List: []*ast.Field{ List: []*ast.Field{
{ &ast.Field{
Names: []*ast.Ident{ Names: []*ast.Ident{
{ &ast.Ident{
NamePos: pos, NamePos: pos,
Name: "union", Name: "union",
}, },
@@ -979,7 +959,7 @@ func (p *cgoPackage) createUnionAccessor(field *ast.Field, typeName string) {
}, },
Results: &ast.FieldList{ Results: &ast.FieldList{
List: []*ast.Field{ List: []*ast.Field{
{ &ast.Field{
Type: &ast.StarExpr{ Type: &ast.StarExpr{
Star: pos, Star: pos,
X: field.Type, X: field.Type,
@@ -1058,9 +1038,9 @@ func (p *cgoPackage) createBitfieldGetter(bitfield bitfieldInfo, typeName string
Recv: &ast.FieldList{ Recv: &ast.FieldList{
Opening: bitfield.pos, Opening: bitfield.pos,
List: []*ast.Field{ List: []*ast.Field{
{ &ast.Field{
Names: []*ast.Ident{ Names: []*ast.Ident{
{ &ast.Ident{
NamePos: bitfield.pos, NamePos: bitfield.pos,
Name: "s", Name: "s",
Obj: &ast.Object{ Obj: &ast.Object{
@@ -1094,7 +1074,7 @@ func (p *cgoPackage) createBitfieldGetter(bitfield bitfieldInfo, typeName string
}, },
Results: &ast.FieldList{ Results: &ast.FieldList{
List: []*ast.Field{ List: []*ast.Field{
{ &ast.Field{
Type: bitfield.field.Type, Type: bitfield.field.Type,
}, },
}, },
@@ -1211,9 +1191,9 @@ func (p *cgoPackage) createBitfieldSetter(bitfield bitfieldInfo, typeName string
Recv: &ast.FieldList{ Recv: &ast.FieldList{
Opening: bitfield.pos, Opening: bitfield.pos,
List: []*ast.Field{ List: []*ast.Field{
{ &ast.Field{
Names: []*ast.Ident{ Names: []*ast.Ident{
{ &ast.Ident{
NamePos: bitfield.pos, NamePos: bitfield.pos,
Name: "s", Name: "s",
Obj: &ast.Object{ Obj: &ast.Object{
@@ -1244,9 +1224,9 @@ func (p *cgoPackage) createBitfieldSetter(bitfield bitfieldInfo, typeName string
Params: &ast.FieldList{ Params: &ast.FieldList{
Opening: bitfield.pos, Opening: bitfield.pos,
List: []*ast.Field{ List: []*ast.Field{
{ &ast.Field{
Names: []*ast.Ident{ Names: []*ast.Ident{
{ &ast.Ident{
NamePos: bitfield.pos, NamePos: bitfield.pos,
Name: "value", Name: "value",
Obj: nil, Obj: nil,
+12 -3
View File
@@ -12,6 +12,7 @@ import (
"go/types" "go/types"
"io/ioutil" "io/ioutil"
"path/filepath" "path/filepath"
"regexp"
"runtime" "runtime"
"strings" "strings"
"testing" "testing"
@@ -24,11 +25,19 @@ var flagUpdate = flag.Bool("update", false, "Update images based on test output.
// platforms and Go versions. // platforms and Go versions.
func normalizeResult(result string) string { func normalizeResult(result string) string {
actual := strings.ReplaceAll(result, "\r\n", "\n") actual := strings.ReplaceAll(result, "\r\n", "\n")
// Make sure all functions are wrapped, even those that would otherwise be
// single-line functions. This is necessary because Go 1.14 changed the way
// such functions are wrapped and it's important to have consistent test
// results.
re := regexp.MustCompile(`func \((.+)\)( .*?) +{ (.+) }`)
actual = re.ReplaceAllString(actual, "func ($1)$2 {\n\t$3\n}")
return actual return actual
} }
func TestCGo(t *testing.T) { func TestCGo(t *testing.T) {
var cflags = []string{"--target=armv6m-unknown-unknown-eabi"} var cflags = []string{"--target=armv6m-none-eabi"}
for _, name := range []string{"basic", "errors", "types", "flags", "const"} { for _, name := range []string{"basic", "errors", "types", "flags", "const"} {
name := name // avoid a race condition name := name // avoid a race condition
@@ -56,7 +65,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
@@ -96,7 +105,7 @@ func TestCGo(t *testing.T) {
if err != nil { if err != nil {
t.Errorf("could not write out CGo AST: %v", err) t.Errorf("could not write out CGo AST: %v", err)
} }
actual := normalizeResult(buf.String()) actual := normalizeResult(string(buf.Bytes()))
// Read the file with the expected output, to compare against. // Read the file with the expected output, to compare against.
outfile := filepath.Join("testdata", name+".out.go") outfile := filepath.Join("testdata", name+".out.go")
+74 -164
View File
@@ -11,184 +11,105 @@ import (
"strings" "strings"
) )
var (
prefixParseFns map[token.Token]func(*tokenizer) (ast.Expr, *scanner.Error)
precedences = map[token.Token]int{
token.ADD: precedenceAdd,
token.SUB: precedenceAdd,
token.MUL: precedenceMul,
token.QUO: precedenceMul,
token.REM: precedenceMul,
}
)
const (
precedenceLowest = iota + 1
precedenceAdd
precedenceMul
precedencePrefix
)
func init() {
// This must be done in an init function to avoid an initialization order
// failure.
prefixParseFns = map[token.Token]func(*tokenizer) (ast.Expr, *scanner.Error){
token.IDENT: parseIdent,
token.INT: parseBasicLit,
token.FLOAT: parseBasicLit,
token.STRING: parseBasicLit,
token.CHAR: parseBasicLit,
token.LPAREN: parseParenExpr,
token.SUB: parseUnaryExpr,
}
}
// parseConst parses the given string as a C constant. // parseConst parses the given string as a C constant.
func parseConst(pos token.Pos, fset *token.FileSet, value string) (ast.Expr, *scanner.Error) { func parseConst(pos token.Pos, fset *token.FileSet, value string) (ast.Expr, *scanner.Error) {
t := newTokenizer(pos, fset, value) t := newTokenizer(pos, fset, value)
expr, err := parseConstExpr(t, precedenceLowest) expr, err := parseConstExpr(t)
t.Next() if t.token != token.EOF {
if t.curToken != token.EOF {
return nil, &scanner.Error{ return nil, &scanner.Error{
Pos: t.fset.Position(t.curPos), Pos: t.fset.Position(t.pos),
Msg: "unexpected token " + t.curToken.String() + ", expected end of expression", Msg: "unexpected token " + t.token.String(),
} }
} }
return expr, err return expr, err
} }
// parseConstExpr parses a stream of C tokens to a Go expression. // parseConstExpr parses a stream of C tokens to a Go expression.
func parseConstExpr(t *tokenizer, precedence int) (ast.Expr, *scanner.Error) { func parseConstExpr(t *tokenizer) (ast.Expr, *scanner.Error) {
if t.curToken == token.EOF { switch t.token {
case token.LPAREN:
lparen := t.pos
t.Next()
x, err := parseConstExpr(t)
if err != nil {
return nil, err
}
if t.token != token.RPAREN {
return nil, unexpectedToken(t, token.RPAREN)
}
expr := &ast.ParenExpr{
Lparen: lparen,
X: x,
Rparen: t.pos,
}
t.Next()
return expr, nil
case token.INT, token.FLOAT, token.STRING, token.CHAR:
expr := &ast.BasicLit{
ValuePos: t.pos,
Kind: t.token,
Value: t.value,
}
t.Next()
return expr, nil
case token.IDENT:
expr := &ast.Ident{
NamePos: t.pos,
Name: "C." + t.value,
}
t.Next()
return expr, nil
case token.EOF:
return nil, &scanner.Error{ return nil, &scanner.Error{
Pos: t.fset.Position(t.curPos), Pos: t.fset.Position(t.pos),
Msg: "empty constant", Msg: "empty constant",
} }
} default:
prefix := prefixParseFns[t.curToken]
if prefix == nil {
return nil, &scanner.Error{ return nil, &scanner.Error{
Pos: t.fset.Position(t.curPos), Pos: t.fset.Position(t.pos),
Msg: fmt.Sprintf("unexpected token %s", t.curToken), Msg: fmt.Sprintf("unexpected token %s", t.token),
} }
} }
leftExpr, err := prefix(t)
for t.peekToken != token.EOF && precedence < precedences[t.peekToken] {
switch t.peekToken {
case token.ADD, token.SUB, token.MUL, token.QUO, token.REM:
t.Next()
leftExpr, err = parseBinaryExpr(t, leftExpr)
}
}
return leftExpr, err
}
func parseIdent(t *tokenizer) (ast.Expr, *scanner.Error) {
return &ast.Ident{
NamePos: t.curPos,
Name: "C." + t.curValue,
}, nil
}
func parseBasicLit(t *tokenizer) (ast.Expr, *scanner.Error) {
return &ast.BasicLit{
ValuePos: t.curPos,
Kind: t.curToken,
Value: t.curValue,
}, nil
}
func parseParenExpr(t *tokenizer) (ast.Expr, *scanner.Error) {
lparen := t.curPos
t.Next()
x, err := parseConstExpr(t, precedenceLowest)
if err != nil {
return nil, err
}
t.Next()
if t.curToken != token.RPAREN {
return nil, unexpectedToken(t, token.RPAREN)
}
expr := &ast.ParenExpr{
Lparen: lparen,
X: x,
Rparen: t.curPos,
}
return expr, nil
}
func parseBinaryExpr(t *tokenizer, left ast.Expr) (ast.Expr, *scanner.Error) {
expression := &ast.BinaryExpr{
X: left,
Op: t.curToken,
OpPos: t.curPos,
}
precedence := precedences[t.curToken]
t.Next()
right, err := parseConstExpr(t, precedence)
expression.Y = right
return expression, err
}
func parseUnaryExpr(t *tokenizer) (ast.Expr, *scanner.Error) {
expression := &ast.UnaryExpr{
OpPos: t.curPos,
Op: t.curToken,
}
t.Next()
x, err := parseConstExpr(t, precedencePrefix)
expression.X = x
return expression, err
} }
// unexpectedToken returns an error of the form "unexpected token FOO, expected // unexpectedToken returns an error of the form "unexpected token FOO, expected
// BAR". // BAR".
func unexpectedToken(t *tokenizer, expected token.Token) *scanner.Error { func unexpectedToken(t *tokenizer, expected token.Token) *scanner.Error {
return &scanner.Error{ return &scanner.Error{
Pos: t.fset.Position(t.curPos), Pos: t.fset.Position(t.pos),
Msg: fmt.Sprintf("unexpected token %s, expected %s", t.curToken, expected), Msg: fmt.Sprintf("unexpected token %s, expected %s", t.token, expected),
} }
} }
// tokenizer reads C source code and converts it to Go tokens. // tokenizer reads C source code and converts it to Go tokens.
type tokenizer struct { type tokenizer struct {
curPos, peekPos token.Pos pos token.Pos
fset *token.FileSet fset *token.FileSet
curToken, peekToken token.Token token token.Token
curValue, peekValue string value string
buf string buf string
} }
// newTokenizer initializes a new tokenizer, positioned at the first token in // newTokenizer initializes a new tokenizer, positioned at the first token in
// the string. // the string.
func newTokenizer(start token.Pos, fset *token.FileSet, buf string) *tokenizer { func newTokenizer(start token.Pos, fset *token.FileSet, buf string) *tokenizer {
t := &tokenizer{ t := &tokenizer{
peekPos: start, pos: start,
fset: fset, fset: fset,
buf: buf, buf: buf,
peekToken: token.ILLEGAL, token: token.ILLEGAL,
} }
// Parse the first two tokens (cur and peek). t.Next() // Parse the first token.
t.Next()
t.Next()
return t return t
} }
// Next consumes the next token in the stream. There is no return value, read // Next consumes the next token in the stream. There is no return value, read
// the next token from the pos, token and value properties. // the next token from the pos, token and value properties.
func (t *tokenizer) Next() { func (t *tokenizer) Next() {
// The previous peek is now the current token. t.pos += token.Pos(len(t.value))
t.curPos = t.peekPos
t.curToken = t.peekToken
t.curValue = t.peekValue
// Parse the next peek token.
t.peekPos += token.Pos(len(t.curValue))
for { for {
if len(t.buf) == 0 { if len(t.buf) == 0 {
t.peekToken = token.EOF t.token = token.EOF
return return
} }
c := t.buf[0] c := t.buf[0]
@@ -197,28 +118,17 @@ func (t *tokenizer) Next() {
// Skip whitespace. // Skip whitespace.
// Based on this source, not sure whether it represents C whitespace: // Based on this source, not sure whether it represents C whitespace:
// https://en.cppreference.com/w/cpp/string/byte/isspace // https://en.cppreference.com/w/cpp/string/byte/isspace
t.peekPos++ t.pos++
t.buf = t.buf[1:] t.buf = t.buf[1:]
case c == '(' || c == ')' || c == '+' || c == '-' || c == '*' || c == '/' || c == '%': case c == '(' || c == ')':
// Single-character tokens. // Single-character tokens.
// TODO: ++ (increment) and -- (decrement) operators.
switch c { switch c {
case '(': case '(':
t.peekToken = token.LPAREN t.token = token.LPAREN
case ')': case ')':
t.peekToken = token.RPAREN t.token = token.RPAREN
case '+':
t.peekToken = token.ADD
case '-':
t.peekToken = token.SUB
case '*':
t.peekToken = token.MUL
case '/':
t.peekToken = token.QUO
case '%':
t.peekToken = token.REM
} }
t.peekValue = t.buf[:1] t.value = t.buf[:1]
t.buf = t.buf[1:] t.buf = t.buf[1:]
return return
case c >= '0' && c <= '9': case c >= '0' && c <= '9':
@@ -236,17 +146,17 @@ func (t *tokenizer) Next() {
break break
} }
} }
t.peekValue = t.buf[:tokenLen] t.value = t.buf[:tokenLen]
t.buf = t.buf[tokenLen:] t.buf = t.buf[tokenLen:]
if hasDot { if hasDot {
// Integer constants are more complicated than this but this is // Integer constants are more complicated than this but this is
// a close approximation. // a close approximation.
// https://en.cppreference.com/w/cpp/language/integer_literal // https://en.cppreference.com/w/cpp/language/integer_literal
t.peekToken = token.FLOAT t.token = token.FLOAT
t.peekValue = strings.TrimRight(t.peekValue, "f") t.value = strings.TrimRight(t.value, "f")
} else { } else {
t.peekToken = token.INT t.token = token.INT
t.peekValue = strings.TrimRight(t.peekValue, "uUlL") t.value = strings.TrimRight(t.value, "uUlL")
} }
return return
case c >= 'A' && c <= 'Z' || c >= 'a' && c <= 'z' || c == '_': case c >= 'A' && c <= 'Z' || c >= 'a' && c <= 'z' || c == '_':
@@ -260,9 +170,9 @@ func (t *tokenizer) Next() {
break break
} }
} }
t.peekValue = t.buf[:tokenLen] t.value = t.buf[:tokenLen]
t.buf = t.buf[tokenLen:] t.buf = t.buf[tokenLen:]
t.peekToken = token.IDENT t.token = token.IDENT
return return
case c == '"': case c == '"':
// String constant. Find the first '"' character that is not // String constant. Find the first '"' character that is not
@@ -278,8 +188,8 @@ func (t *tokenizer) Next() {
escape = c == '\\' escape = c == '\\'
} }
} }
t.peekToken = token.STRING t.token = token.STRING
t.peekValue = t.buf[:tokenLen] t.value = t.buf[:tokenLen]
t.buf = t.buf[tokenLen:] t.buf = t.buf[tokenLen:]
return return
case c == '\'': case c == '\'':
@@ -296,12 +206,12 @@ func (t *tokenizer) Next() {
escape = c == '\\' escape = c == '\\'
} }
} }
t.peekToken = token.CHAR t.token = token.CHAR
t.peekValue = t.buf[:tokenLen] t.value = t.buf[:tokenLen]
t.buf = t.buf[tokenLen:] t.buf = t.buf[tokenLen:]
return return
default: default:
t.peekToken = token.ILLEGAL t.token = token.ILLEGAL
return return
} }
} }
+2 -19
View File
@@ -18,7 +18,7 @@ func TestParseConst(t *testing.T) {
{`(5)`, `(5)`}, {`(5)`, `(5)`},
{`(((5)))`, `(5)`}, {`(((5)))`, `(5)`},
{`)`, `error: 1:1: unexpected token )`}, {`)`, `error: 1:1: unexpected token )`},
{`5)`, `error: 1:2: unexpected token ), expected end of expression`}, {`5)`, `error: 1:2: unexpected token )`},
{" \t)", `error: 1:4: unexpected token )`}, {" \t)", `error: 1:4: unexpected token )`},
{`5.8f`, `5.8`}, {`5.8f`, `5.8`},
{`foo`, `C.foo`}, {`foo`, `C.foo`},
@@ -30,24 +30,7 @@ func TestParseConst(t *testing.T) {
{`'a'`, `'a'`}, {`'a'`, `'a'`},
{`0b10`, `0b10`}, {`0b10`, `0b10`},
{`0x1234_5678`, `0x1234_5678`}, {`0x1234_5678`, `0x1234_5678`},
{`5 5`, `error: 1:3: unexpected token INT, expected end of expression`}, // test for a bugfix {`5 5`, `error: 1:3: unexpected token INT`}, // test for a bugfix
// Binary operators.
{`5+5`, `5 + 5`},
{`5-5`, `5 - 5`},
{`5*5`, `5 * 5`},
{`5/5`, `5 / 5`},
{`5%5`, `5 % 5`},
{`(5/5)`, `(5 / 5)`},
{`1 - 2`, `1 - 2`},
{`1 - 2 + 3`, `1 - 2 + 3`},
{`1 - 2 * 3`, `1 - 2*3`},
{`(1 - 2) * 3`, `(1 - 2) * 3`},
{`1 * 2 - 3`, `1*2 - 3`},
{`1 * (2 - 3)`, `1 * (2 - 3)`},
// Unary operators.
{`-5`, `-5`},
{`-5-2`, `-5 - 2`},
{`5 - - 2`, `5 - -2`},
} { } {
fset := token.NewFileSet() fset := token.NewFileSet()
startPos := fset.AddFile("", -1, 1000).Pos(0) startPos := fset.AddFile("", -1, 1000).Pos(0)
+12 -48
View File
@@ -16,7 +16,7 @@ import (
) )
/* /*
#include <clang-c/Index.h> // if this fails, install libclang-11-dev #include <clang-c/Index.h> // if this fails, install libclang-10-dev
#include <stdlib.h> #include <stdlib.h>
#include <stdint.h> #include <stdint.h>
@@ -72,14 +72,17 @@ var diagnosticSeverity = [...]string{
C.CXDiagnostic_Fatal: "fatal", C.CXDiagnostic_Fatal: "fatal",
} }
func (p *cgoPackage) parseFragment(fragment string, cflags []string, filename string) { func (p *cgoPackage) parseFragment(fragment string, cflags []string, posFilename string, posLine int) {
index := C.clang_createIndex(0, 0) index := C.clang_createIndex(0, 0)
defer C.clang_disposeIndex(index) defer C.clang_disposeIndex(index)
// pretend to be a .c file // pretend to be a .c file
filenameC := C.CString(filename + "!cgo.c") filenameC := C.CString(posFilename + "!cgo.c")
defer C.free(unsafe.Pointer(filenameC)) defer C.free(unsafe.Pointer(filenameC))
// fix up error locations
fragment = fmt.Sprintf("# %d %#v\n", posLine+1, posFilename) + fragment
fragmentC := C.CString(fragment) fragmentC := C.CString(fragment)
defer C.free(unsafe.Pointer(fragmentC)) defer C.free(unsafe.Pointer(fragmentC))
@@ -193,14 +196,14 @@ func tinygo_clang_globals_visitor(c, parent C.GoCXCursor, client_data C.CXClient
} }
fn.args = append(fn.args, paramInfo{ fn.args = append(fn.args, paramInfo{
name: argName, name: argName,
typeExpr: p.makeDecayingASTType(argType, pos), typeExpr: p.makeASTType(argType, pos),
}) })
} }
resultType := C.tinygo_clang_getCursorResultType(c) resultType := C.tinygo_clang_getCursorResultType(c)
if resultType.kind != C.CXType_Void { if resultType.kind != C.CXType_Void {
fn.results = &ast.FieldList{ fn.results = &ast.FieldList{
List: []*ast.Field{ List: []*ast.Field{
{ &ast.Field{
Type: p.makeASTType(resultType, pos), Type: p.makeASTType(resultType, pos),
}, },
}, },
@@ -377,7 +380,7 @@ func (p *cgoPackage) addErrorAfter(pos token.Pos, after, msg string) {
func (p *cgoPackage) addErrorAt(position token.Position, msg string) { func (p *cgoPackage) addErrorAt(position token.Position, msg string) {
if filepath.IsAbs(position.Filename) { if filepath.IsAbs(position.Filename) {
// Relative paths for readability, like other Go parser errors. // Relative paths for readability, like other Go parser errors.
relpath, err := filepath.Rel(p.currentDir, position.Filename) relpath, err := filepath.Rel(p.dir, position.Filename)
if err == nil { if err == nil {
position.Filename = relpath position.Filename = relpath
} }
@@ -388,41 +391,6 @@ func (p *cgoPackage) addErrorAt(position token.Position, msg string) {
}) })
} }
// makeDecayingASTType does the same as makeASTType but takes care of decaying
// types (arrays in function parameters, etc). It is otherwise identical to
// makeASTType.
func (p *cgoPackage) makeDecayingASTType(typ C.CXType, pos token.Pos) ast.Expr {
// Strip typedefs, if any.
underlyingType := typ
if underlyingType.kind == C.CXType_Typedef {
c := C.tinygo_clang_getTypeDeclaration(typ)
underlyingType = C.tinygo_clang_getTypedefDeclUnderlyingType(c)
// TODO: support a chain of typedefs. At the moment, it seems to get
// stuck in an endless loop when trying to get to the most underlying
// type.
}
// Check for decaying type. An example would be an array type in a
// parameter. This declaration:
// void foo(char buf[6]);
// is the same as this one:
// void foo(char *buf);
// But this one:
// void bar(char buf[6][4]);
// equals this:
// void bar(char *buf[4]);
// so not all array dimensions should be stripped, just the first one.
// TODO: there are more kinds of decaying types.
if underlyingType.kind == C.CXType_ConstantArray {
// Apply type decaying.
pointeeType := C.clang_getElementType(underlyingType)
return &ast.StarExpr{
Star: pos,
X: p.makeASTType(pointeeType, pos),
}
}
return p.makeASTType(typ, pos)
}
// makeASTType return the ast.Expr for the given libclang type. In other words, // makeASTType return the ast.Expr for the given libclang type. In other words,
// it converts a libclang type to a type in the Go AST. // it converts a libclang type to a type in the Go AST.
func (p *cgoPackage) makeASTType(typ C.CXType, pos token.Pos) ast.Expr { func (p *cgoPackage) makeASTType(typ C.CXType, pos token.Pos) ast.Expr {
@@ -807,7 +775,7 @@ func tinygo_clang_struct_visitor(c, parent C.GoCXCursor, client_data C.CXClientD
} }
*inBitfield = false *inBitfield = false
field.Names = []*ast.Ident{ field.Names = []*ast.Ident{
{ &ast.Ident{
NamePos: pos, NamePos: pos,
Name: name, Name: name,
Obj: &ast.Object{ Obj: &ast.Object{
@@ -828,12 +796,8 @@ func tinygo_clang_enum_visitor(c, parent C.GoCXCursor, client_data C.CXClientDat
pos := p.getCursorPosition(c) pos := p.getCursorPosition(c)
value := C.tinygo_clang_getEnumConstantDeclValue(c) value := C.tinygo_clang_getEnumConstantDeclValue(c)
p.constants[name] = constantInfo{ p.constants[name] = constantInfo{
expr: &ast.BasicLit{ expr: &ast.BasicLit{pos, token.INT, strconv.FormatInt(int64(value), 10)},
ValuePos: pos, pos: pos,
Kind: token.INT,
Value: strconv.FormatInt(int64(value), 10),
},
pos: pos,
} }
return C.CXChildVisit_Continue return C.CXChildVisit_Continue
} }
+1
View File
@@ -1,4 +1,5 @@
// +build !byollvm // +build !byollvm
// +build !llvm10
package cgo package cgo
+14
View File
@@ -0,0 +1,14 @@
// +build !byollvm
// +build llvm10
package cgo
/*
#cgo linux CFLAGS: -I/usr/lib/llvm-10/include
#cgo darwin CFLAGS: -I/usr/local/opt/llvm@10/include
#cgo freebsd CFLAGS: -I/usr/local/llvm10/include
#cgo linux LDFLAGS: -L/usr/lib/llvm-10/lib -lclang
#cgo darwin LDFLAGS: -L/usr/local/opt/llvm@10/lib -lclang -lffi
#cgo freebsd LDFLAGS: -L/usr/local/llvm10/lib -lclang
*/
import "C"
+1 -1
View File
@@ -3,7 +3,7 @@
// are slightly different from the ones defined in libclang.go, but they // are slightly different from the ones defined in libclang.go, but they
// should be ABI compatible. // should be ABI compatible.
#include <clang-c/Index.h> // if this fails, install libclang-11-dev #include <clang-c/Index.h> // if this fails, install libclang-10-dev
CXCursor tinygo_clang_getTranslationUnitCursor(CXTranslationUnit tu) { CXCursor tinygo_clang_getTranslationUnitCursor(CXTranslationUnit tu) {
return clang_getTranslationUnitCursor(tu); return clang_getTranslationUnitCursor(tu);
-8
View File
@@ -14,12 +14,6 @@ typedef someType noType; // undefined type
#define SOME_CONST_2 6) // const not used (so no error) #define SOME_CONST_2 6) // const not used (so no error)
#define SOME_CONST_3 1234 // const too large for byte #define SOME_CONST_3 1234 // const too large for byte
*/ */
//
//
// #define SOME_CONST_4 8) // after some empty lines
import "C"
// #warning another warning
import "C" import "C"
// Make sure that errors for the following lines won't change with future // Make sure that errors for the following lines won't change with future
@@ -36,6 +30,4 @@ var (
_ = C.SOME_CONST_1 _ = C.SOME_CONST_1
_ byte = C.SOME_CONST_3 _ byte = C.SOME_CONST_3
_ = C.SOME_CONST_4
) )
+1 -4
View File
@@ -1,16 +1,13 @@
// CGo errors: // CGo errors:
// testdata/errors.go:4:2: warning: some warning // testdata/errors.go:4:2: warning: some warning
// testdata/errors.go:11:9: error: unknown type name 'someType' // testdata/errors.go:11:9: error: unknown type name 'someType'
// testdata/errors.go:22:5: warning: another warning // testdata/errors.go:13:23: unexpected token )
// testdata/errors.go:13:23: unexpected token ), expected end of expression
// testdata/errors.go:19:26: unexpected token ), expected end of expression
// Type checking errors after CGo processing: // Type checking errors after CGo processing:
// testdata/errors.go:102: cannot use 2 << 10 (untyped int constant 2048) as uint8 value in variable declaration (overflows) // testdata/errors.go:102: cannot use 2 << 10 (untyped int constant 2048) as uint8 value in variable declaration (overflows)
// testdata/errors.go:105: unknown field z in struct literal // testdata/errors.go:105: unknown field z in struct literal
// testdata/errors.go:108: undeclared name: C.SOME_CONST_1 // testdata/errors.go:108: undeclared name: C.SOME_CONST_1
// testdata/errors.go:110: cannot use C.SOME_CONST_3 (untyped int constant 1234) as byte value in variable declaration (overflows) // testdata/errors.go:110: cannot use C.SOME_CONST_3 (untyped int constant 1234) as byte value in variable declaration (overflows)
// testdata/errors.go:112: undeclared name: C.SOME_CONST_4
package main package main
-4
View File
@@ -111,10 +111,6 @@ void variadic2(int x, int y, ...);
*/ */
import "C" import "C"
// // Test that we can refer from this CGo fragment to the fragment above.
// typedef myint myint2;
import "C"
var ( var (
// Simple typedefs. // Simple typedefs.
_ C.myint _ C.myint
+15 -5
View File
@@ -67,7 +67,9 @@ type C.union3_t = C.union_1
type C.union_nested_t = C.union_3 type C.union_nested_t = C.union_3
type C.unionarray_t = struct{ arr [10]C.uchar } type C.unionarray_t = struct{ arr [10]C.uchar }
func (s *C.struct_4) bitfield_a() C.uchar { return s.__bitfield_1 & 0x1f } func (s *C.struct_4) bitfield_a() C.uchar {
return s.__bitfield_1 & 0x1f
}
func (s *C.struct_4) set_bitfield_a(value C.uchar) { func (s *C.struct_4) set_bitfield_a(value C.uchar) {
s.__bitfield_1 = s.__bitfield_1&^0x1f | value&0x1f<<0 s.__bitfield_1 = s.__bitfield_1&^0x1f | value&0x1f<<0
} }
@@ -103,9 +105,15 @@ type C.struct_type1 struct {
} }
type C.struct_type2 struct{ _type C.int } type C.struct_type2 struct{ _type C.int }
func (union *C.union_1) unionfield_i() *C.int { return (*C.int)(unsafe.Pointer(&union.$union)) } func (union *C.union_1) unionfield_i() *C.int {
func (union *C.union_1) unionfield_d() *float64 { return (*float64)(unsafe.Pointer(&union.$union)) } return (*C.int)(unsafe.Pointer(&union.$union))
func (union *C.union_1) unionfield_s() *C.short { return (*C.short)(unsafe.Pointer(&union.$union)) } }
func (union *C.union_1) unionfield_d() *float64 {
return (*float64)(unsafe.Pointer(&union.$union))
}
func (union *C.union_1) unionfield_s() *C.short {
return (*C.short)(unsafe.Pointer(&union.$union))
}
type C.union_1 struct{ $union uint64 } type C.union_1 struct{ $union uint64 }
@@ -130,7 +138,9 @@ func (union *C.union_3) unionfield_thing() *C.union3_t {
type C.union_3 struct{ $union [2]uint64 } type C.union_3 struct{ $union [2]uint64 }
func (union *C.union_union2d) unionfield_i() *C.int { return (*C.int)(unsafe.Pointer(&union.$union)) } func (union *C.union_union2d) unionfield_i() *C.int {
return (*C.int)(unsafe.Pointer(&union.$union))
}
func (union *C.union_union2d) unionfield_d() *[2]float64 { func (union *C.union_union2d) unionfield_d() *[2]float64 {
return (*[2]float64)(unsafe.Pointer(&union.$union)) return (*[2]float64)(unsafe.Pointer(&union.$union))
} }
+18 -142
View File
@@ -5,7 +5,6 @@ package compileopts
import ( import (
"errors" "errors"
"fmt" "fmt"
"os"
"path/filepath" "path/filepath"
"regexp" "regexp"
"strings" "strings"
@@ -22,7 +21,7 @@ type Config struct {
TestConfig TestConfig TestConfig TestConfig
} }
// Triple returns the LLVM target triple, like armv6m-unknown-unknown-eabi. // Triple returns the LLVM target triple, like armv6m-none-eabi.
func (c *Config) Triple() string { func (c *Config) Triple() string {
return c.Target.Triple return c.Target.Triple
} }
@@ -56,7 +55,7 @@ func (c *Config) GOARCH() string {
// BuildTags returns the complete list of build tags used during this build. // BuildTags returns the complete list of build tags used during this build.
func (c *Config) BuildTags() []string { func (c *Config) BuildTags() []string {
tags := append(c.Target.BuildTags, []string{"tinygo", "math_big_pure_go", "gc." + c.GC(), "scheduler." + c.Scheduler(), "serial." + c.Serial()}...) tags := append(c.Target.BuildTags, []string{"tinygo", "gc." + c.GC(), "scheduler." + c.Scheduler()}...)
for i := 1; i <= c.GoMinorVersion; i++ { for i := 1; i <= c.GoMinorVersion; i++ {
tags = append(tags, fmt.Sprintf("go1.%d", i)) tags = append(tags, fmt.Sprintf("go1.%d", i))
} }
@@ -81,7 +80,12 @@ func (c *Config) GC() string {
if c.Target.GC != "" { if c.Target.GC != "" {
return c.Target.GC return c.Target.GC
} }
return "conservative" for _, tag := range c.Target.BuildTags {
if tag == "baremetal" || tag == "wasm" {
return "conservative"
}
}
return "extalloc"
} }
// NeedsStackObjects returns true if the compiler should insert stack objects // NeedsStackObjects returns true if the compiler should insert stack objects
@@ -90,7 +94,7 @@ func (c *Config) NeedsStackObjects() bool {
switch c.GC() { switch c.GC() {
case "conservative", "extalloc": case "conservative", "extalloc":
for _, tag := range c.BuildTags() { for _, tag := range c.BuildTags() {
if tag == "tinygo.wasm" { if tag == "wasm" {
return true return true
} }
} }
@@ -114,39 +118,6 @@ func (c *Config) Scheduler() string {
return "coroutines" return "coroutines"
} }
// Serial returns the serial implementation for this build configuration: uart,
// usb (meaning USB-CDC), or none.
func (c *Config) Serial() string {
if c.Options.Serial != "" {
return c.Options.Serial
}
if c.Target.Serial != "" {
return c.Target.Serial
}
return "none"
}
// OptLevels returns the optimization level (0-2), size level (0-2), and inliner
// threshold as used in the LLVM optimization pipeline.
func (c *Config) OptLevels() (optLevel, sizeLevel int, inlinerThreshold uint) {
switch c.Options.Opt {
case "none", "0":
return 0, 0, 0 // -O0
case "1":
return 1, 0, 0 // -O1
case "2":
return 2, 0, 225 // -O2
case "s":
return 2, 1, 225 // -Os
case "z":
return 2, 2, 5 // -Oz, default
default:
// This is not shown to the user: valid choices are already checked as
// part of Options.Verify(). It is here as a sanity check.
panic("unknown optimization level: -opt=" + c.Options.Opt)
}
}
// FuncImplementation picks an appropriate func value implementation for the // FuncImplementation picks an appropriate func value implementation for the
// target. // target.
func (c *Config) FuncImplementation() string { func (c *Config) FuncImplementation() string {
@@ -189,102 +160,20 @@ func (c *Config) AutomaticStackSize() bool {
return false return false
} }
// RP2040BootPatch returns whether the RP2040 boot patch should be applied that
// calculates and patches in the checksum for the 2nd stage bootloader.
func (c *Config) RP2040BootPatch() bool {
if c.Target.RP2040BootPatch != nil {
return *c.Target.RP2040BootPatch
}
return false
}
// MuslArchitecture returns the architecture name as used in musl libc. It is
// usually the same as the first part of the LLVM triple, but not always.
func MuslArchitecture(triple string) string {
arch := strings.Split(triple, "-")[0]
if strings.HasPrefix(arch, "arm") || strings.HasPrefix(arch, "thumb") {
arch = "arm"
}
return arch
}
// LibcPath returns the path to the libc directory. The libc path will be either
// a precompiled libc shipped with a TinyGo build, or a libc path in the cache
// directory (which might not yet be built).
func (c *Config) LibcPath(name string) (path string, precompiled bool) {
// Try to load a precompiled library.
precompiledDir := filepath.Join(goenv.Get("TINYGOROOT"), "pkg", c.Triple(), name)
if _, err := os.Stat(precompiledDir); err == nil {
// Found a precompiled library for this OS/architecture. Return the path
// directly.
return precompiledDir, true
}
// No precompiled library found. Determine the path name that will be used
// in the build cache.
var outname string
if c.CPU() != "" {
outname = name + "-" + c.Triple() + "-" + c.CPU()
} else {
outname = name + "-" + c.Triple()
}
return filepath.Join(goenv.Get("GOCACHE"), outname), 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 {
var cflags []string cflags := append([]string{}, c.Options.CFlags...)
for _, flag := range c.Target.CFlags { for _, flag := range c.Target.CFlags {
cflags = append(cflags, strings.ReplaceAll(flag, "{root}", goenv.Get("TINYGOROOT"))) cflags = append(cflags, strings.ReplaceAll(flag, "{root}", goenv.Get("TINYGOROOT")))
} }
switch c.Target.Libc { if c.Target.Libc == "picolibc" {
case "picolibc":
root := goenv.Get("TINYGOROOT") root := goenv.Get("TINYGOROOT")
picolibcDir := filepath.Join(root, "lib", "picolibc", "newlib", "libc") cflags = append(cflags, "-nostdlibinc", "-Xclang", "-internal-isystem", "-Xclang", filepath.Join(root, "lib", "picolibc", "newlib", "libc", "include"))
path, _ := c.LibcPath("picolibc") cflags = append(cflags, "-I"+filepath.Join(root, "lib/picolibc-include"))
cflags = append(cflags,
"--sysroot="+path,
"-Xclang", "-internal-isystem", "-Xclang", filepath.Join(picolibcDir, "include"),
"-Xclang", "-internal-isystem", "-Xclang", filepath.Join(picolibcDir, "tinystdio"),
)
case "musl":
root := goenv.Get("TINYGOROOT")
path, _ := c.LibcPath("musl")
arch := MuslArchitecture(c.Triple())
cflags = append(cflags,
"--sysroot="+path,
"-Xclang", "-internal-isystem", "-Xclang", filepath.Join(root, "lib", "musl", "arch", arch),
"-Xclang", "-internal-isystem", "-Xclang", filepath.Join(root, "lib", "musl", "include"),
)
case "wasi-libc":
root := goenv.Get("TINYGOROOT")
cflags = append(cflags, "--sysroot="+root+"/lib/wasi-libc/sysroot")
case "":
// No libc specified, nothing to add.
default:
// Incorrect configuration. This could be handled in a better way, but
// usually this will be found by developers (not by TinyGo users).
panic("unknown libc: " + c.Target.Libc)
} }
// Always emit debug information. It is optionally stripped at link time. if c.Debug() {
cflags = append(cflags, "-g") cflags = append(cflags, "-g")
// Use the same optimization level as TinyGo.
cflags = append(cflags, "-O"+c.Options.Opt)
// Set the LLVM target triple.
cflags = append(cflags, "--target="+c.Triple())
// Set the -mcpu (or similar) flag.
if c.Target.CPU != "" {
if c.GOARCH() == "amd64" || c.GOARCH() == "386" {
// x86 prefers the -march flag (-mcpu is deprecated there).
cflags = append(cflags, "-march="+c.Target.CPU)
} else if strings.HasPrefix(c.Triple(), "avr") {
// AVR MCUs use -mmcu instead of -mcpu.
cflags = append(cflags, "-mmcu="+c.Target.CPU)
} else {
// The rest just uses -mcpu.
cflags = append(cflags, "-mcpu="+c.Target.CPU)
}
} }
return cflags return cflags
} }
@@ -295,7 +184,7 @@ func (c *Config) CFlags() []string {
func (c *Config) LDFlags() []string { func (c *Config) LDFlags() []string {
root := goenv.Get("TINYGOROOT") root := goenv.Get("TINYGOROOT")
// Merge and adjust LDFlags. // Merge and adjust LDFlags.
var ldflags []string ldflags := append([]string{}, c.Options.LDFlags...)
for _, flag := range c.Target.LDFlags { for _, flag := range c.Target.LDFlags {
ldflags = append(ldflags, strings.ReplaceAll(flag, "{root}", root)) ldflags = append(ldflags, strings.ReplaceAll(flag, "{root}", root))
} }
@@ -324,9 +213,8 @@ func (c *Config) VerifyIR() bool {
return c.Options.VerifyIR return c.Options.VerifyIR
} }
// Debug returns whether debug (DWARF) information should be retained by the // Debug returns whether to add debug symbols to the IR, for debugging with GDB
// linker. By default, debug information is retained but it can be removed with // and similar.
// the -no-debug flag.
func (c *Config) Debug() bool { func (c *Config) Debug() bool {
return c.Options.Debug return c.Options.Debug
} }
@@ -350,11 +238,6 @@ func (c *Config) BinaryFormat(ext string) string {
// More information: // More information:
// https://github.com/Microsoft/uf2 // https://github.com/Microsoft/uf2
return "uf2" return "uf2"
case ".zip":
if c.Target.BinaryFormat != "" {
return c.Target.BinaryFormat
}
return "zip"
default: default:
// Use the ELF format for unrecognized file formats. // Use the ELF format for unrecognized file formats.
return "elf" return "elf"
@@ -372,9 +255,6 @@ func (c *Config) Programmer() (method, openocdInterface string) {
case "openocd", "msd", "command": case "openocd", "msd", "command":
// The -programmer flag only specifies the flash method. // The -programmer flag only specifies the flash method.
return c.Options.Programmer, c.Target.OpenOCDInterface return c.Options.Programmer, c.Target.OpenOCDInterface
case "bmp":
// The -programmer flag only specifies the flash method.
return c.Options.Programmer, ""
default: default:
// The -programmer flag specifies something else, assume it specifies // The -programmer flag specifies something else, assume it specifies
// the OpenOCD interface name. // the OpenOCD interface name.
@@ -441,10 +321,6 @@ func (c *Config) WasmAbi() string {
return c.Target.WasmAbi return c.Target.WasmAbi
} }
func (c *Config) LLVMFeatures() string {
return c.Options.LLVMFeatures
}
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
+19 -44
View File
@@ -2,47 +2,37 @@ package compileopts
import ( import (
"fmt" "fmt"
"regexp"
"strings" "strings"
) )
var ( var (
validGCOptions = []string{"none", "leaking", "extalloc", "conservative"} validGCOptions = []string{"none", "leaking", "extalloc", "conservative"}
validSchedulerOptions = []string{"none", "tasks", "coroutines"} validSchedulerOptions = []string{"none", "tasks", "coroutines"}
validSerialOptions = []string{"none", "uart", "usb"}
validPrintSizeOptions = []string{"none", "short", "full"} validPrintSizeOptions = []string{"none", "short", "full"}
validPanicStrategyOptions = []string{"print", "trap"} validPanicStrategyOptions = []string{"print", "trap"}
validOptOptions = []string{"none", "0", "1", "2", "s", "z"}
) )
// 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, but can also be passed in environment // usually passed from the command line.
// variables for example.
type Options struct { type Options struct {
GOOS string // environment variable Target string
GOARCH string // environment variable Opt string
Target string GC string
Opt string PanicStrategy string
GC string Scheduler string
PanicStrategy string PrintIR bool
Scheduler string DumpSSA bool
Serial string VerifyIR bool
PrintIR bool PrintCommands bool
DumpSSA bool Debug bool
VerifyIR bool PrintSizes string
PrintCommands func(cmd string, args ...string) PrintStacks bool
Parallelism int // -p flag CFlags []string
Debug bool LDFlags []string
PrintSizes string Tags string
PrintAllocs *regexp.Regexp // regexp string WasmAbi string
PrintStacks bool TestConfig TestConfig
Tags string Programmer string
WasmAbi string
GlobalValues map[string]map[string]string // map[pkgpath]map[varname]value
TestConfig TestConfig
Programmer string
OpenOCDCommands []string
LLVMFeatures string
} }
// Verify performs a validation on the given options, raising an error if options are not valid. // Verify performs a validation on the given options, raising an error if options are not valid.
@@ -65,15 +55,6 @@ func (o *Options) Verify() error {
} }
} }
if o.Serial != "" {
valid := isInArray(validSerialOptions, o.Serial)
if !valid {
return fmt.Errorf(`invalid serial option '%s': valid values are %s`,
o.Serial,
strings.Join(validSerialOptions, ", "))
}
}
if o.PrintSizes != "" { if o.PrintSizes != "" {
valid := isInArray(validPrintSizeOptions, o.PrintSizes) valid := isInArray(validPrintSizeOptions, o.PrintSizes)
if !valid { if !valid {
@@ -92,12 +73,6 @@ func (o *Options) Verify() error {
} }
} }
if o.Opt != "" {
if !isInArray(validOptOptions, o.Opt) {
return fmt.Errorf("invalid -opt=%s: valid values are %s", o.Opt, strings.Join(validOptOptions, ", "))
}
}
return nil return nil
} }
+72 -62
View File
@@ -31,7 +31,7 @@ type TargetSpec struct {
BuildTags []string `json:"build-tags"` BuildTags []string `json:"build-tags"`
GC string `json:"gc"` GC string `json:"gc"`
Scheduler string `json:"scheduler"` Scheduler string `json:"scheduler"`
Serial string `json:"serial"` // which serial output to use (uart, usb, none) Compiler string `json:"compiler"`
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"`
@@ -41,12 +41,10 @@ type TargetSpec struct {
LDFlags []string `json:"ldflags"` LDFlags []string `json:"ldflags"`
LinkerScript string `json:"linkerscript"` LinkerScript string `json:"linkerscript"`
ExtraFiles []string `json:"extra-files"` ExtraFiles []string `json:"extra-files"`
RP2040BootPatch *bool `json:"rp2040-boot-patch"` // Patch RP2040 2nd stage bootloader checksum
Emulator []string `json:"emulator" override:"copy"` // inherited Emulator must not be append Emulator []string `json:"emulator" override:"copy"` // inherited Emulator must not be append
FlashCommand string `json:"flash-command"` FlashCommand string `json:"flash-command"`
GDB []string `json:"gdb"` GDB []string `json:"gdb"`
PortReset string `json:"flash-1200-bps-reset"` PortReset string `json:"flash-1200-bps-reset"`
SerialPort []string `json:"serial-port"` // serial port IDs in the form "acm:vid:pid" or "usb:vid:pid"
FlashMethod string `json:"flash-method"` FlashMethod string `json:"flash-method"`
FlashVolume string `json:"msd-volume-name"` FlashVolume string `json:"msd-volume-name"`
FlashFilename string `json:"msd-firmware-name"` FlashFilename string `json:"msd-firmware-name"`
@@ -161,54 +159,75 @@ func (spec *TargetSpec) resolveInherits() error {
} }
// Load a target specification. // Load a target specification.
func LoadTarget(options *Options) (*TargetSpec, error) { func LoadTarget(target string) (*TargetSpec, error) {
if options.Target == "" { if target == "" {
// Configure based on GOOS/GOARCH environment variables (falling back to // Configure based on GOOS/GOARCH environment variables (falling back to
// runtime.GOOS/runtime.GOARCH), and generate a LLVM target based on it. // runtime.GOOS/runtime.GOARCH), and generate a LLVM target based on it.
goos := goenv.Get("GOOS")
goarch := goenv.Get("GOARCH")
llvmos := goos
llvmarch := map[string]string{ llvmarch := map[string]string{
"386": "i386", "386": "i386",
"amd64": "x86_64", "amd64": "x86_64",
"arm64": "aarch64", "arm64": "aarch64",
"arm": "armv7", }[goarch]
}[options.GOARCH]
if llvmarch == "" { if llvmarch == "" {
llvmarch = options.GOARCH llvmarch = goarch
} }
llvmos := options.GOOS target = llvmarch + "--" + llvmos
if llvmos == "darwin" { if goarch == "arm" {
// Use macosx* instead of darwin, otherwise darwin/arm64 will refer
// to iOS!
llvmos = "macosx10.12.0"
if llvmarch == "aarch64" {
// Looks like Apple prefers to call this architecture ARM64
// instead of AArch64.
llvmarch = "arm64"
}
}
// Target triples (which actually have four components, but are called
// triples for historical reasons) have the form:
// arch-vendor-os-environment
target := llvmarch + "-unknown-" + llvmos
if options.GOARCH == "arm" {
target += "-gnueabihf" target += "-gnueabihf"
} }
return defaultTarget(options.GOOS, options.GOARCH, target) return defaultTarget(goos, goarch, target)
} }
// See whether there is a target specification for this target (e.g. // See whether there is a target specification for this target (e.g.
// Arduino). // Arduino).
spec := &TargetSpec{} spec := &TargetSpec{}
err := spec.loadFromGivenStr(options.Target) err := spec.loadFromGivenStr(target)
if err != nil { if err == nil {
// Successfully loaded this target from a built-in .json file. Make sure
// it includes all parents as specified in the "inherits" key.
err = spec.resolveInherits()
if err != nil {
return nil, err
}
return spec, nil
} else if !os.IsNotExist(err) {
// Expected a 'file not found' error, got something else. Report it as
// an error.
return nil, err return nil, err
} else {
// Load target from given triple, ignore GOOS/GOARCH environment
// variables.
tripleSplit := strings.Split(target, "-")
if len(tripleSplit) < 3 {
return nil, errors.New("expected a full LLVM target or a custom target in -target flag")
}
if tripleSplit[0] == "arm" {
// LLVM and Clang have a different idea of what "arm" means, so
// upgrade to a slightly more modern ARM. In fact, when you pass
// --target=arm--linux-gnueabihf to Clang, it will convert that
// internally to armv7-unknown-linux-gnueabihf. Changing the
// architecture to armv7 will keep things consistent.
tripleSplit[0] = "armv7"
}
goos := tripleSplit[2]
if strings.HasPrefix(goos, "darwin") {
goos = "darwin"
}
goarch := map[string]string{ // map from LLVM arch to Go arch
"i386": "386",
"i686": "386",
"x86_64": "amd64",
"aarch64": "arm64",
"armv7": "arm",
}[tripleSplit[0]]
if goarch == "" {
goarch = tripleSplit[0]
}
return defaultTarget(goos, goarch, strings.Join(tripleSplit, "-"))
} }
// Successfully loaded this target from a built-in .json file. Make sure
// it includes all parents as specified in the "inherits" key.
err = spec.resolveInherits()
if err != nil {
return nil, err
}
return spec, nil
} }
// WindowsBuildNotSupportedErr is being thrown, when goos is windows and no target has been specified. // WindowsBuildNotSupportedErr is being thrown, when goos is windows and no target has been specified.
@@ -221,49 +240,40 @@ func defaultTarget(goos, goarch, triple string) (*TargetSpec, error) {
// No target spec available. Use the default one, useful on most systems // No target spec available. Use the default one, useful on most systems
// with a regular OS. // with a regular OS.
spec := TargetSpec{ spec := TargetSpec{
Triple: triple, Triple: triple,
GOOS: goos, GOOS: goos,
GOARCH: goarch, GOARCH: goarch,
BuildTags: []string{goos, goarch}, BuildTags: []string{goos, goarch},
Scheduler: "tasks", Compiler: "clang",
Linker: "cc", Linker: "cc",
DefaultStackSize: 1024 * 64, // 64kB CFlags: []string{"--target=" + triple},
GDB: []string{"gdb"}, GDB: []string{"gdb"},
PortReset: "false", PortReset: "false",
}
switch goarch {
case "386":
spec.CPU = "pentium4"
case "amd64":
spec.CPU = "x86-64"
case "arm":
spec.CPU = "generic"
case "arm64":
spec.CPU = "generic"
} }
if goos == "darwin" { if goos == "darwin" {
spec.CFlags = append(spec.CFlags, "-isysroot", "/Library/Developer/CommandLineTools/SDKs/MacOSX.sdk")
spec.LDFlags = append(spec.LDFlags, "-Wl,-dead_strip") spec.LDFlags = append(spec.LDFlags, "-Wl,-dead_strip")
} else if goos == "linux" {
spec.Linker = "ld.lld"
spec.RTLib = "compiler-rt"
spec.Libc = "musl"
spec.LDFlags = append(spec.LDFlags, "--gc-sections")
} else { } else {
spec.LDFlags = append(spec.LDFlags, "-no-pie", "-Wl,--gc-sections") // WARNING: clang < 5.0 requires -nopie spec.LDFlags = append(spec.LDFlags, "-no-pie", "-Wl,--gc-sections") // WARNING: clang < 5.0 requires -nopie
} }
if goarch != "wasm" { if goarch != "wasm" {
spec.ExtraFiles = append(spec.ExtraFiles, "src/runtime/gc_"+goarch+".S") spec.ExtraFiles = append(spec.ExtraFiles, "src/runtime/gc_"+goarch+".S")
spec.ExtraFiles = append(spec.ExtraFiles, "src/internal/task/task_stack_"+goarch+".S")
} }
if goarch != runtime.GOARCH { if goarch != runtime.GOARCH {
// Some educated guesses as to how to invoke helper programs. // Some educated guesses as to how to invoke helper programs.
spec.GDB = []string{"gdb-multiarch"} spec.GDB = []string{"gdb-multiarch"}
if goarch == "arm" && goos == "linux" { if goarch == "arm" && goos == "linux" {
spec.Emulator = []string{"qemu-arm"} spec.CFlags = append(spec.CFlags, "--sysroot=/usr/arm-linux-gnueabihf")
spec.Linker = "arm-linux-gnueabihf-gcc"
spec.Emulator = []string{"qemu-arm", "-L", "/usr/arm-linux-gnueabihf"}
} }
if goarch == "arm64" && goos == "linux" { if goarch == "arm64" && goos == "linux" {
spec.Emulator = []string{"qemu-aarch64"} spec.CFlags = append(spec.CFlags, "--sysroot=/usr/aarch64-linux-gnu")
spec.Linker = "aarch64-linux-gnu-gcc"
spec.Emulator = []string{"qemu-aarch64", "-L", "/usr/aarch64-linux-gnu"}
}
if goarch == "386" && runtime.GOARCH == "amd64" {
spec.CFlags = append(spec.CFlags, "-m32")
spec.LDFlags = append(spec.LDFlags, "-m32")
} }
} }
return &spec, nil return &spec, nil
+3 -4
View File
@@ -1,23 +1,22 @@
package compileopts package compileopts
import ( import (
"os"
"reflect" "reflect"
"testing" "testing"
) )
func TestLoadTarget(t *testing.T) { func TestLoadTarget(t *testing.T) {
_, err := LoadTarget(&Options{Target: "arduino"}) _, err := LoadTarget("arduino")
if err != nil { if err != nil {
t.Error("LoadTarget test failed:", err) t.Error("LoadTarget test failed:", err)
} }
_, err = LoadTarget(&Options{Target: "notexist"}) _, err = LoadTarget("notexist")
if err == nil { if err == nil {
t.Error("LoadTarget should have failed with non existing target") t.Error("LoadTarget should have failed with non existing target")
} }
if !os.IsNotExist(err) { if err.Error() != "expected a full LLVM target or a custom target in -target flag" {
t.Error("LoadTarget failed for wrong reason:", err) t.Error("LoadTarget failed for wrong reason:", err)
} }
} }
-102
View File
@@ -1,102 +0,0 @@
package compiler
// This file defines alias functions for functions that are normally defined in
// Go assembly.
//
// The Go toolchain defines many performance critical functions in assembly
// instead of plain Go. This is a problem for TinyGo as it currently (as of
// august 2021) is not able to compile these assembly files and even if it
// could, it would not be able to make use of them for many targets that are
// supported by TinyGo (baremetal RISC-V, AVR, etc). Therefore, many of these
// functions are aliased to their generic Go implementation.
// This results in slower than possible implementations, but at least they are
// usable.
import "tinygo.org/x/go-llvm"
var stdlibAliases = map[string]string{
// crypto packages
"crypto/md5.block": "crypto/md5.blockGeneric",
"crypto/sha1.block": "crypto/sha1.blockGeneric",
"crypto/sha1.blockAMD64": "crypto/sha1.blockGeneric",
"crypto/sha256.block": "crypto/sha256.blockGeneric",
"crypto/sha512.blockAMD64": "crypto/sha512.blockGeneric",
// math package
"math.Asin": "math.asin",
"math.Asinh": "math.asinh",
"math.Acos": "math.acos",
"math.Acosh": "math.acosh",
"math.Atan": "math.atan",
"math.Atanh": "math.atanh",
"math.Atan2": "math.atan2",
"math.Cbrt": "math.cbrt",
"math.Ceil": "math.ceil",
"math.archCeil": "math.ceil",
"math.Cos": "math.cos",
"math.Cosh": "math.cosh",
"math.Erf": "math.erf",
"math.Erfc": "math.erfc",
"math.Exp": "math.exp",
"math.archExp": "math.exp",
"math.Expm1": "math.expm1",
"math.Exp2": "math.exp2",
"math.archExp2": "math.exp2",
"math.Floor": "math.floor",
"math.archFloor": "math.floor",
"math.Frexp": "math.frexp",
"math.Hypot": "math.hypot",
"math.archHypot": "math.hypot",
"math.Ldexp": "math.ldexp",
"math.Log": "math.log",
"math.archLog": "math.log",
"math.Log1p": "math.log1p",
"math.Log10": "math.log10",
"math.Log2": "math.log2",
"math.Max": "math.max",
"math.archMax": "math.max",
"math.Min": "math.min",
"math.archMin": "math.min",
"math.Mod": "math.mod",
"math.Modf": "math.modf",
"math.archModf": "math.modf",
"math.Pow": "math.pow",
"math.Remainder": "math.remainder",
"math.Sin": "math.sin",
"math.Sinh": "math.sinh",
"math.Sqrt": "math.sqrt",
"math.archSqrt": "math.sqrt",
"math.Tan": "math.tan",
"math.Tanh": "math.tanh",
"math.Trunc": "math.trunc",
"math.archTrunc": "math.trunc",
}
// createAlias implements the function (in the builder) as a call to the alias
// function.
func (b *builder) createAlias(alias llvm.Value) {
b.llvmFn.SetVisibility(llvm.HiddenVisibility)
b.llvmFn.SetUnnamedAddr(true)
if b.Debug {
if b.fn.Syntax() != nil {
// Create debug info file if present.
b.difunc = b.attachDebugInfo(b.fn)
}
pos := b.program.Fset.Position(b.fn.Pos())
b.SetCurrentDebugLocation(uint(pos.Line), uint(pos.Column), b.difunc, llvm.Metadata{})
}
entryBlock := llvm.AddBasicBlock(b.llvmFn, "entry")
b.SetInsertPointAtEnd(entryBlock)
if b.llvmFn.Type() != alias.Type() {
b.addError(b.fn.Pos(), "alias function should have the same type as aliasee "+alias.Name())
b.CreateUnreachable()
return
}
result := b.CreateCall(alias, b.llvmFn.Params(), "")
if result.Type().TypeKind() == llvm.VoidTypeKind {
b.CreateRetVoid()
} else {
b.CreateRet(result)
}
}
+2 -66
View File
@@ -101,55 +101,6 @@ func (b *builder) createSliceBoundsCheck(capacity, low, high, max llvm.Value, lo
b.createRuntimeAssert(outOfBounds, "slice", "slicePanic") b.createRuntimeAssert(outOfBounds, "slice", "slicePanic")
} }
// createSliceToArrayPointerCheck adds a check for slice-to-array pointer
// conversions. This conversion was added in Go 1.17. For details, see:
// https://tip.golang.org/ref/spec#Conversions_from_slice_to_array_pointer
func (b *builder) createSliceToArrayPointerCheck(sliceLen llvm.Value, arrayLen int64) {
// From the spec:
// > If the length of the slice is less than the length of the array, a
// > run-time panic occurs.
arrayLenValue := llvm.ConstInt(b.uintptrType, uint64(arrayLen), false)
isLess := b.CreateICmp(llvm.IntULT, sliceLen, arrayLenValue, "")
b.createRuntimeAssert(isLess, "slicetoarray", "sliceToArrayPointerPanic")
}
// createUnsafeSliceCheck inserts a runtime check used for unsafe.Slice. This
// function must panic if the ptr/len parameters are invalid.
func (b *builder) createUnsafeSliceCheck(ptr, len llvm.Value, lenType *types.Basic) {
// From the documentation of unsafe.Slice:
// > At run time, if len is negative, or if ptr is nil and len is not
// > zero, a run-time panic occurs.
// However, in practice, it is also necessary to check that the length is
// not too big that a GEP wouldn't be possible without wrapping the pointer.
// These two checks (non-negative and not too big) can be merged into one
// using an unsiged greater than.
// Make sure the len value is at least as big as a uintptr.
if len.Type().IntTypeWidth() < b.uintptrType.IntTypeWidth() {
if lenType.Info()&types.IsUnsigned != 0 {
len = b.CreateZExt(len, b.uintptrType, "")
} else {
len = b.CreateSExt(len, b.uintptrType, "")
}
}
// Determine the maximum slice size, and therefore the maximum value of the
// len parameter.
maxSize := b.maxSliceSize(ptr.Type().ElementType())
maxSizeValue := llvm.ConstInt(len.Type(), maxSize, false)
// Do the check. By using unsigned greater than for the length check, signed
// negative values are also checked (which are very large numbers when
// interpreted as signed values).
zero := llvm.ConstInt(len.Type(), 0, false)
lenOutOfBounds := b.CreateICmp(llvm.IntUGT, len, maxSizeValue, "")
ptrIsNil := b.CreateICmp(llvm.IntEQ, ptr, llvm.ConstNull(ptr.Type()), "")
lenIsNotZero := b.CreateICmp(llvm.IntNE, len, zero, "")
assert := b.CreateAnd(ptrIsNil, lenIsNotZero, "")
assert = b.CreateOr(assert, lenOutOfBounds, "")
b.createRuntimeAssert(assert, "unsafe.Slice", "unsafeSlicePanic")
}
// 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) {
@@ -164,9 +115,11 @@ func (b *builder) createChanBoundsCheck(elementSize uint64, bufSize llvm.Value,
if bufSize.Type().IntTypeWidth() < b.uintptrType.IntTypeWidth() { if bufSize.Type().IntTypeWidth() < b.uintptrType.IntTypeWidth() {
if bufSizeType.Info()&types.IsUnsigned != 0 { if bufSizeType.Info()&types.IsUnsigned != 0 {
// Unsigned, so zero-extend to uint type. // Unsigned, so zero-extend to uint type.
bufSizeType = types.Typ[types.Uint]
bufSize = b.CreateZExt(bufSize, b.intType, "") bufSize = b.CreateZExt(bufSize, b.intType, "")
} else { } else {
// Signed, so sign-extend to int type. // Signed, so sign-extend to int type.
bufSizeType = types.Typ[types.Int]
bufSize = b.CreateSExt(bufSize, b.intType, "") bufSize = b.CreateSExt(bufSize, b.intType, "")
} }
} }
@@ -209,10 +162,6 @@ func (b *builder) createNilCheck(inst ssa.Value, ptr llvm.Value, blockPrefix str
case *ssa.Alloc: case *ssa.Alloc:
// An alloc is never nil. // An alloc is never nil.
return return
case *ssa.FreeVar:
// A free variable is allocated in a parent function and is thus never
// nil.
return
case *ssa.IndexAddr: case *ssa.IndexAddr:
// This pointer is the result of an index operation into a slice or // This pointer is the result of an index operation into a slice or
// array. Such slices/arrays are already bounds checked so the pointer // array. Such slices/arrays are already bounds checked so the pointer
@@ -253,19 +202,6 @@ func (b *builder) createNegativeShiftCheck(shift llvm.Value) {
b.createRuntimeAssert(isNegative, "shift", "negativeShiftPanic") b.createRuntimeAssert(isNegative, "shift", "negativeShiftPanic")
} }
// createDivideByZeroCheck asserts that y is not zero. If it is, a runtime panic
// will be emitted. This follows the Go specification which says that a divide
// by zero must cause a run time panic.
func (b *builder) createDivideByZeroCheck(y llvm.Value) {
if b.info.nobounds {
return
}
// isZero = y == 0
isZero := b.CreateICmp(llvm.IntEQ, y, llvm.ConstInt(y.Type(), 0, false), "")
b.createRuntimeAssert(isZero, "divbyzero", "divideByZeroPanic")
}
// createRuntimeAssert is a common function to create a new branch on an assert // createRuntimeAssert is a common function to create a new branch on an assert
// bool, calling an assert func if the assert value is true (1). // bool, calling an assert func if the assert value is true (1).
func (b *builder) createRuntimeAssert(assert llvm.Value, blockPrefix, assertFunc string) { func (b *builder) createRuntimeAssert(assert llvm.Value, blockPrefix, assertFunc string) {
+3 -2
View File
@@ -38,10 +38,11 @@ func (b *builder) createAtomicOp(call *ssa.CallCommon) (llvm.Value, bool) {
old := b.getValue(call.Args[1]) old := b.getValue(call.Args[1])
newVal := b.getValue(call.Args[2]) newVal := b.getValue(call.Args[2])
if strings.HasSuffix(name, "64") { if strings.HasSuffix(name, "64") {
if strings.HasPrefix(b.Triple, "thumb") { arch := strings.Split(b.Triple, "-")[0]
if strings.HasPrefix(arch, "arm") && strings.HasSuffix(arch, "m") {
// Work around a bug in LLVM, at least LLVM 11: // Work around a bug in LLVM, at least LLVM 11:
// https://reviews.llvm.org/D95891 // https://reviews.llvm.org/D95891
// Check for thumbv6m, thumbv7, thumbv7em, and perhaps others. // Check for armv6m, armv7, armv7em, and perhaps others.
// See also: https://gcc.gnu.org/onlinedocs/gcc/_005f_005fsync-Builtins.html // See also: https://gcc.gnu.org/onlinedocs/gcc/_005f_005fsync-Builtins.html
compareAndSwap := b.mod.NamedFunction("__sync_val_compare_and_swap_8") compareAndSwap := b.mod.NamedFunction("__sync_val_compare_and_swap_8")
if compareAndSwap.IsNil() { if compareAndSwap.IsNil() {
+2 -2
View File
@@ -63,10 +63,10 @@ func (c *compilerContext) expandFormalParamType(t llvm.Type, name string, goType
case llvm.StructTypeKind: case llvm.StructTypeKind:
fieldInfos := c.flattenAggregateType(t, name, goType) fieldInfos := c.flattenAggregateType(t, name, goType)
if len(fieldInfos) <= maxFieldsPerParam { if len(fieldInfos) <= maxFieldsPerParam {
// managed to expand this parameter
return fieldInfos return fieldInfos
} else {
// failed to lower
} }
// failed to expand this parameter: too many fields
} }
// TODO: split small arrays // TODO: split small arrays
return []paramInfo{ return []paramInfo{
+9 -28
View File
@@ -32,15 +32,8 @@ func (b *builder) createChanSend(instr *ssa.Send) {
// store value-to-send // store value-to-send
valueType := b.getLLVMType(instr.X.Type()) valueType := b.getLLVMType(instr.X.Type())
isZeroSize := b.targetData.TypeAllocSize(valueType) == 0 valueAlloca, valueAllocaCast, valueAllocaSize := b.createTemporaryAlloca(valueType, "chan.value")
var valueAlloca, valueAllocaCast, valueAllocaSize llvm.Value b.CreateStore(chanValue, valueAlloca)
if isZeroSize {
valueAlloca = llvm.ConstNull(llvm.PointerType(valueType, 0))
valueAllocaCast = llvm.ConstNull(b.i8ptrType)
} else {
valueAlloca, valueAllocaCast, valueAllocaSize = b.createTemporaryAlloca(valueType, "chan.value")
b.CreateStore(chanValue, valueAlloca)
}
// Allocate blockedlist buffer. // Allocate blockedlist buffer.
channelBlockedList := b.mod.GetTypeByName("runtime.channelBlockedList") channelBlockedList := b.mod.GetTypeByName("runtime.channelBlockedList")
@@ -53,9 +46,7 @@ func (b *builder) createChanSend(instr *ssa.Send) {
// 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(channelBlockedListAllocaCast, channelBlockedListAllocaSize)
if !isZeroSize { 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
@@ -65,14 +56,7 @@ func (b *builder) createChanRecv(unop *ssa.UnOp) llvm.Value {
ch := b.getValue(unop.X) ch := b.getValue(unop.X)
// Allocate memory to receive into. // Allocate memory to receive into.
isZeroSize := b.targetData.TypeAllocSize(valueType) == 0 valueAlloca, valueAllocaCast, valueAllocaSize := b.createTemporaryAlloca(valueType, "chan.value")
var valueAlloca, valueAllocaCast, valueAllocaSize llvm.Value
if isZeroSize {
valueAlloca = llvm.ConstNull(llvm.PointerType(valueType, 0))
valueAllocaCast = llvm.ConstNull(b.i8ptrType)
} else {
valueAlloca, valueAllocaCast, valueAllocaSize = b.createTemporaryAlloca(valueType, "chan.value")
}
// Allocate blockedlist buffer. // Allocate blockedlist buffer.
channelBlockedList := b.mod.GetTypeByName("runtime.channelBlockedList") channelBlockedList := b.mod.GetTypeByName("runtime.channelBlockedList")
@@ -80,14 +64,9 @@ func (b *builder) createChanRecv(unop *ssa.UnOp) llvm.Value {
// Do the receive. // Do the receive.
commaOk := b.createRuntimeCall("chanRecv", []llvm.Value{ch, valueAllocaCast, channelBlockedListAlloca}, "") commaOk := b.createRuntimeCall("chanRecv", []llvm.Value{ch, valueAllocaCast, channelBlockedListAlloca}, "")
var received llvm.Value received := b.CreateLoad(valueAlloca, "chan.received")
if isZeroSize {
received = llvm.ConstNull(valueType)
} else {
received = b.CreateLoad(valueAlloca, "chan.received")
b.emitLifetimeEnd(valueAllocaCast, valueAllocaSize)
}
b.emitLifetimeEnd(channelBlockedListAllocaCast, channelBlockedListAllocaSize) b.emitLifetimeEnd(channelBlockedListAllocaCast, channelBlockedListAllocaSize)
b.emitLifetimeEnd(valueAllocaCast, valueAllocaSize)
if unop.CommaOk { if unop.CommaOk {
tuple := llvm.Undef(b.ctx.StructType([]llvm.Type{valueType, b.ctx.Int1Type()}, false)) tuple := llvm.Undef(b.ctx.StructType([]llvm.Type{valueType, b.ctx.Int1Type()}, false))
@@ -137,6 +116,7 @@ func (b *builder) createSelect(expr *ssa.Select) llvm.Value {
// determine the receive buffer size and alignment. // determine the receive buffer size and alignment.
recvbufSize := uint64(0) recvbufSize := uint64(0)
recvbufAlign := 0 recvbufAlign := 0
hasReceives := false
var selectStates []llvm.Value var selectStates []llvm.Value
chanSelectStateType := b.getLLVMRuntimeType("chanSelectState") chanSelectStateType := b.getLLVMRuntimeType("chanSelectState")
for _, state := range expr.States { for _, state := range expr.States {
@@ -153,6 +133,7 @@ func (b *builder) createSelect(expr *ssa.Select) llvm.Value {
if align := b.targetData.ABITypeAlignment(llvmType); align > recvbufAlign { if align := b.targetData.ABITypeAlignment(llvmType); align > recvbufAlign {
recvbufAlign = align recvbufAlign = align
} }
hasReceives = true
case types.SendOnly: case types.SendOnly:
// Store this value in an alloca and put a pointer to this alloca // Store this value in an alloca and put a pointer to this alloca
// in the send state. // in the send state.
@@ -169,7 +150,7 @@ func (b *builder) createSelect(expr *ssa.Select) llvm.Value {
// Create a receive buffer, where the received value will be stored. // Create a receive buffer, where the received value will be stored.
recvbuf := llvm.Undef(b.i8ptrType) recvbuf := llvm.Undef(b.i8ptrType)
if recvbufSize != 0 { if hasReceives {
allocaType := llvm.ArrayType(b.ctx.Int8Type(), int(recvbufSize)) allocaType := llvm.ArrayType(b.ctx.Int8Type(), int(recvbufSize))
recvbufAlloca, _, _ := b.createTemporaryAlloca(allocaType, "select.recvbuf.alloca") recvbufAlloca, _, _ := b.createTemporaryAlloca(allocaType, "select.recvbuf.alloca")
recvbufAlloca.SetAlignment(recvbufAlign) recvbufAlloca.SetAlignment(recvbufAlign)
+114 -300
View File
@@ -23,7 +23,7 @@ import (
// Version of the compiler pacakge. Must be incremented each time the compiler // Version of the compiler pacakge. Must be incremented each time the compiler
// package changes in a way that affects the generated LLVM module. // package changes in a way that affects the generated LLVM module.
// This version is independent of the TinyGo version number. // This version is independent of the TinyGo version number.
const Version = 24 // last change: add layout param to runtime.alloc calls const Version = 6 // last change: fix issue 1304
func init() { func init() {
llvm.InitializeAllTargets() llvm.InitializeAllTargets()
@@ -33,6 +33,9 @@ func init() {
llvm.InitializeAllAsmPrinters() llvm.InitializeAllAsmPrinters()
} }
// The TinyGo import path.
const tinygoPath = "github.com/tinygo-org/tinygo"
// Config is the configuration for the compiler. Most settings should be copied // Config is the configuration for the compiler. Most settings should be copied
// directly from compileopts.Config, it recreated here to decouple the compiler // directly from compileopts.Config, it recreated here to decouple the compiler
// package a bit and because it makes caching easier. // package a bit and because it makes caching easier.
@@ -48,7 +51,6 @@ type Config struct {
GOARCH string GOARCH string
CodeModel string CodeModel string
RelocationModel string RelocationModel string
SizeLevel int
// Various compiler options that determine how code is generated. // Various compiler options that determine how code is generated.
Scheduler string Scheduler string
@@ -57,7 +59,6 @@ type Config struct {
DefaultStackSize uint64 DefaultStackSize uint64
NeedsStackObjects bool NeedsStackObjects bool
Debug bool // Whether to emit debug information in the LLVM module. Debug bool // Whether to emit debug information in the LLVM module.
LLVMFeatures string
} }
// compilerContext contains function-independent data that should still be // compilerContext contains function-independent data that should still be
@@ -72,18 +73,15 @@ type compilerContext struct {
cu llvm.Metadata cu llvm.Metadata
difiles map[string]llvm.Metadata difiles map[string]llvm.Metadata
ditypes map[types.Type]llvm.Metadata ditypes map[types.Type]llvm.Metadata
llvmTypes map[types.Type]llvm.Type
machine llvm.TargetMachine machine llvm.TargetMachine
targetData llvm.TargetData targetData llvm.TargetData
intType llvm.Type intType llvm.Type
i8ptrType llvm.Type // for convenience i8ptrType llvm.Type // for convenience
rawVoidFuncType llvm.Type // for convenience
funcPtrAddrSpace int funcPtrAddrSpace int
uintptrType llvm.Type uintptrType llvm.Type
program *ssa.Program program *ssa.Program
diagnostics []error diagnostics []error
astComments map[string]*ast.CommentGroup astComments map[string]*ast.CommentGroup
pkg *types.Package
runtimePkg *types.Package runtimePkg *types.Package
} }
@@ -95,7 +93,6 @@ func newCompilerContext(moduleName string, machine llvm.TargetMachine, config *C
DumpSSA: dumpSSA, DumpSSA: dumpSSA,
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),
llvmTypes: make(map[types.Type]llvm.Type),
machine: machine, machine: machine,
targetData: machine.CreateTargetData(), targetData: machine.CreateTargetData(),
astComments: map[string]*ast.CommentGroup{}, astComments: map[string]*ast.CommentGroup{},
@@ -124,7 +121,6 @@ func newCompilerContext(moduleName string, machine llvm.TargetMachine, config *C
dummyFuncType := llvm.FunctionType(c.ctx.VoidType(), nil, false) dummyFuncType := llvm.FunctionType(c.ctx.VoidType(), nil, false)
dummyFunc := llvm.AddFunction(c.mod, "tinygo.dummy", dummyFuncType) dummyFunc := llvm.AddFunction(c.mod, "tinygo.dummy", dummyFuncType)
c.funcPtrAddrSpace = dummyFunc.Type().PointerAddressSpace() c.funcPtrAddrSpace = dummyFunc.Type().PointerAddressSpace()
c.rawVoidFuncType = dummyFunc.Type()
dummyFunc.EraseFromParentAsFunction() dummyFunc.EraseFromParentAsFunction()
return c return c
@@ -142,6 +138,7 @@ type builder struct {
blockExits map[*ssa.BasicBlock]llvm.BasicBlock // these are the exit blocks blockExits map[*ssa.BasicBlock]llvm.BasicBlock // these are the exit blocks
currentBlock *ssa.BasicBlock currentBlock *ssa.BasicBlock
phis []phiNode phis []phiNode
taskHandle llvm.Value
deferPtr llvm.Value deferPtr llvm.Value
difunc llvm.Metadata difunc llvm.Metadata
dilocals map[*types.Var]llvm.Metadata dilocals map[*types.Var]llvm.Metadata
@@ -188,12 +185,7 @@ func NewTargetMachine(config *Config) (llvm.TargetMachine, error) {
if err != nil { if err != nil {
return llvm.TargetMachine{}, err return llvm.TargetMachine{}, err
} }
features := strings.Join(config.Features, ",")
feat := config.Features
if len(config.LLVMFeatures) > 0 {
feat = append(feat, config.LLVMFeatures)
}
features := strings.Join(feat, ",")
var codeModel llvm.CodeModel var codeModel llvm.CodeModel
var relocationModel llvm.RelocMode var relocationModel llvm.RelocMode
@@ -233,6 +225,10 @@ func Sizes(machine llvm.TargetMachine) types.Sizes {
targetData := machine.CreateTargetData() targetData := machine.CreateTargetData()
defer targetData.Dispose() defer targetData.Dispose()
intPtrType := targetData.IntPtrType()
if intPtrType.IntTypeWidth()/8 <= 32 {
}
var intWidth int var intWidth int
if targetData.PointerSize() <= 4 { if targetData.PointerSize() <= 4 {
// 8, 16, 32 bits targets // 8, 16, 32 bits targets
@@ -247,14 +243,13 @@ func Sizes(machine llvm.TargetMachine) types.Sizes {
return &stdSizes{ return &stdSizes{
IntSize: int64(intWidth / 8), IntSize: int64(intWidth / 8),
PtrSize: int64(targetData.PointerSize()), PtrSize: int64(targetData.PointerSize()),
MaxAlign: int64(targetData.PrefTypeAlignment(targetData.IntPtrType())), MaxAlign: int64(targetData.PrefTypeAlignment(intPtrType)),
} }
} }
// CompilePackage compiles a single package to a LLVM module. // CompilePackage compiles a single package to a LLVM module.
func CompilePackage(moduleName string, pkg *loader.Package, ssaPkg *ssa.Package, machine llvm.TargetMachine, config *Config, dumpSSA bool) (llvm.Module, []error) { func CompilePackage(moduleName string, pkg *loader.Package, ssaPkg *ssa.Package, machine llvm.TargetMachine, config *Config, dumpSSA bool) (llvm.Module, []error) {
c := newCompilerContext(moduleName, machine, config, dumpSSA) c := newCompilerContext(moduleName, machine, config, dumpSSA)
c.pkg = pkg.Pkg
c.runtimePkg = ssaPkg.Prog.ImportedPackage("runtime").Pkg c.runtimePkg = ssaPkg.Prog.ImportedPackage("runtime").Pkg
c.program = ssaPkg.Prog c.program = ssaPkg.Prog
@@ -288,14 +283,14 @@ func CompilePackage(moduleName string, pkg *loader.Package, ssaPkg *ssa.Package,
if c.Debug { if c.Debug {
c.mod.AddNamedMetadataOperand("llvm.module.flags", c.mod.AddNamedMetadataOperand("llvm.module.flags",
c.ctx.MDNode([]llvm.Metadata{ c.ctx.MDNode([]llvm.Metadata{
llvm.ConstInt(c.ctx.Int32Type(), 2, false).ConstantAsMetadata(), // Warning on mismatch llvm.ConstInt(c.ctx.Int32Type(), 1, false).ConstantAsMetadata(), // Error on mismatch
c.ctx.MDString("Debug Info Version"), c.ctx.MDString("Debug Info Version"),
llvm.ConstInt(c.ctx.Int32Type(), 3, false).ConstantAsMetadata(), // DWARF version llvm.ConstInt(c.ctx.Int32Type(), 3, false).ConstantAsMetadata(), // DWARF version
}), }),
) )
c.mod.AddNamedMetadataOperand("llvm.module.flags", c.mod.AddNamedMetadataOperand("llvm.module.flags",
c.ctx.MDNode([]llvm.Metadata{ c.ctx.MDNode([]llvm.Metadata{
llvm.ConstInt(c.ctx.Int32Type(), 7, false).ConstantAsMetadata(), // Max on mismatch llvm.ConstInt(c.ctx.Int32Type(), 1, false).ConstantAsMetadata(),
c.ctx.MDString("Dwarf Version"), c.ctx.MDString("Dwarf Version"),
llvm.ConstInt(c.ctx.Int32Type(), 4, false).ConstantAsMetadata(), llvm.ConstInt(c.ctx.Int32Type(), 4, false).ConstantAsMetadata(),
}), }),
@@ -303,7 +298,7 @@ func CompilePackage(moduleName string, pkg *loader.Package, ssaPkg *ssa.Package,
c.dibuilder.Finalize() c.dibuilder.Finalize()
} }
return c.mod, c.diagnostics return c.mod, nil
} }
// getLLVMRuntimeType obtains a named type from the runtime package and returns // getLLVMRuntimeType obtains a named type from the runtime package and returns
@@ -314,23 +309,10 @@ func (c *compilerContext) getLLVMRuntimeType(name string) llvm.Type {
return c.getLLVMType(typ) return c.getLLVMType(typ)
} }
// getLLVMType returns a LLVM type for a Go type. It doesn't recreate already // getLLVMType creates and returns a LLVM type for a Go type. In the case of
// created types. This is somewhat important for performance, but especially // named struct types (or Go types implemented as named LLVM structs such as
// important for named struct types (which should only be created once). // strings) it also creates it first if necessary.
func (c *compilerContext) getLLVMType(goType types.Type) llvm.Type { func (c *compilerContext) getLLVMType(goType types.Type) llvm.Type {
// Try to load the LLVM type from the cache.
if t, ok := c.llvmTypes[goType]; ok {
return t
}
// Not already created, so adding this type to the cache.
llvmType := c.makeLLVMType(goType)
c.llvmTypes[goType] = llvmType
return llvmType
}
// makeLLVMType creates a LLVM type for a Go type. Don't call this, use
// getLLVMType instead.
func (c *compilerContext) makeLLVMType(goType types.Type) llvm.Type {
switch typ := goType.(type) { switch typ := goType.(type) {
case *types.Array: case *types.Array:
elemType := c.getLLVMType(typ.Elem()) elemType := c.getLLVMType(typ.Elem())
@@ -379,10 +361,12 @@ func (c *compilerContext) makeLLVMType(goType types.Type) llvm.Type {
// LLVM. This is because it is otherwise impossible to create // LLVM. This is because it is otherwise impossible to create
// self-referencing types such as linked lists. // self-referencing types such as linked lists.
llvmName := typ.Obj().Pkg().Path() + "." + typ.Obj().Name() llvmName := typ.Obj().Pkg().Path() + "." + typ.Obj().Name()
llvmType := c.ctx.StructCreateNamed(llvmName) llvmType := c.mod.GetTypeByName(llvmName)
c.llvmTypes[goType] = llvmType // avoid infinite recursion if llvmType.IsNil() {
underlying := c.getLLVMType(st) llvmType = c.ctx.StructCreateNamed(llvmName)
llvmType.StructSetBody(underlying.StructElementTypes(), false) underlying := c.getLLVMType(st)
llvmType.StructSetBody(underlying.StructElementTypes(), false)
}
return llvmType return llvmType
} }
return c.getLLVMType(typ.Underlying()) return c.getLLVMType(typ.Underlying())
@@ -449,7 +433,7 @@ func (c *compilerContext) createDIType(typ types.Type) llvm.Metadata {
AlignInBits: uint32(c.targetData.ABITypeAlignment(llvmType)) * 8, AlignInBits: uint32(c.targetData.ABITypeAlignment(llvmType)) * 8,
ElementType: c.getDIType(typ.Elem()), ElementType: c.getDIType(typ.Elem()),
Subscripts: []llvm.DISubrange{ Subscripts: []llvm.DISubrange{
{ llvm.DISubrange{
Lo: 0, Lo: 0,
Count: typ.Len(), Count: typ.Len(),
}, },
@@ -725,11 +709,11 @@ func (c *compilerContext) createPackage(irbuilder llvm.Builder, pkg *ssa.Package
member := pkg.Members[name] member := pkg.Members[name]
switch member := member.(type) { switch member := member.(type) {
case *ssa.Function: case *ssa.Function:
// Create the function definition.
b := newBuilder(c, irbuilder, member)
if member.Blocks == nil { if member.Blocks == nil {
continue // external function continue // external function
} }
// Create the function definition.
b := newBuilder(c, irbuilder, member)
b.createFunction() b.createFunction()
case *ssa.Type: case *ssa.Type:
if types.IsInterface(member.Type()) { if types.IsInterface(member.Type()) {
@@ -768,36 +752,10 @@ func (c *compilerContext) createPackage(irbuilder llvm.Builder, pkg *ssa.Package
case *ssa.Global: case *ssa.Global:
// Global variable. // Global variable.
info := c.getGlobalInfo(member) info := c.getGlobalInfo(member)
global := c.getGlobal(member)
if !info.extern { if !info.extern {
global := c.getGlobal(member)
global.SetInitializer(llvm.ConstNull(global.Type().ElementType())) global.SetInitializer(llvm.ConstNull(global.Type().ElementType()))
global.SetVisibility(llvm.HiddenVisibility) global.SetVisibility(llvm.HiddenVisibility)
if info.section != "" {
global.SetSection(info.section)
}
}
}
}
// Add forwarding functions for functions that would otherwise be
// implemented in assembly.
for _, name := range members {
member := pkg.Members[name]
switch member := member.(type) {
case *ssa.Function:
if member.Blocks != nil {
continue // external function
}
info := c.getFunctionInfo(member)
if aliasName, ok := stdlibAliases[info.linkName]; ok {
alias := c.mod.NamedFunction(aliasName)
if alias.IsNil() {
// Shouldn't happen, but perhaps best to just ignore.
// The error will be a link error, if there is an error.
continue
}
b := newBuilder(c, irbuilder, member)
b.createAlias(alias)
} }
} }
} }
@@ -819,14 +777,10 @@ func (b *builder) createFunction() {
b.addError(b.fn.Pos(), errValue) b.addError(b.fn.Pos(), errValue)
return return
} }
b.addStandardDefinedAttributes(b.llvmFn)
if !b.info.exported { if !b.info.exported {
b.llvmFn.SetVisibility(llvm.HiddenVisibility) b.llvmFn.SetVisibility(llvm.HiddenVisibility)
b.llvmFn.SetUnnamedAddr(true) b.llvmFn.SetUnnamedAddr(true)
} }
if b.info.section != "" {
b.llvmFn.SetSection(b.info.section)
}
if b.info.exported && strings.HasPrefix(b.Triple, "wasm") { if b.info.exported && strings.HasPrefix(b.Triple, "wasm") {
// Set the exported name. This is necessary for WebAssembly because // Set the exported name. This is necessary for WebAssembly because
// otherwise the function is not exported. // otherwise the function is not exported.
@@ -846,17 +800,6 @@ func (b *builder) createFunction() {
b.llvmFn.AddFunctionAttr(noinline) b.llvmFn.AddFunctionAttr(noinline)
} }
if b.info.interrupt {
// Mark this function as an interrupt.
// This is necessary on MCUs that don't push caller saved registers when
// entering an interrupt, such as on AVR.
if strings.HasPrefix(b.Triple, "avr") {
b.llvmFn.AddFunctionAttr(b.ctx.CreateStringAttribute("signal", ""))
} else {
b.addError(b.fn.Pos(), "//go:interrupt not supported on this architecture")
}
}
// Add debug info, if needed. // Add debug info, if needed.
if b.Debug { if b.Debug {
if b.fn.Synthetic == "package initializer" { if b.fn.Synthetic == "package initializer" {
@@ -1021,61 +964,11 @@ func (b *builder) createFunction() {
} }
} }
// posser is an interface that's implemented by both ssa.Value and
// ssa.Instruction. It is implemented by everything that has a Pos() method,
// which is all that getPos() needs.
type posser interface {
Pos() token.Pos
}
// getPos returns position information for a ssa.Value or ssa.Instruction.
//
// Not all instructions have position information, especially when they're
// implicit (such as implicit casts or implicit returns at the end of a
// function). In these cases, it makes sense to try a bit harder to guess what
// the position really should be.
func getPos(val posser) token.Pos {
pos := val.Pos()
if pos != token.NoPos {
// Easy: position is known.
return pos
}
// No position information is known.
switch val := val.(type) {
case *ssa.MakeInterface:
return getPos(val.X)
case *ssa.MakeClosure:
return val.Fn.(*ssa.Function).Pos()
case *ssa.Return:
syntax := val.Parent().Syntax()
if syntax != nil {
// non-synthetic
return syntax.End()
}
return token.NoPos
case *ssa.FieldAddr:
return getPos(val.X)
case *ssa.IndexAddr:
return getPos(val.X)
case *ssa.Slice:
return getPos(val.X)
case *ssa.Store:
return getPos(val.Addr)
case *ssa.Extract:
return getPos(val.Tuple)
default:
// This is reachable, for example with *ssa.Const, *ssa.If, and
// *ssa.Jump. They might be implemented in some way in the future.
return token.NoPos
}
}
// 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.program.Fset.Position(getPos(instr)) 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{})
} }
@@ -1101,8 +994,52 @@ func (b *builder) createInstruction(instr ssa.Instruction) {
case *ssa.Defer: case *ssa.Defer:
b.createDefer(instr) b.createDefer(instr)
case *ssa.Go: case *ssa.Go:
// Get all function parameters to pass to the goroutine.
var params []llvm.Value
for _, param := range instr.Call.Args {
params = append(params, b.getValue(param))
}
// Start a new goroutine. // Start a new goroutine.
b.createGo(instr) if callee := instr.Call.StaticCallee(); callee != nil {
// Static callee is known. This makes it easier to start a new
// goroutine.
var context llvm.Value
switch value := instr.Call.Value.(type) {
case *ssa.Function:
// Goroutine call is regular function call. No context is necessary.
context = llvm.Undef(b.i8ptrType)
case *ssa.MakeClosure:
// A goroutine call on a func value, but the callee is trivial to find. For
// example: immediately applied functions.
funcValue := b.getValue(value)
context = b.extractFuncContext(funcValue)
default:
panic("StaticCallee returned an unexpected value")
}
params = append(params, context) // context parameter
b.createGoInstruction(b.getFunction(callee), params, "", callee.Pos())
} else if !instr.Call.IsInvoke() {
// This is a function pointer.
// At the moment, two extra params are passed to the newly started
// goroutine:
// * The function context, for closures.
// * The function pointer (for tasks).
funcPtr, context := b.decodeFuncValue(b.getValue(instr.Call.Value), instr.Call.Value.Type().Underlying().(*types.Signature))
params = append(params, context) // context parameter
switch b.Scheduler {
case "none", "coroutines":
// There are no additional parameters needed for the goroutine start operation.
case "tasks":
// Add the function pointer as a parameter to start the goroutine.
params = append(params, funcPtr)
default:
panic("unknown scheduler type")
}
b.createGoInstruction(funcPtr, params, b.fn.RelString(nil), instr.Pos())
} else {
b.addError(instr.Pos(), "todo: go on interface call")
}
case *ssa.If: case *ssa.If:
cond := b.getValue(instr.Cond) cond := b.getValue(instr.Cond)
block := instr.Block() block := instr.Block()
@@ -1315,38 +1252,6 @@ func (b *builder) createBuiltin(argTypes []types.Type, argValues []llvm.Value, c
case "ssa:wrapnilchk": case "ssa:wrapnilchk":
// TODO: do an actual nil check? // TODO: do an actual nil check?
return argValues[0], nil return argValues[0], nil
// Builtins from the unsafe package.
case "Add": // unsafe.Add
// This is basically just a GEP operation.
// Note: the pointer is always of type *i8.
ptr := argValues[0]
len := argValues[1]
return b.CreateGEP(ptr, []llvm.Value{len}, ""), nil
case "Slice": // unsafe.Slice
// This creates a slice from a pointer and a length.
// Note that the exception mentioned in the documentation (if the
// pointer and length are nil, the slice is also nil) is trivially
// already the case.
ptr := argValues[0]
len := argValues[1]
slice := llvm.Undef(b.ctx.StructType([]llvm.Type{
ptr.Type(),
b.uintptrType,
b.uintptrType,
}, false))
b.createUnsafeSliceCheck(ptr, len, argTypes[1].Underlying().(*types.Basic))
if len.Type().IntTypeWidth() < b.uintptrType.IntTypeWidth() {
// Too small, zero-extend len.
len = b.CreateZExt(len, b.uintptrType, "")
} else if len.Type().IntTypeWidth() > b.uintptrType.IntTypeWidth() {
// Too big, truncate len.
len = b.CreateTrunc(len, b.uintptrType, "")
}
slice = b.CreateInsertValue(slice, ptr, 0, "")
slice = b.CreateInsertValue(slice, len, 1, "")
slice = b.CreateInsertValue(slice, len, 2, "")
return slice, nil
default: default:
return llvm.Value{}, b.makeError(pos, "todo: builtin: "+callName) return llvm.Value{}, b.makeError(pos, "todo: builtin: "+callName)
} }
@@ -1358,9 +1263,9 @@ func (b *builder) createBuiltin(argTypes []types.Type, argValues []llvm.Value, c
// //
// This is also where compiler intrinsics are implemented. // This is also where compiler intrinsics are implemented.
func (b *builder) createFunctionCall(instr *ssa.CallCommon) (llvm.Value, error) { func (b *builder) createFunctionCall(instr *ssa.CallCommon) (llvm.Value, error) {
var params []llvm.Value if instr.IsInvoke() {
for _, param := range instr.Args { fnCast, args := b.getInvokeCall(instr)
params = append(params, b.getValue(param)) return b.createCall(fnCast, args, ""), nil
} }
// Try to call the function directly for trivially static calls. // Try to call the function directly for trivially static calls.
@@ -1375,11 +1280,6 @@ 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 == "math.Ceil" || name == "math.Floor" || name == "math.Sqrt" || name == "math.Trunc":
result, ok := b.createMathOp(instr)
if ok {
return result, nil
}
case name == "device.Asm" || name == "device/arm.Asm" || name == "device/arm64.Asm" || name == "device/avr.Asm" || name == "device/riscv.Asm": case name == "device.Asm" || name == "device/arm.Asm" || name == "device/arm64.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/arm64.AsmFull" || name == "device/avr.AsmFull" || name == "device/riscv.AsmFull": case name == "device.AsmFull" || name == "device/arm.AsmFull" || name == "device/arm64.AsmFull" || name == "device/avr.AsmFull" || name == "device/riscv.AsmFull":
@@ -1392,8 +1292,6 @@ func (b *builder) createFunctionCall(instr *ssa.CallCommon) (llvm.Value, error)
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, "syscall.rawSyscallNoError"):
return b.createRawSyscallNoError(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"):
@@ -1431,20 +1329,12 @@ func (b *builder) createFunctionCall(instr *ssa.CallCommon) (llvm.Value, error)
} 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.).)
var argTypes []types.Type var argTypes []types.Type
var argValues []llvm.Value
for _, arg := range instr.Args { for _, arg := range instr.Args {
argTypes = append(argTypes, arg.Type()) argTypes = append(argTypes, arg.Type())
argValues = append(argValues, b.getValue(arg))
} }
return b.createBuiltin(argTypes, params, call.Name(), instr.Pos()) return b.createBuiltin(argTypes, argValues, call.Name(), instr.Pos())
} else if instr.IsInvoke() {
// Interface method call (aka invoke call).
itf := b.getValue(instr.Value) // interface value (runtime._interface)
typecode := b.CreateExtractValue(itf, 0, "invoke.func.typecode")
value := b.CreateExtractValue(itf, 1, "invoke.func.value") // receiver
// Prefix the params with receiver value and suffix with typecode.
params = append([]llvm.Value{value}, params...)
params = append(params, typecode)
callee = b.getInvokeFunction(instr)
context = llvm.Undef(b.i8ptrType)
} else { } else {
// Function pointer. // Function pointer.
value := b.getValue(instr.Value) value := b.getValue(instr.Value)
@@ -1454,6 +1344,11 @@ func (b *builder) createFunctionCall(instr *ssa.CallCommon) (llvm.Value, error)
b.createNilCheck(instr.Value, callee, "fpcall") b.createNilCheck(instr.Value, callee, "fpcall")
} }
var params []llvm.Value
for _, param := range instr.Args {
params = append(params, b.getValue(param))
}
if !exported { if !exported {
// This function takes a context parameter. // This function takes a context parameter.
// Add it to the end of the parameter list. // Add it to the end of the parameter list.
@@ -1471,7 +1366,7 @@ 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(expr) return b.createConst(b.info.linkName, expr)
case *ssa.Function: case *ssa.Function:
if b.getFunctionInfo(expr).exported { if b.getFunctionInfo(expr).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())
@@ -1496,28 +1391,6 @@ func (b *builder) getValue(expr ssa.Value) llvm.Value {
} }
} }
// maxSliceSize determines the maximum size a slice of the given element type
// can be.
func (c *compilerContext) maxSliceSize(elementType llvm.Type) uint64 {
// Calculate ^uintptr(0), which is the max value that fits in uintptr.
maxPointerValue := llvm.ConstNot(llvm.ConstInt(c.uintptrType, 0, false)).ZExtValue()
// Calculate (^uint(0))/2, which is the max value that fits in an int.
maxIntegerValue := llvm.ConstNot(llvm.ConstInt(c.intType, 0, false)).ZExtValue() / 2
// Determine the maximum allowed size for a slice. The biggest possible
// pointer (starting from 0) would be maxPointerValue*sizeof(elementType) so
// divide by the element type to get the real maximum size.
maxSize := maxPointerValue / c.targetData.TypeAllocSize(elementType)
// len(slice) is an int. Make sure the length remains small enough to fit in
// an int.
if maxSize > maxIntegerValue {
maxSize = maxIntegerValue
}
return maxSize
}
// createExpr translates a Go SSA expression to LLVM IR. This can be zero, one, // createExpr translates a Go SSA expression to LLVM IR. This can be zero, one,
// or multiple LLVM IR instructions and/or runtime calls. // or multiple LLVM IR instructions and/or runtime calls.
func (b *builder) createExpr(expr ssa.Value) (llvm.Value, error) { func (b *builder) createExpr(expr ssa.Value) (llvm.Value, error) {
@@ -1538,8 +1411,7 @@ func (b *builder) createExpr(expr ssa.Value) (llvm.Value, error) {
return llvm.Value{}, b.makeError(expr.Pos(), fmt.Sprintf("value is too big (%v bytes)", size)) return llvm.Value{}, b.makeError(expr.Pos(), fmt.Sprintf("value is too big (%v bytes)", size))
} }
sizeValue := llvm.ConstInt(b.uintptrType, size, false) sizeValue := llvm.ConstInt(b.uintptrType, size, false)
layoutValue := b.createObjectLayout(typ, expr.Pos()) buf := b.createRuntimeCall("alloc", []llvm.Value{sizeValue}, expr.Comment)
buf := b.createRuntimeCall("alloc", []llvm.Value{sizeValue, layoutValue}, expr.Comment)
buf = b.CreateBitCast(buf, llvm.PointerType(typ, 0), "") buf = b.CreateBitCast(buf, llvm.PointerType(typ, 0), "")
return buf, nil return buf, nil
} else { } else {
@@ -1732,8 +1604,10 @@ func (b *builder) createExpr(expr ssa.Value) (llvm.Value, error) {
elemSize := b.targetData.TypeAllocSize(llvmElemType) elemSize := b.targetData.TypeAllocSize(llvmElemType)
elemSizeValue := llvm.ConstInt(b.uintptrType, elemSize, false) elemSizeValue := llvm.ConstInt(b.uintptrType, elemSize, false)
maxSize := b.maxSliceSize(llvmElemType) // Calculate (^uintptr(0)) >> 1, which is the max value that fits in
if elemSize > maxSize { // uintptr if uintptr were signed.
maxSize := llvm.ConstLShr(llvm.ConstNot(llvm.ConstInt(b.uintptrType, 0, false)), llvm.ConstInt(b.uintptrType, 1, false))
if elemSize > maxSize.ZExtValue() {
// This seems to be checked by the typechecker already, but let's // This seems to be checked by the typechecker already, but let's
// check it again just to be sure. // check it again just to be sure.
return llvm.Value{}, b.makeError(expr.Pos(), fmt.Sprintf("slice element type is too big (%v bytes)", elemSize)) return llvm.Value{}, b.makeError(expr.Pos(), fmt.Sprintf("slice element type is too big (%v bytes)", elemSize))
@@ -1742,8 +1616,7 @@ 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().Underlying().(*types.Basic)
capType := expr.Cap.Type().Underlying().(*types.Basic) capType := expr.Cap.Type().Underlying().(*types.Basic)
maxSizeValue := llvm.ConstInt(b.uintptrType, maxSize, false) b.createSliceBoundsCheck(maxSize, sliceLen, sliceCap, sliceCap, lenType, capType, capType)
b.createSliceBoundsCheck(maxSizeValue, sliceLen, sliceCap, sliceCap, lenType, capType, capType)
// Allocate the backing array. // Allocate the backing array.
sliceCapCast, err := b.createConvert(expr.Cap.Type(), types.Typ[types.Uintptr], sliceCap, expr.Pos()) sliceCapCast, err := b.createConvert(expr.Cap.Type(), types.Typ[types.Uintptr], sliceCap, expr.Pos())
@@ -1751,8 +1624,7 @@ func (b *builder) createExpr(expr ssa.Value) (llvm.Value, error) {
return llvm.Value{}, err return llvm.Value{}, err
} }
sliceSize := b.CreateBinOp(llvm.Mul, elemSizeValue, sliceCapCast, "makeslice.cap") sliceSize := b.CreateBinOp(llvm.Mul, elemSizeValue, sliceCapCast, "makeslice.cap")
layoutValue := b.createObjectLayout(llvmElemType, expr.Pos()) slicePtr := b.createRuntimeCall("alloc", []llvm.Value{sliceSize}, "makeslice.buf")
slicePtr := b.createRuntimeCall("alloc", []llvm.Value{sliceSize, layoutValue}, "makeslice.buf")
slicePtr = b.CreateBitCast(slicePtr, llvm.PointerType(llvmElemType, 0), "makeslice.array") slicePtr = b.CreateBitCast(slicePtr, llvm.PointerType(llvmElemType, 0), "makeslice.array")
// Extend or truncate if necessary. This is safe as we've already done // Extend or truncate if necessary. This is safe as we've already done
@@ -1987,17 +1859,6 @@ func (b *builder) createExpr(expr ssa.Value) (llvm.Value, error) {
default: default:
return llvm.Value{}, b.makeError(expr.Pos(), "unknown slice type: "+typ.String()) return llvm.Value{}, b.makeError(expr.Pos(), "unknown slice type: "+typ.String())
} }
case *ssa.SliceToArrayPointer:
// Conversion from a slice to an array pointer, as the name clearly
// says. This requires a runtime check to make sure the slice is at
// least as big as the array.
slice := b.getValue(expr.X)
sliceLen := b.CreateExtractValue(slice, 1, "")
arrayLen := expr.Type().Underlying().(*types.Pointer).Elem().Underlying().(*types.Array).Len()
b.createSliceToArrayPointerCheck(sliceLen, arrayLen)
ptr := b.CreateExtractValue(slice, 0, "")
ptr = b.CreateBitCast(ptr, b.getLLVMType(expr.Type()), "")
return ptr, nil
case *ssa.TypeAssert: case *ssa.TypeAssert:
return b.createTypeAssert(expr), nil return b.createTypeAssert(expr), nil
case *ssa.UnOp: case *ssa.UnOp:
@@ -2026,58 +1887,17 @@ func (b *builder) createBinOp(op token.Token, typ, ytyp types.Type, x, y llvm.Va
return b.CreateSub(x, y, ""), nil return b.CreateSub(x, y, ""), nil
case token.MUL: // * case token.MUL: // *
return b.CreateMul(x, y, ""), nil return b.CreateMul(x, y, ""), nil
case token.QUO, token.REM: // /, % case token.QUO: // /
// Check for a divide by zero. If y is zero, the Go
// specification says that a runtime error must be triggered.
b.createDivideByZeroCheck(y)
if signed { if signed {
// Deal with signed division overflow. return b.CreateSDiv(x, y, ""), nil
// The LLVM LangRef says:
//
// Overflow also leads to undefined behavior; this is a
// rare case, but can occur, for example, by doing a
// 32-bit division of -2147483648 by -1.
//
// The Go specification however says this about division:
//
// The one exception to this rule is that if the dividend
// x is the most negative value for the int type of x, the
// quotient q = x / -1 is equal to x (and r = 0) due to
// two's-complement integer overflow.
//
// In other words, in the special case that the lowest
// possible signed integer is divided by -1, the result of
// the division is the same as x (the dividend).
// This is implemented by checking for this condition and
// changing y to 1 if it occurs, for example for 32-bit
// ints:
//
// if x == -2147483648 && y == -1 {
// y = 1
// }
//
// Dividing x by 1 obviously returns x, therefore satisfying
// the Go specification without a branch.
llvmType := x.Type()
minusOne := llvm.ConstSub(llvm.ConstInt(llvmType, 0, false), llvm.ConstInt(llvmType, 1, false))
lowestInteger := llvm.ConstInt(x.Type(), 1<<(llvmType.IntTypeWidth()-1), false)
yIsMinusOne := b.CreateICmp(llvm.IntEQ, y, minusOne, "")
xIsLowestInteger := b.CreateICmp(llvm.IntEQ, x, lowestInteger, "")
hasOverflow := b.CreateAnd(yIsMinusOne, xIsLowestInteger, "")
y = b.CreateSelect(hasOverflow, llvm.ConstInt(llvmType, 1, true), y, "")
if op == token.QUO {
return b.CreateSDiv(x, y, ""), nil
} else {
return b.CreateSRem(x, y, ""), nil
}
} else { } else {
if op == token.QUO { return b.CreateUDiv(x, y, ""), nil
return b.CreateUDiv(x, y, ""), nil }
} else { case token.REM: // %
return b.CreateURem(x, y, ""), nil if signed {
} return b.CreateSRem(x, y, ""), nil
} else {
return b.CreateURem(x, y, ""), nil
} }
case token.AND: // & case token.AND: // &
return b.CreateAnd(x, y, ""), nil return b.CreateAnd(x, y, ""), nil
@@ -2424,7 +2244,7 @@ func (b *builder) createBinOp(op token.Token, typ, ytyp types.Type, x, y llvm.Va
} }
// createConst creates a LLVM constant value from a Go constant. // createConst creates a LLVM constant value from a Go constant.
func (b *builder) createConst(expr *ssa.Const) llvm.Value { func (b *builder) createConst(prefix string, expr *ssa.Const) llvm.Value {
switch typ := expr.Type().Underlying().(type) { switch typ := expr.Type().Underlying().(type) {
case *types.Basic: case *types.Basic:
llvmType := b.getLLVMType(typ) llvmType := b.getLLVMType(typ)
@@ -2438,20 +2258,14 @@ func (b *builder) createConst(expr *ssa.Const) llvm.Value {
} else if typ.Info()&types.IsString != 0 { } else if typ.Info()&types.IsString != 0 {
str := constant.StringVal(expr.Value) str := constant.StringVal(expr.Value)
strLen := llvm.ConstInt(b.uintptrType, uint64(len(str)), false) strLen := llvm.ConstInt(b.uintptrType, uint64(len(str)), false)
var strPtr llvm.Value objname := prefix + "$string"
if str != "" { global := llvm.AddGlobal(b.mod, llvm.ArrayType(b.ctx.Int8Type(), len(str)), objname)
objname := b.pkg.Path() + "$string" global.SetInitializer(b.ctx.ConstString(str, false))
global := llvm.AddGlobal(b.mod, llvm.ArrayType(b.ctx.Int8Type(), len(str)), objname) global.SetLinkage(llvm.InternalLinkage)
global.SetInitializer(b.ctx.ConstString(str, false)) global.SetGlobalConstant(true)
global.SetLinkage(llvm.InternalLinkage) global.SetUnnamedAddr(true)
global.SetGlobalConstant(true) zero := llvm.ConstInt(b.ctx.Int32Type(), 0, false)
global.SetUnnamedAddr(true) strPtr := b.CreateInBoundsGEP(global, []llvm.Value{zero, zero}, "")
global.SetAlignment(1)
zero := llvm.ConstInt(b.ctx.Int32Type(), 0, false)
strPtr = b.CreateInBoundsGEP(global, []llvm.Value{zero, zero}, "")
} else {
strPtr = llvm.ConstNull(b.i8ptrType)
}
strObj := llvm.ConstNamedStruct(b.getLLVMRuntimeType("_string"), []llvm.Value{strPtr, strLen}) strObj := llvm.ConstNamedStruct(b.getLLVMRuntimeType("_string"), []llvm.Value{strPtr, strLen})
return strObj return strObj
} else if typ.Kind() == types.UnsafePointer { } else if typ.Kind() == types.UnsafePointer {
@@ -2470,15 +2284,15 @@ func (b *builder) createConst(expr *ssa.Const) llvm.Value {
n, _ := constant.Float64Val(expr.Value) n, _ := constant.Float64Val(expr.Value)
return llvm.ConstFloat(llvmType, n) return llvm.ConstFloat(llvmType, n)
} else if typ.Kind() == types.Complex64 { } else if typ.Kind() == types.Complex64 {
r := b.createConst(ssa.NewConst(constant.Real(expr.Value), types.Typ[types.Float32])) r := b.createConst(prefix, ssa.NewConst(constant.Real(expr.Value), types.Typ[types.Float32]))
i := b.createConst(ssa.NewConst(constant.Imag(expr.Value), types.Typ[types.Float32])) i := b.createConst(prefix, ssa.NewConst(constant.Imag(expr.Value), types.Typ[types.Float32]))
cplx := llvm.Undef(b.ctx.StructType([]llvm.Type{b.ctx.FloatType(), b.ctx.FloatType()}, false)) cplx := llvm.Undef(b.ctx.StructType([]llvm.Type{b.ctx.FloatType(), b.ctx.FloatType()}, false))
cplx = b.CreateInsertValue(cplx, r, 0, "") cplx = b.CreateInsertValue(cplx, r, 0, "")
cplx = b.CreateInsertValue(cplx, i, 1, "") cplx = b.CreateInsertValue(cplx, i, 1, "")
return cplx return cplx
} else if typ.Kind() == types.Complex128 { } else if typ.Kind() == types.Complex128 {
r := b.createConst(ssa.NewConst(constant.Real(expr.Value), types.Typ[types.Float64])) r := b.createConst(prefix, ssa.NewConst(constant.Real(expr.Value), types.Typ[types.Float64]))
i := b.createConst(ssa.NewConst(constant.Imag(expr.Value), types.Typ[types.Float64])) i := b.createConst(prefix, ssa.NewConst(constant.Imag(expr.Value), types.Typ[types.Float64]))
cplx := llvm.Undef(b.ctx.StructType([]llvm.Type{b.ctx.DoubleType(), b.ctx.DoubleType()}, false)) cplx := llvm.Undef(b.ctx.StructType([]llvm.Type{b.ctx.DoubleType(), b.ctx.DoubleType()}, false))
cplx = b.CreateInsertValue(cplx, r, 0, "") cplx = b.CreateInsertValue(cplx, r, 0, "")
cplx = b.CreateInsertValue(cplx, i, 1, "") cplx = b.CreateInsertValue(cplx, i, 1, "")
+36 -79
View File
@@ -9,7 +9,6 @@ import (
"testing" "testing"
"github.com/tinygo-org/tinygo/compileopts" "github.com/tinygo-org/tinygo/compileopts"
"github.com/tinygo-org/tinygo/goenv"
"github.com/tinygo-org/tinygo/loader" "github.com/tinygo-org/tinygo/loader"
"tinygo.org/x/go-llvm" "tinygo.org/x/go-llvm"
) )
@@ -17,16 +16,9 @@ import (
// Pass -update to go test to update the output of the test files. // Pass -update to go test to update the output of the test files.
var flagUpdate = flag.Bool("update", false, "update tests based on test output") var flagUpdate = flag.Bool("update", false, "update tests based on test output")
type testCase struct {
file string
target string
}
// Basic tests for the compiler. Build some Go files and compare the output with // Basic tests for the compiler. Build some Go files and compare the output with
// the expected LLVM IR for regression testing. // the expected LLVM IR for regression testing.
func TestCompiler(t *testing.T) { func TestCompiler(t *testing.T) {
t.Parallel()
// Check LLVM version. // Check LLVM version.
llvmMajor, err := strconv.Atoi(strings.SplitN(llvm.Version, ".", 2)[0]) llvmMajor, err := strconv.Atoi(strings.SplitN(llvm.Version, ".", 2)[0])
if err != nil { if err != nil {
@@ -40,68 +32,42 @@ func TestCompiler(t *testing.T) {
t.Skip("compiler tests require LLVM 11 or above, got LLVM ", llvm.Version) t.Skip("compiler tests require LLVM 11 or above, got LLVM ", llvm.Version)
} }
tests := []testCase{ target, err := compileopts.LoadTarget("i686--linux")
{"basic.go", ""},
{"pointer.go", ""},
{"slice.go", ""},
{"string.go", ""},
{"float.go", ""},
{"interface.go", ""},
{"func.go", ""},
{"pragma.go", ""},
{"goroutine.go", "wasm"},
{"goroutine.go", "cortex-m-qemu"},
{"channel.go", ""},
{"intrinsics.go", "cortex-m-qemu"},
{"intrinsics.go", "wasm"},
{"gc.go", ""},
}
_, minor, err := goenv.GetGorootVersion(goenv.Get("GOROOT"))
if err != nil { if err != nil {
t.Fatal("could not read Go version:", err) t.Fatal("failed to load target:", err)
} }
if minor >= 17 { config := &compileopts.Config{
tests = append(tests, testCase{"go1.17.go", ""}) Options: &compileopts.Options{},
Target: target,
}
compilerConfig := &Config{
Triple: config.Triple(),
GOOS: config.GOOS(),
GOARCH: config.GOARCH(),
CodeModel: config.CodeModel(),
RelocationModel: config.RelocationModel(),
Scheduler: config.Scheduler(),
FuncImplementation: config.FuncImplementation(),
AutomaticStackSize: config.AutomaticStackSize(),
}
machine, err := NewTargetMachine(compilerConfig)
if err != nil {
t.Fatal("failed to create target machine:", err)
} }
for _, tc := range tests { tests := []string{
name := tc.file "basic.go",
targetString := "wasm" "pointer.go",
if tc.target != "" { "slice.go",
targetString = tc.target "string.go",
name = tc.file + "-" + tc.target "float.go",
} "interface.go",
}
t.Run(name, func(t *testing.T) {
options := &compileopts.Options{
Target: targetString,
}
target, err := compileopts.LoadTarget(options)
if err != nil {
t.Fatal("failed to load target:", err)
}
config := &compileopts.Config{
Options: options,
Target: target,
}
compilerConfig := &Config{
Triple: config.Triple(),
GOOS: config.GOOS(),
GOARCH: config.GOARCH(),
CodeModel: config.CodeModel(),
RelocationModel: config.RelocationModel(),
Scheduler: config.Scheduler(),
FuncImplementation: config.FuncImplementation(),
AutomaticStackSize: config.AutomaticStackSize(),
}
machine, err := NewTargetMachine(compilerConfig)
if err != nil {
t.Fatal("failed to create target machine:", err)
}
for _, testCase := range tests {
t.Run(testCase, func(t *testing.T) {
// Load entire program AST into memory. // Load entire program AST into memory.
lprogram, err := loader.Load(config, []string{"./testdata/" + tc.file}, config.ClangHeaders, types.Config{ lprogram, err := loader.Load(config, []string{"./testdata/" + testCase}, config.ClangHeaders, types.Config{
Sizes: Sizes(machine), Sizes: Sizes(machine),
}) })
if err != nil { if err != nil {
@@ -109,25 +75,20 @@ func TestCompiler(t *testing.T) {
} }
err = lprogram.Parse() err = lprogram.Parse()
if err != nil { if err != nil {
t.Fatalf("could not parse test case %s: %s", tc.file, err) t.Fatalf("could not parse test case %s: %s", testCase, err)
} }
// Compile AST to IR. // Compile AST to IR.
program := lprogram.LoadSSA() program := lprogram.LoadSSA()
pkg := lprogram.MainPkg() pkg := lprogram.MainPkg()
mod, errs := CompilePackage(tc.file, pkg, program.Package(pkg.Pkg), machine, compilerConfig, false) mod, errs := CompilePackage(testCase, pkg, program.Package(pkg.Pkg), machine, compilerConfig, false)
if errs != nil { if errs != nil {
for _, err := range errs { for _, err := range errs {
t.Error(err) t.Log("error:", err)
} }
return return
} }
err = llvm.VerifyModule(mod, llvm.PrintMessageAction)
if err != nil {
t.Error(err)
}
// Optimize IR a little. // Optimize IR a little.
funcPasses := llvm.NewFunctionPassManagerForModule(mod) funcPasses := llvm.NewFunctionPassManagerForModule(mod)
defer funcPasses.Dispose() defer funcPasses.Dispose()
@@ -138,22 +99,18 @@ func TestCompiler(t *testing.T) {
} }
funcPasses.FinalizeFunc() funcPasses.FinalizeFunc()
outFilePrefix := tc.file[:len(tc.file)-3] outfile := "./testdata/" + testCase[:len(testCase)-3] + ".ll"
if tc.target != "" {
outFilePrefix += "-" + tc.target
}
outPath := "./testdata/" + outFilePrefix + ".ll"
// Update test if needed. Do not check the result. // Update test if needed. Do not check the result.
if *flagUpdate { if *flagUpdate {
err := ioutil.WriteFile(outPath, []byte(mod.String()), 0666) err := ioutil.WriteFile(outfile, []byte(mod.String()), 0666)
if err != nil { if err != nil {
t.Error("failed to write updated output file:", err) t.Error("failed to write updated output file:", err)
} }
return return
} }
expected, err := ioutil.ReadFile(outPath) expected, err := ioutil.ReadFile(outfile)
if err != nil { if err != nil {
t.Fatal("failed to read golden file:", err) t.Fatal("failed to read golden file:", err)
} }
+5 -6
View File
@@ -217,8 +217,7 @@ func (b *builder) createDefer(instr *ssa.Defer) {
// This may be hit a variable number of times, so use a heap allocation. // This may be hit a variable number of times, so use a heap allocation.
size := b.targetData.TypeAllocSize(deferFrameType) size := b.targetData.TypeAllocSize(deferFrameType)
sizeValue := llvm.ConstInt(b.uintptrType, size, false) sizeValue := llvm.ConstInt(b.uintptrType, size, false)
nilPtr := llvm.ConstNull(b.i8ptrType) allocCall := b.createRuntimeCall("alloc", []llvm.Value{sizeValue}, "defer.alloc.call")
allocCall := b.createRuntimeCall("alloc", []llvm.Value{sizeValue, nilPtr}, "defer.alloc.call")
alloca = b.CreateBitCast(allocCall, llvm.PointerType(deferFrameType, 0), "defer.alloc") alloca = b.CreateBitCast(allocCall, llvm.PointerType(deferFrameType, 0), "defer.alloc")
} }
if b.NeedsStackObjects { if b.NeedsStackObjects {
@@ -332,10 +331,10 @@ func (b *builder) createRunDefers() {
//Pass context //Pass context
forwardParams = append(forwardParams, context) forwardParams = append(forwardParams, context)
} else { } else {
// Move typecode from the start to the end of the list of // Isolate the typecode.
// parameters. typecode := forwardParams[0]
forwardParams = append(forwardParams[1:], forwardParams[0]) forwardParams = forwardParams[1:]
fnPtr = b.getInvokeFunction(callback) 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
+11 -1
View File
@@ -3,6 +3,7 @@ package compiler
// This file contains some utility functions related to error handling. // This file contains some utility functions related to error handling.
import ( import (
"go/scanner"
"go/token" "go/token"
"go/types" "go/types"
"path/filepath" "path/filepath"
@@ -19,11 +20,20 @@ func (c *compilerContext) makeError(pos token.Pos, msg string) types.Error {
} }
} }
// addError adds a new compiler diagnostic with the given location and message.
func (c *compilerContext) addError(pos token.Pos, msg string) { func (c *compilerContext) addError(pos token.Pos, msg string) {
c.diagnostics = append(c.diagnostics, c.makeError(pos, msg)) c.diagnostics = append(c.diagnostics, c.makeError(pos, msg))
} }
// errorAt returns an error value at the location of the instruction.
// The location information may not be complete as it depends on debug
// information in the IR.
func errorAt(inst llvm.Value, msg string) scanner.Error {
return scanner.Error{
Pos: getPosition(inst),
Msg: msg,
}
}
// getPosition returns the position information for the given value, as far as // getPosition returns the position information for the given value, as far as
// it is available. // it is available.
func getPosition(val llvm.Value) token.Position { func getPosition(val llvm.Value) token.Position {
+7 -31
View File
@@ -23,15 +23,16 @@ func (c *compilerContext) createFuncValue(builder llvm.Builder, funcPtr, context
switch c.FuncImplementation { switch c.FuncImplementation {
case "doubleword": case "doubleword":
// Closure is: {context, function pointer} // Closure is: {context, function pointer}
funcValueScalar = llvm.ConstBitCast(funcPtr, c.rawVoidFuncType) funcValueScalar = funcPtr
case "switch": case "switch":
sigGlobal := c.getTypeCode(sig)
funcValueWithSignatureGlobalName := funcPtr.Name() + "$withSignature" funcValueWithSignatureGlobalName := funcPtr.Name() + "$withSignature"
funcValueWithSignatureGlobal := c.mod.NamedGlobal(funcValueWithSignatureGlobalName) funcValueWithSignatureGlobal := c.mod.NamedGlobal(funcValueWithSignatureGlobalName)
if funcValueWithSignatureGlobal.IsNil() { if funcValueWithSignatureGlobal.IsNil() {
funcValueWithSignatureType := c.getLLVMRuntimeType("funcValueWithSignature") funcValueWithSignatureType := c.getLLVMRuntimeType("funcValueWithSignature")
funcValueWithSignature := llvm.ConstNamedStruct(funcValueWithSignatureType, []llvm.Value{ funcValueWithSignature := llvm.ConstNamedStruct(funcValueWithSignatureType, []llvm.Value{
llvm.ConstPtrToInt(funcPtr, c.uintptrType), llvm.ConstPtrToInt(funcPtr, c.uintptrType),
c.getFuncSignatureID(sig), sigGlobal,
}) })
funcValueWithSignatureGlobal = llvm.AddGlobal(c.mod, funcValueWithSignatureType, funcValueWithSignatureGlobalName) funcValueWithSignatureGlobal = llvm.AddGlobal(c.mod, funcValueWithSignatureType, funcValueWithSignatureGlobalName)
funcValueWithSignatureGlobal.SetInitializer(funcValueWithSignature) funcValueWithSignatureGlobal.SetInitializer(funcValueWithSignature)
@@ -49,19 +50,6 @@ func (c *compilerContext) createFuncValue(builder llvm.Builder, funcPtr, context
return funcValue return funcValue
} }
// getFuncSignatureID returns a new external global for a given signature. This
// global reference is not real, it is only used during func lowering to assign
// signature types to functions and will then be removed.
func (c *compilerContext) getFuncSignatureID(sig *types.Signature) llvm.Value {
sigGlobalName := "reflect/types.funcid:" + getTypeCodeName(sig)
sigGlobal := c.mod.NamedGlobal(sigGlobalName)
if sigGlobal.IsNil() {
sigGlobal = llvm.AddGlobal(c.mod, c.ctx.Int8Type(), sigGlobalName)
sigGlobal.SetGlobalConstant(true)
}
return sigGlobal
}
// extractFuncScalar returns some scalar that can be used in comparisons. It is // extractFuncScalar returns some scalar that can be used in comparisons. It is
// a cheap operation. // a cheap operation.
func (b *builder) extractFuncScalar(funcValue llvm.Value) llvm.Value { func (b *builder) extractFuncScalar(funcValue llvm.Value) llvm.Value {
@@ -80,23 +68,10 @@ func (b *builder) decodeFuncValue(funcValue llvm.Value, sig *types.Signature) (f
context = b.CreateExtractValue(funcValue, 0, "") context = b.CreateExtractValue(funcValue, 0, "")
switch b.FuncImplementation { switch b.FuncImplementation {
case "doubleword": case "doubleword":
bitcast := b.CreateExtractValue(funcValue, 1, "") funcPtr = b.CreateExtractValue(funcValue, 1, "")
if !bitcast.IsAConstantExpr().IsNil() && bitcast.Opcode() == llvm.BitCast {
funcPtr = bitcast.Operand(0)
return
}
llvmSig := b.getRawFuncType(sig)
funcPtr = b.CreateBitCast(bitcast, llvmSig, "")
case "switch": case "switch":
if !funcValue.IsAConstant().IsNil() {
// If this is a constant func value, the underlying function is
// known and can be returned directly.
funcValueWithSignatureGlobal := llvm.ConstExtractValue(funcValue, []uint32{1}).Operand(0)
funcPtr = llvm.ConstExtractValue(funcValueWithSignatureGlobal.Initializer(), []uint32{0}).Operand(0)
return
}
llvmSig := b.getRawFuncType(sig) llvmSig := b.getRawFuncType(sig)
sigGlobal := b.getFuncSignatureID(sig) sigGlobal := b.getTypeCode(sig)
funcPtr = b.createRuntimeCall("getFuncPtr", []llvm.Value{funcValue, sigGlobal}, "") funcPtr = b.createRuntimeCall("getFuncPtr", []llvm.Value{funcValue, sigGlobal}, "")
funcPtr = b.CreateIntToPtr(funcPtr, llvmSig, "") funcPtr = b.CreateIntToPtr(funcPtr, llvmSig, "")
default: default:
@@ -109,7 +84,8 @@ func (b *builder) decodeFuncValue(funcValue llvm.Value, sig *types.Signature) (f
func (c *compilerContext) getFuncType(typ *types.Signature) llvm.Type { func (c *compilerContext) getFuncType(typ *types.Signature) llvm.Type {
switch c.FuncImplementation { switch c.FuncImplementation {
case "doubleword": case "doubleword":
return c.ctx.StructType([]llvm.Type{c.i8ptrType, c.rawVoidFuncType}, false) rawPtr := c.getRawFuncType(typ)
return c.ctx.StructType([]llvm.Type{c.i8ptrType, rawPtr}, false)
case "switch": case "switch":
return c.getLLVMRuntimeType("funcValue") return c.getLLVMRuntimeType("funcValue")
default: default:
+15 -124
View File
@@ -5,115 +5,25 @@ package compiler
import ( import (
"go/token" "go/token"
"go/types"
"github.com/tinygo-org/tinygo/compiler/llvmutil" "github.com/tinygo-org/tinygo/compiler/llvmutil"
"golang.org/x/tools/go/ssa" "golang.org/x/tools/go/ssa"
"tinygo.org/x/go-llvm" "tinygo.org/x/go-llvm"
) )
// createGo emits code to start a new goroutine. // createGoInstruction starts a new goroutine with the provided function pointer
func (b *builder) createGo(instr *ssa.Go) { // and parameters.
// Get all function parameters to pass to the goroutine. // In general, you should pass all regular parameters plus the context parameter.
var params []llvm.Value // There is one exception: the task-based scheduler needs to have the function
for _, param := range instr.Call.Args { // pointer passed in as a parameter too in addition to the context.
params = append(params, b.getValue(param)) //
} // 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 {
var prefix string
var funcPtr llvm.Value
hasContext := false
if callee := instr.Call.StaticCallee(); callee != nil {
// Static callee is known. This makes it easier to start a new
// goroutine.
var context llvm.Value
switch value := instr.Call.Value.(type) {
case *ssa.Function:
// Goroutine call is regular function call. No context is necessary.
if b.Scheduler == "coroutines" {
// The context parameter is assumed to be always present in the
// coroutines scheduler.
context = llvm.Undef(b.i8ptrType)
}
case *ssa.MakeClosure:
// A goroutine call on a func value, but the callee is trivial to find. For
// example: immediately applied functions.
funcValue := b.getValue(value)
context = b.extractFuncContext(funcValue)
default:
panic("StaticCallee returned an unexpected value")
}
if !context.IsNil() {
params = append(params, context) // context parameter
hasContext = true
}
funcPtr = b.getFunction(callee)
} else if builtin, ok := instr.Call.Value.(*ssa.Builtin); ok {
// We cheat. None of the builtins do any long or blocking operation, so
// we might as well run these builtins right away without the program
// noticing the difference.
// Possible exceptions:
// - copy: this is a possibly long operation, but not a blocking
// operation. Semantically it makes no difference to run it right
// away (not in a goroutine). However, in practice it makes no sense
// to run copy in a goroutine as there is no way to (safely) know
// when it is finished.
// - panic: the error message would appear in the parent goroutine.
// But because `go panic("err")` would halt the program anyway
// (there is no recover), panicking right away would give the same
// behavior as creating a goroutine, switching the scheduler to that
// goroutine, and panicking there. So this optimization seems
// correct.
// - recover: because it runs in a new goroutine, it is never a
// deferred function. Thus this is a no-op.
if builtin.Name() == "recover" {
// This is a no-op, even in a deferred function:
// go recover()
return
}
var argTypes []types.Type
var argValues []llvm.Value
for _, arg := range instr.Call.Args {
argTypes = append(argTypes, arg.Type())
argValues = append(argValues, b.getValue(arg))
}
b.createBuiltin(argTypes, argValues, builtin.Name(), instr.Pos())
return
} else if instr.Call.IsInvoke() {
// This is a method call on an interface value.
itf := b.getValue(instr.Call.Value)
itfTypeCode := b.CreateExtractValue(itf, 0, "")
itfValue := b.CreateExtractValue(itf, 1, "")
funcPtr = b.getInvokeFunction(&instr.Call)
params = append([]llvm.Value{itfValue}, params...) // start with receiver
params = append(params, itfTypeCode) // end with typecode
} else {
// This is a function pointer.
// At the moment, two extra params are passed to the newly started
// goroutine:
// * The function context, for closures.
// * The function pointer (for tasks).
var context llvm.Value
funcPtr, context = b.decodeFuncValue(b.getValue(instr.Call.Value), instr.Call.Value.Type().Underlying().(*types.Signature))
params = append(params, context) // context parameter
hasContext = true
switch b.Scheduler {
case "none", "coroutines":
// There are no additional parameters needed for the goroutine start operation.
case "tasks":
// Add the function pointer as a parameter to start the goroutine.
params = append(params, funcPtr)
default:
panic("unknown scheduler type")
}
prefix = b.fn.RelString(nil)
}
paramBundle := b.emitPointerPack(params) paramBundle := b.emitPointerPack(params)
var callee, stackSize llvm.Value var callee, stackSize llvm.Value
switch b.Scheduler { switch b.Scheduler {
case "none", "tasks": case "none", "tasks":
callee = b.createGoroutineStartWrapper(funcPtr, prefix, hasContext, instr.Pos()) callee = b.createGoroutineStartWrapper(funcPtr, prefix, pos)
if b.AutomaticStackSize { if b.AutomaticStackSize {
// The stack size is not known until after linking. Call a dummy // The stack size is not known until after linking. Call a dummy
// function that will be replaced with a load from a special ELF // function that will be replaced with a load from a special ELF
@@ -124,9 +34,6 @@ func (b *builder) createGo(instr *ssa.Go) {
} else { } else {
// The stack size is fixed at compile time. By emitting it here as a // The stack size is fixed at compile time. By emitting it here as a
// constant, it can be optimized. // constant, it can be optimized.
if b.Scheduler == "tasks" && b.DefaultStackSize == 0 {
b.addError(instr.Pos(), "default stack size for goroutines is not set")
}
stackSize = llvm.ConstInt(b.uintptrType, b.DefaultStackSize, false) stackSize = llvm.ConstInt(b.uintptrType, b.DefaultStackSize, false)
} }
case "coroutines": case "coroutines":
@@ -139,6 +46,7 @@ func (b *builder) createGo(instr *ssa.Go) {
} }
start := b.getFunction(b.program.ImportedPackage("internal/task").Members["start"].(*ssa.Function)) start := b.getFunction(b.program.ImportedPackage("internal/task").Members["start"].(*ssa.Function))
b.createCall(start, []llvm.Value{callee, paramBundle, stackSize, llvm.Undef(b.i8ptrType), llvm.ConstPointerNull(b.i8ptrType)}, "") b.createCall(start, []llvm.Value{callee, paramBundle, stackSize, llvm.Undef(b.i8ptrType), llvm.ConstPointerNull(b.i8ptrType)}, "")
return llvm.Undef(funcPtr.Type().ElementType().ReturnType())
} }
// createGoroutineStartWrapper creates a wrapper for the task-based // createGoroutineStartWrapper creates a wrapper for the task-based
@@ -159,12 +67,7 @@ func (b *builder) createGo(instr *ssa.Go) {
// allows a single (pointer) argument to the newly started goroutine. Also, it // allows a single (pointer) argument to the newly started goroutine. Also, it
// ignores the return value because newly started goroutines do not have a // ignores the return value because newly started goroutines do not have a
// return value. // return value.
// func (c *compilerContext) createGoroutineStartWrapper(fn llvm.Value, prefix string, pos token.Pos) llvm.Value {
// The hasContext parameter indicates whether the context parameter (the second
// to last parameter of the function) is used for this wrapper. If hasContext is
// false, the parameter bundle is assumed to have no context parameter and undef
// is passed instead.
func (c *compilerContext) createGoroutineStartWrapper(fn llvm.Value, prefix string, hasContext bool, pos token.Pos) llvm.Value {
var wrapper llvm.Value var wrapper llvm.Value
builder := c.ctx.NewBuilder() builder := c.ctx.NewBuilder()
@@ -181,7 +84,6 @@ 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)
c.addStandardAttributes(wrapper)
wrapper.SetLinkage(llvm.LinkOnceODRLinkage) wrapper.SetLinkage(llvm.LinkOnceODRLinkage)
wrapper.SetUnnamedAddr(true) wrapper.SetUnnamedAddr(true)
wrapper.AddAttributeAtIndex(-1, c.ctx.CreateStringAttribute("tinygo-gowrapper", name)) wrapper.AddAttributeAtIndex(-1, c.ctx.CreateStringAttribute("tinygo-gowrapper", name))
@@ -212,15 +114,8 @@ func (c *compilerContext) createGoroutineStartWrapper(fn llvm.Value, prefix stri
// Create the list of params for the call. // Create the list of params for the call.
paramTypes := fn.Type().ElementType().ParamTypes() paramTypes := fn.Type().ElementType().ParamTypes()
paramTypes = paramTypes[:len(paramTypes)-1] // strip parentHandle parameter params := llvmutil.EmitPointerUnpack(builder, c.mod, wrapper.Param(0), paramTypes[:len(paramTypes)-1])
if !hasContext { params = append(params, llvm.Undef(c.i8ptrType))
paramTypes = paramTypes[:len(paramTypes)-1] // strip context parameter
}
params := llvmutil.EmitPointerUnpack(builder, c.mod, wrapper.Param(0), paramTypes)
if !hasContext {
params = append(params, llvm.Undef(c.i8ptrType)) // add dummy context parameter
}
params = append(params, llvm.Undef(c.i8ptrType)) // add dummy parentHandle parameter
// Create the call. // Create the call.
builder.CreateCall(fn, params, "") builder.CreateCall(fn, params, "")
@@ -246,7 +141,6 @@ 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, prefix+".gowrapper", wrapperType) wrapper = llvm.AddFunction(c.mod, prefix+".gowrapper", wrapperType)
c.addStandardAttributes(wrapper)
wrapper.SetLinkage(llvm.LinkOnceODRLinkage) wrapper.SetLinkage(llvm.LinkOnceODRLinkage)
wrapper.SetUnnamedAddr(true) wrapper.SetUnnamedAddr(true)
wrapper.AddAttributeAtIndex(-1, c.ctx.CreateStringAttribute("tinygo-gowrapper", "")) wrapper.AddAttributeAtIndex(-1, c.ctx.CreateStringAttribute("tinygo-gowrapper", ""))
@@ -283,11 +177,8 @@ func (c *compilerContext) createGoroutineStartWrapper(fn llvm.Value, prefix stri
// Get the function pointer. // Get the function pointer.
fnPtr := params[len(params)-1] fnPtr := params[len(params)-1]
// The last parameter in the packed object has somewhat of a dual role. // Ignore the last param, which isn't used anymore.
// Inside the parameter bundle it's the function pointer, stored right // TODO: avoid this extra "parent handle" parameter in most functions.
// after the context pointer. But in the IR call instruction, it's the
// parentHandle function that's always undef outside of the coroutines
// scheduler. Thus, make the parameter undef here.
params[len(params)-1] = llvm.Undef(c.i8ptrType) params[len(params)-1] = llvm.Undef(c.i8ptrType)
// Create the call. // Create the call.
+2 -2
View File
@@ -72,7 +72,7 @@ func (b *builder) createInlineAsmFull(instr *ssa.CallCommon) (llvm.Value, error)
args := []llvm.Value{} args := []llvm.Value{}
constraints := []string{} constraints := []string{}
hasOutput := false hasOutput := false
asmString = regexp.MustCompile(`\{\}`).ReplaceAllStringFunc(asmString, func(s string) string { asmString = regexp.MustCompile("\\{\\}").ReplaceAllStringFunc(asmString, func(s string) string {
hasOutput = true hasOutput = true
return "$0" return "$0"
}) })
@@ -80,7 +80,7 @@ func (b *builder) createInlineAsmFull(instr *ssa.CallCommon) (llvm.Value, error)
constraints = append(constraints, "=&r") constraints = append(constraints, "=&r")
registerNumbers[""] = 0 registerNumbers[""] = 0
} }
asmString = regexp.MustCompile(`\{[a-zA-Z]+\}`).ReplaceAllStringFunc(asmString, func(s string) string { asmString = regexp.MustCompile("\\{[a-zA-Z]+\\}").ReplaceAllStringFunc(asmString, func(s string) string {
// TODO: skip strings like {r4} etc. that look like ARM push/pop // TODO: skip strings like {r4} etc. that look like ARM push/pop
// instructions. // instructions.
name := s[1 : len(s)-1] name := s[1 : len(s)-1]
+53 -101
View File
@@ -46,8 +46,6 @@ func (c *compilerContext) getTypeCode(typ types.Type) llvm.Value {
var references llvm.Value var references llvm.Value
var length int64 var length int64
var methodSet llvm.Value var methodSet llvm.Value
var ptrTo llvm.Value
var typeAssert llvm.Value
switch typ := typ.(type) { switch typ := typ.(type) {
case *types.Named: case *types.Named:
references = c.getTypeCode(typ.Underlying()) references = c.getTypeCode(typ.Underlying())
@@ -70,32 +68,23 @@ func (c *compilerContext) getTypeCode(typ types.Type) llvm.Value {
} }
if _, ok := typ.Underlying().(*types.Interface); !ok { if _, ok := typ.Underlying().(*types.Interface); !ok {
methodSet = c.getTypeMethodSet(typ) methodSet = c.getTypeMethodSet(typ)
} else {
typeAssert = c.getInterfaceImplementsFunc(typ)
typeAssert = llvm.ConstPtrToInt(typeAssert, c.uintptrType)
} }
if _, ok := typ.Underlying().(*types.Pointer); !ok { if !references.IsNil() || length != 0 || !methodSet.IsNil() {
ptrTo = c.getTypeCode(types.NewPointer(typ)) // Set the 'references' field of the runtime.typecodeID struct.
globalValue := llvm.ConstNull(global.Type().ElementType())
if !references.IsNil() {
globalValue = llvm.ConstInsertValue(globalValue, references, []uint32{0})
}
if length != 0 {
lengthValue := llvm.ConstInt(c.uintptrType, uint64(length), false)
globalValue = llvm.ConstInsertValue(globalValue, lengthValue, []uint32{1})
}
if !methodSet.IsNil() {
globalValue = llvm.ConstInsertValue(globalValue, methodSet, []uint32{2})
}
global.SetInitializer(globalValue)
global.SetLinkage(llvm.LinkOnceODRLinkage)
} }
globalValue := llvm.ConstNull(global.Type().ElementType())
if !references.IsNil() {
globalValue = llvm.ConstInsertValue(globalValue, references, []uint32{0})
}
if length != 0 {
lengthValue := llvm.ConstInt(c.uintptrType, uint64(length), false)
globalValue = llvm.ConstInsertValue(globalValue, lengthValue, []uint32{1})
}
if !methodSet.IsNil() {
globalValue = llvm.ConstInsertValue(globalValue, methodSet, []uint32{2})
}
if !ptrTo.IsNil() {
globalValue = llvm.ConstInsertValue(globalValue, ptrTo, []uint32{3})
}
if !typeAssert.IsNil() {
globalValue = llvm.ConstInsertValue(globalValue, typeAssert, []uint32{4})
}
global.SetInitializer(globalValue)
global.SetLinkage(llvm.LinkOnceODRLinkage)
global.SetGlobalConstant(true) global.SetGlobalConstant(true)
} }
return global return global
@@ -200,11 +189,7 @@ func getTypeCodeName(t types.Type) string {
case *types.Interface: case *types.Interface:
methods := make([]string, t.NumMethods()) methods := make([]string, t.NumMethods())
for i := 0; i < t.NumMethods(); i++ { for i := 0; i < t.NumMethods(); i++ {
name := t.Method(i).Name() methods[i] = t.Method(i).Name() + ":" + getTypeCodeName(t.Method(i).Type())
if !token.IsExported(name) {
name = t.Method(i).Pkg().Path() + "." + name
}
methods[i] = name + ":" + getTypeCodeName(t.Method(i).Type())
} }
return "interface:" + "{" + strings.Join(methods, ",") + "}" return "interface:" + "{" + strings.Join(methods, ",") + "}"
case *types.Map: case *types.Map:
@@ -317,33 +302,15 @@ func (c *compilerContext) getInterfaceMethodSet(typ types.Type) llvm.Value {
return llvm.ConstGEP(global, []llvm.Value{zero, zero}) return llvm.ConstGEP(global, []llvm.Value{zero, zero})
} }
// getMethodSignatureName returns a unique name (that can be used as the name of
// a global) for the given method.
func (c *compilerContext) getMethodSignatureName(method *types.Func) string {
signature := methodSignature(method)
var globalName string
if token.IsExported(method.Name()) {
globalName = "reflect/methods." + signature
} else {
globalName = method.Type().(*types.Signature).Recv().Pkg().Path() + ".$methods." + signature
}
return globalName
}
// getMethodSignature returns a global variable which is a reference to an // getMethodSignature returns a global variable which is a reference to an
// 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 {
globalName := c.getMethodSignatureName(method) signature := methodSignature(method)
signatureGlobal := c.mod.NamedGlobal(globalName) signatureGlobal := c.mod.NamedGlobal("func " + signature)
if signatureGlobal.IsNil() { if signatureGlobal.IsNil() {
// TODO: put something useful in these globals, such as the method signatureGlobal = llvm.AddGlobal(c.mod, c.ctx.Int8Type(), "func "+signature)
// signature. Useful to one day implement reflect.Value.Method(n).
signatureGlobal = llvm.AddGlobal(c.mod, c.ctx.Int8Type(), globalName)
signatureGlobal.SetInitializer(llvm.ConstInt(c.ctx.Int8Type(), 0, false))
signatureGlobal.SetLinkage(llvm.LinkOnceODRLinkage)
signatureGlobal.SetGlobalConstant(true) signatureGlobal.SetGlobalConstant(true)
signatureGlobal.SetAlignment(1)
} }
return signatureGlobal return signatureGlobal
} }
@@ -363,19 +330,18 @@ func (b *builder) createTypeAssert(expr *ssa.TypeAssert) llvm.Value {
commaOk := llvm.Value{} commaOk := llvm.Value{}
if _, ok := expr.AssertedType.Underlying().(*types.Interface); ok { if _, ok := expr.AssertedType.Underlying().(*types.Interface); ok {
// Type assert on interface type. // Type assert on interface type.
// This is a call to an interface type assert function. // This pseudo call will be lowered in the interface lowering pass to a
// The interface lowering pass will define this function by filling it // real call which checks whether the provided typecode is any of the
// with a type switch over all concrete types that implement this // concrete types that implements this interface.
// interface, and returning whether it's one of the matched types.
// This is very different from how interface asserts are implemented in // This is very different from how interface asserts are implemented in
// the main Go compiler, where the runtime checks whether the type // the main Go compiler, where the runtime checks whether the type
// implements each method of the interface. See: // implements each method of the interface. See:
// https://research.swtch.com/interfaces // https://research.swtch.com/interfaces
fn := b.getInterfaceImplementsFunc(expr.AssertedType) methodSet := b.getInterfaceMethodSet(expr.AssertedType)
commaOk = b.CreateCall(fn, []llvm.Value{actualTypeNum}, "") commaOk = b.createRuntimeCall("interfaceImplements", []llvm.Value{actualTypeNum, methodSet}, "")
} else { } else {
globalName := "reflect/types.typeid:" + getTypeCodeName(expr.AssertedType) globalName := "reflect/types.type:" + getTypeCodeName(expr.AssertedType) + "$id"
assertedTypeCodeGlobal := b.mod.NamedGlobal(globalName) assertedTypeCodeGlobal := b.mod.NamedGlobal(globalName)
if assertedTypeCodeGlobal.IsNil() { if assertedTypeCodeGlobal.IsNil() {
// Create a new typecode global. // Create a new typecode global.
@@ -439,52 +405,39 @@ func (b *builder) createTypeAssert(expr *ssa.TypeAssert) llvm.Value {
} }
} }
// getMethodsString returns a string to be used in the "tinygo-methods" string // getInvokePtr creates an interface function pointer lookup for the specified invoke instruction, using a specified typecode.
// attribute for interface functions. func (b *builder) getInvokePtr(instr *ssa.CallCommon, typecode llvm.Value) llvm.Value {
func (c *compilerContext) getMethodsString(itf *types.Interface) string { llvmFnType := b.getRawFuncType(instr.Method.Type().(*types.Signature))
methods := make([]string, itf.NumMethods()) values := []llvm.Value{
for i := range methods { typecode,
methods[i] = c.getMethodSignatureName(itf.Method(i)) b.getInterfaceMethodSet(instr.Value.Type()),
b.getMethodSignature(instr.Method),
} }
return strings.Join(methods, "; ") fn := b.createRuntimeCall("interfaceMethod", values, "invoke.func")
return b.CreateIntToPtr(fn, llvmFnType, "invoke.func.cast")
} }
// getInterfaceImplementsfunc returns a declared function that works as a type // getInvokeCall creates and returns the function pointer and parameters of an
// switch. The interface lowering pass will define this function. // interface call.
func (c *compilerContext) getInterfaceImplementsFunc(assertedType types.Type) llvm.Value { func (b *builder) getInvokeCall(instr *ssa.CallCommon) (llvm.Value, []llvm.Value) {
fnName := getTypeCodeName(assertedType.Underlying()) + ".$typeassert" // Call an interface method with dynamic dispatch.
llvmFn := c.mod.NamedFunction(fnName) itf := b.getValue(instr.Value) // interface
if llvmFn.IsNil() {
llvmFnType := llvm.FunctionType(c.ctx.Int1Type(), []llvm.Type{c.uintptrType}, false)
llvmFn = llvm.AddFunction(c.mod, fnName, llvmFnType)
c.addStandardDeclaredAttributes(llvmFn)
methods := c.getMethodsString(assertedType.Underlying().(*types.Interface))
llvmFn.AddFunctionAttr(c.ctx.CreateStringAttribute("tinygo-methods", methods))
}
return llvmFn
}
// getInvokeFunction returns the thunk to call the given interface method. The typecode := b.CreateExtractValue(itf, 0, "invoke.typecode")
// thunk is declared, not defined: it will be defined by the interface lowering fnCast := b.getInvokePtr(instr, typecode)
// pass. receiverValue := b.CreateExtractValue(itf, 1, "invoke.func.receiver")
func (c *compilerContext) getInvokeFunction(instr *ssa.CallCommon) llvm.Value {
fnName := getTypeCodeName(instr.Value.Type().Underlying()) + "." + instr.Method.Name() + "$invoke" args := []llvm.Value{receiverValue}
llvmFn := c.mod.NamedFunction(fnName) for _, arg := range instr.Args {
if llvmFn.IsNil() { args = append(args, b.getValue(arg))
sig := instr.Method.Type().(*types.Signature)
var paramTuple []*types.Var
for i := 0; i < sig.Params().Len(); i++ {
paramTuple = append(paramTuple, sig.Params().At(i))
}
paramTuple = append(paramTuple, types.NewVar(token.NoPos, nil, "$typecode", types.Typ[types.Uintptr]))
llvmFnType := c.getRawFuncType(types.NewSignature(sig.Recv(), types.NewTuple(paramTuple...), sig.Results(), false)).ElementType()
llvmFn = llvm.AddFunction(c.mod, fnName, llvmFnType)
c.addStandardDeclaredAttributes(llvmFn)
llvmFn.AddFunctionAttr(c.ctx.CreateStringAttribute("tinygo-invoke", c.getMethodSignatureName(instr.Method)))
methods := c.getMethodsString(instr.Value.Type().Underlying().(*types.Interface))
llvmFn.AddFunctionAttr(c.ctx.CreateStringAttribute("tinygo-methods", methods))
} }
return llvmFn // Add the context parameter. An interface call never takes a context but we
// have to supply the parameter anyway.
args = append(args, llvm.Undef(b.i8ptrType))
// Add the parent goroutine handle.
args = append(args, llvm.Undef(b.i8ptrType))
return fnCast, args
} }
// getInterfaceInvokeWrapper returns a wrapper for the given method so it can be // getInterfaceInvokeWrapper returns a wrapper for the given method so it can be
@@ -521,7 +474,6 @@ func (c *compilerContext) getInterfaceInvokeWrapper(fn *ssa.Function, llvmFn llv
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)
c.addStandardAttributes(wrapper)
wrapper.LastParam().SetName("parentHandle") wrapper.LastParam().SetName("parentHandle")
wrapper.SetLinkage(llvm.LinkOnceODRLinkage) wrapper.SetLinkage(llvm.LinkOnceODRLinkage)
+6 -6
View File
@@ -36,22 +36,22 @@ func (b *builder) createInterruptGlobal(instr *ssa.CallCommon) (llvm.Value, erro
// Fall back to a generic error. // Fall back to a generic error.
return llvm.Value{}, b.makeError(instr.Pos(), "interrupt function must be constant") return llvm.Value{}, b.makeError(instr.Pos(), "interrupt function must be constant")
} }
funcRawPtr, funcContext := b.decodeFuncValue(funcValue, nil)
funcPtr := llvm.ConstPtrToInt(funcRawPtr, b.uintptrType)
// 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.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 := b.fn.Package().Pkg.Path() + "$interrupt" + strconv.FormatInt(id.Int64(), 10) globalName := "runtime/interrupt.$interrupt" + strconv.FormatInt(id.Int64(), 10)
if global := b.mod.NamedGlobal(globalName); !global.IsNil() {
return llvm.Value{}, b.makeError(instr.Pos(), "interrupt redeclared in this program")
}
global := llvm.AddGlobal(b.mod, globalLLVMType, globalName) global := llvm.AddGlobal(b.mod, globalLLVMType, globalName)
global.SetVisibility(llvm.HiddenVisibility) global.SetVisibility(llvm.HiddenVisibility)
global.SetGlobalConstant(true) global.SetGlobalConstant(true)
global.SetUnnamedAddr(true) global.SetUnnamedAddr(true)
initializer := llvm.ConstNull(globalLLVMType) initializer := llvm.ConstNull(globalLLVMType)
initializer = llvm.ConstInsertValue(initializer, funcContext, []uint32{0}) initializer = llvm.ConstInsertValue(initializer, funcValue, []uint32{0})
initializer = llvm.ConstInsertValue(initializer, funcPtr, []uint32{1}) initializer = llvm.ConstInsertValue(initializer, llvm.ConstInt(b.intType, uint64(id.Int64()), true), []uint32{1, 0})
initializer = llvm.ConstInsertValue(initializer, llvm.ConstInt(b.intType, uint64(id.Int64()), true), []uint32{2, 0})
global.SetInitializer(initializer) global.SetInitializer(initializer)
// Add debug info to the interrupt global. // Add debug info to the interrupt global.
-54
View File
@@ -48,57 +48,3 @@ func (b *builder) createMemoryZeroCall(args []ssa.Value) (llvm.Value, error) {
b.CreateCall(llvmFn, params, "") b.CreateCall(llvmFn, params, "")
return llvm.Value{}, nil return llvm.Value{}, nil
} }
var mathToLLVMMapping = map[string]string{
"math.Sqrt": "llvm.sqrt.f64",
"math.Floor": "llvm.floor.f64",
"math.Ceil": "llvm.ceil.f64",
"math.Trunc": "llvm.trunc.f64",
}
// createMathOp tries to lower the given call as a LLVM math intrinsic, if
// possible. It returns the call result if possible, and a boolean whether it
// succeeded. If it doesn't succeed, the architecture doesn't support the given
// intrinsic.
func (b *builder) createMathOp(call *ssa.CallCommon) (llvm.Value, bool) {
// Check whether this intrinsic is supported on the given GOARCH.
// If it is unsupported, this can have two reasons:
//
// 1. LLVM can expand the intrinsic inline (using float instructions), but
// the result doesn't pass the tests of the math package.
// 2. LLVM cannot expand the intrinsic inline, will therefore lower it as a
// libm function call, but the libm function call also fails the math
// package tests.
//
// Whatever the implementation, it must pass the tests in the math package
// so unfortunately only the below intrinsic+architecture combinations are
// supported.
name := call.StaticCallee().RelString(nil)
switch name {
case "math.Ceil", "math.Floor", "math.Trunc":
if b.GOARCH != "wasm" && b.GOARCH != "arm64" {
return llvm.Value{}, false
}
case "math.Sqrt":
if b.GOARCH != "wasm" && b.GOARCH != "amd64" && b.GOARCH != "386" {
return llvm.Value{}, false
}
default:
return llvm.Value{}, false // only the above functions are supported.
}
llvmFn := b.mod.NamedFunction(mathToLLVMMapping[name])
if llvmFn.IsNil() {
// The intrinsic doesn't exist yet, so declare it.
// At the moment, all supported intrinsics have the form "double
// foo(double %x)" so we can hardcode the signature here.
llvmType := llvm.FunctionType(b.ctx.DoubleType(), []llvm.Type{b.ctx.DoubleType()}, false)
llvmFn = llvm.AddFunction(b.mod, mathToLLVMMapping[name], llvmType)
}
// Create a call to the intrinsic.
args := make([]llvm.Value, len(call.Args))
for i, arg := range call.Args {
args[i] = b.getValue(arg)
}
return b.CreateCall(llvmFn, args, ""), true
}
+16 -202
View File
@@ -1,11 +1,6 @@
package compiler package compiler
import ( import (
"fmt"
"go/token"
"go/types"
"math/big"
"github.com/tinygo-org/tinygo/compiler/llvmutil" "github.com/tinygo-org/tinygo/compiler/llvmutil"
"tinygo.org/x/go-llvm" "tinygo.org/x/go-llvm"
) )
@@ -13,6 +8,21 @@ import (
// This file contains helper functions for LLVM that are not exposed in the Go // This file contains helper functions for LLVM that are not exposed in the Go
// bindings. // bindings.
// Return a list of values (actually, instructions) where this value is used as
// an operand.
func getUses(value llvm.Value) []llvm.Value {
if value.IsNil() {
return nil
}
var uses []llvm.Value
use := value.FirstUse()
for !use.IsNil() {
uses = append(uses, use.User())
use = use.NextUse()
}
return uses
}
// createTemporaryAlloca creates a new alloca in the entry block and adds // createTemporaryAlloca creates a new alloca in the entry block and adds
// lifetime start information in the IR signalling that the alloca won't be used // lifetime start information in the IR signalling that the alloca won't be used
// before this point. // before this point.
@@ -34,7 +44,7 @@ func (b *builder) emitLifetimeEnd(ptr, size llvm.Value) {
// bitcasts, or else allocates a value on the heap if it cannot be packed in the // bitcasts, or else allocates a value on the heap if it cannot be packed in the
// pointer value directly. It returns the pointer with the packed data. // pointer value directly. It returns the pointer with the packed data.
func (b *builder) emitPointerPack(values []llvm.Value) llvm.Value { func (b *builder) emitPointerPack(values []llvm.Value) llvm.Value {
return llvmutil.EmitPointerPack(b.Builder, b.mod, b.pkg.Path(), b.NeedsStackObjects, values) return llvmutil.EmitPointerPack(b.Builder, b.mod, b.NeedsStackObjects, values)
} }
// emitPointerUnpack extracts a list of values packed using emitPointerPack. // emitPointerUnpack extracts a list of values packed using emitPointerPack.
@@ -57,199 +67,3 @@ func (c *compilerContext) makeGlobalArray(buf []byte, name string, elementType l
global.SetInitializer(value) global.SetInitializer(value)
return global return global
} }
// createObjectLayout returns a LLVM value (of type i8*) that describes where
// there are pointers in the type t. If all the data fits in a word, it is
// returned as a word. Otherwise it will store the data in a global.
//
// The value contains two pieces of information: the length of the object and
// which words contain a pointer (indicated by setting the given bit to 1). For
// arrays, only the element is stored. This works because the GC knows the
// object size and can therefore know how this value is repeated in the object.
func (c *compilerContext) createObjectLayout(t llvm.Type, pos token.Pos) llvm.Value {
// Use the element type for arrays. This works even for nested arrays.
for {
kind := t.TypeKind()
if kind == llvm.ArrayTypeKind {
t = t.ElementType()
continue
}
if kind == llvm.StructTypeKind {
fields := t.StructElementTypes()
if len(fields) == 1 {
t = fields[0]
continue
}
}
break
}
// Do a few checks to see whether we need to generate any object layout
// information at all.
objectSizeBytes := c.targetData.TypeAllocSize(t)
pointerSize := c.targetData.TypeAllocSize(c.i8ptrType)
pointerAlignment := c.targetData.PrefTypeAlignment(c.i8ptrType)
if objectSizeBytes < pointerSize {
// Too small to contain a pointer.
layout := (uint64(1) << 1) | 1
return llvm.ConstIntToPtr(llvm.ConstInt(c.uintptrType, layout, false), c.i8ptrType)
}
bitmap := c.getPointerBitmap(t, pos)
if bitmap.BitLen() == 0 {
// There are no pointers in this type, so we can simplify the layout.
// TODO: this can be done in many other cases, e.g. when allocating an
// array (like [4][]byte, which repeats a slice 4 times).
layout := (uint64(1) << 1) | 1
return llvm.ConstIntToPtr(llvm.ConstInt(c.uintptrType, layout, false), c.i8ptrType)
}
if objectSizeBytes%uint64(pointerAlignment) != 0 {
// This shouldn't happen except for packed structs, which aren't
// currently used.
c.addError(pos, "internal error: unexpected object size for object with pointer field")
return llvm.ConstNull(c.i8ptrType)
}
objectSizeWords := objectSizeBytes / uint64(pointerAlignment)
pointerBits := pointerSize * 8
var sizeFieldBits uint64
switch pointerBits {
case 16:
sizeFieldBits = 4
case 32:
sizeFieldBits = 5
case 64:
sizeFieldBits = 6
default:
panic("unknown pointer size")
}
layoutFieldBits := pointerBits - 1 - sizeFieldBits
// Try to emit the value as an inline integer. This is possible in most
// cases.
if objectSizeWords < layoutFieldBits {
// If it can be stored directly in the pointer value, do so.
// The runtime knows that if the least significant bit of the pointer is
// set, the pointer contains the value itself.
layout := bitmap.Uint64()<<(sizeFieldBits+1) | (objectSizeWords << 1) | 1
return llvm.ConstIntToPtr(llvm.ConstInt(c.uintptrType, layout, false), c.i8ptrType)
}
// Unfortunately, the object layout is too big to fit in a pointer-sized
// integer. Store it in a global instead.
// Try first whether the global already exists. All objects with a
// particular name have the same type, so this is possible.
globalName := "runtime/gc.layout:" + fmt.Sprintf("%d-%0*x", objectSizeWords, (objectSizeWords+15)/16, bitmap)
global := c.mod.NamedGlobal(globalName)
if !global.IsNil() {
return llvm.ConstBitCast(global, c.i8ptrType)
}
// Create the global initializer.
bitmapBytes := make([]byte, int(objectSizeWords+7)/8)
copy(bitmapBytes, bitmap.Bytes())
var bitmapByteValues []llvm.Value
for _, b := range bitmapBytes {
bitmapByteValues = append(bitmapByteValues, llvm.ConstInt(c.ctx.Int8Type(), uint64(b), false))
}
initializer := c.ctx.ConstStruct([]llvm.Value{
llvm.ConstInt(c.uintptrType, objectSizeWords, false),
llvm.ConstArray(c.ctx.Int8Type(), bitmapByteValues),
}, false)
global = llvm.AddGlobal(c.mod, initializer.Type(), globalName)
global.SetInitializer(initializer)
global.SetUnnamedAddr(true)
global.SetGlobalConstant(true)
global.SetLinkage(llvm.LinkOnceODRLinkage)
if c.targetData.PrefTypeAlignment(c.uintptrType) < 2 {
// AVR doesn't have alignment by default.
global.SetAlignment(2)
}
if c.Debug && pos != token.NoPos {
// Creating a fake global so that the value can be inspected in GDB.
// For example, the layout for strings.stringFinder (as of Go version
// 1.15) has the following type according to GDB:
// type = struct {
// uintptr numBits;
// uint8 data[33];
// }
// ...that's sort of a mixed C/Go type, but it is readable. More
// importantly, these object layout globals can be read and printed by
// GDB which may be useful for debugging.
position := c.program.Fset.Position(pos)
diglobal := c.dibuilder.CreateGlobalVariableExpression(c.difiles[position.Filename], llvm.DIGlobalVariableExpression{
Name: globalName,
File: c.getDIFile(position.Filename),
Line: position.Line,
Type: c.getDIType(types.NewStruct([]*types.Var{
types.NewVar(pos, nil, "numBits", types.Typ[types.Uintptr]),
types.NewVar(pos, nil, "data", types.NewArray(types.Typ[types.Byte], int64(len(bitmapByteValues)))),
}, nil)),
LocalToUnit: false,
Expr: c.dibuilder.CreateExpression(nil),
})
global.AddMetadata(0, diglobal)
}
return llvm.ConstBitCast(global, c.i8ptrType)
}
// getPointerBitmap scans the given LLVM type for pointers and sets bits in a
// bigint at the word offset that contains a pointer. This scan is recursive.
func (c *compilerContext) getPointerBitmap(typ llvm.Type, pos token.Pos) *big.Int {
alignment := c.targetData.PrefTypeAlignment(c.i8ptrType)
switch typ.TypeKind() {
case llvm.IntegerTypeKind, llvm.FloatTypeKind, llvm.DoubleTypeKind:
return big.NewInt(0)
case llvm.PointerTypeKind:
return big.NewInt(1)
case llvm.StructTypeKind:
ptrs := big.NewInt(0)
if typ.StructName() == "runtime.funcValue" {
// Hack: the type runtime.funcValue contains an 'id' field which is
// of type uintptr, but before the LowerFuncValues pass it actually
// contains a pointer (ptrtoint) to a global. This trips up the
// interp package. Therefore, make the id field a pointer for now.
typ = c.ctx.StructType([]llvm.Type{c.i8ptrType, c.i8ptrType}, false)
}
for i, subtyp := range typ.StructElementTypes() {
subptrs := c.getPointerBitmap(subtyp, pos)
if subptrs.BitLen() == 0 {
continue
}
offset := c.targetData.ElementOffset(typ, i)
if offset%uint64(alignment) != 0 {
// This error will let the compilation fail, but by continuing
// the error can still easily be shown.
c.addError(pos, "internal error: allocated struct contains unaligned pointer")
continue
}
subptrs.Lsh(subptrs, uint(offset)/uint(alignment))
ptrs.Or(ptrs, subptrs)
}
return ptrs
case llvm.ArrayTypeKind:
subtyp := typ.ElementType()
subptrs := c.getPointerBitmap(subtyp, pos)
ptrs := big.NewInt(0)
if subptrs.BitLen() == 0 {
return ptrs
}
elementSize := c.targetData.TypeAllocSize(subtyp)
if elementSize%uint64(alignment) != 0 {
// This error will let the compilation fail (but continues so that
// other errors can be shown).
c.addError(pos, "internal error: allocated array contains unaligned pointer")
return ptrs
}
for i := 0; i < typ.ArrayLength(); i++ {
ptrs.Lsh(ptrs, uint(elementSize)/uint(alignment))
ptrs.Or(ptrs, subptrs)
}
return ptrs
default:
// Should not happen.
panic("unknown LLVM type")
}
}
+4 -4
View File
@@ -12,7 +12,7 @@ import (
// bitcasts, or else allocates a value on the heap if it cannot be packed in the // bitcasts, or else allocates a value on the heap if it cannot be packed in the
// pointer value directly. It returns the pointer with the packed data. // pointer value directly. It returns the pointer with the packed data.
// If the values are all constants, they are be stored in a constant global and deduplicated. // If the values are all constants, they are be stored in a constant global and deduplicated.
func EmitPointerPack(builder llvm.Builder, mod llvm.Module, prefix string, needsStackObjects bool, values []llvm.Value) llvm.Value { func EmitPointerPack(builder llvm.Builder, mod llvm.Module, needsStackObjects bool, values []llvm.Value) llvm.Value {
ctx := mod.Context() ctx := mod.Context()
targetData := llvm.NewTargetData(mod.DataLayout()) targetData := llvm.NewTargetData(mod.DataLayout())
i8ptrType := llvm.PointerType(mod.Context().Int8Type(), 0) i8ptrType := llvm.PointerType(mod.Context().Int8Type(), 0)
@@ -83,11 +83,12 @@ func EmitPointerPack(builder llvm.Builder, mod llvm.Module, prefix string, needs
if constant { if constant {
// The data is known at compile time, so store it in a constant global. // The data is known at compile time, so store it in a constant global.
// The global address is marked as unnamed, which allows LLVM to merge duplicates. // The global address is marked as unnamed, which allows LLVM to merge duplicates.
global := llvm.AddGlobal(mod, packedType, prefix+"$pack") funcName := builder.GetInsertBlock().Parent().Name()
global := llvm.AddGlobal(mod, packedType, funcName+"$pack")
global.SetInitializer(ctx.ConstStruct(values, false)) global.SetInitializer(ctx.ConstStruct(values, false))
global.SetGlobalConstant(true) global.SetGlobalConstant(true)
global.SetUnnamedAddr(true) global.SetUnnamedAddr(true)
global.SetLinkage(llvm.InternalLinkage) global.SetLinkage(llvm.PrivateLinkage)
return llvm.ConstBitCast(global, i8ptrType) return llvm.ConstBitCast(global, i8ptrType)
} }
@@ -96,7 +97,6 @@ func EmitPointerPack(builder llvm.Builder, mod llvm.Module, prefix string, needs
alloc := mod.NamedFunction("runtime.alloc") alloc := mod.NamedFunction("runtime.alloc")
packedHeapAlloc := builder.CreateCall(alloc, []llvm.Value{ packedHeapAlloc := builder.CreateCall(alloc, []llvm.Value{
sizeValue, sizeValue,
llvm.ConstNull(i8ptrType),
llvm.Undef(i8ptrType), // unused context parameter llvm.Undef(i8ptrType), // unused context parameter
llvm.ConstPointerNull(i8ptrType), // coroutine handle llvm.ConstPointerNull(i8ptrType), // coroutine handle
}, "") }, "")
+5 -75
View File
@@ -24,9 +24,7 @@ type functionInfo struct {
module string // go:wasm-module module string // go:wasm-module
importName string // go:linkname, go:export - The name the developer assigns importName string // go:linkname, go:export - The name the developer assigns
linkName string // go:linkname, go:export - The name that we map for the particular module -> importName linkName string // go:linkname, go:export - The name that we map for the particular module -> importName
section string // go:section - object file section name
exported bool // go:export, CGo exported bool // go:export, CGo
interrupt bool // go:interrupt
nobounds bool // go:nobounds nobounds bool // go:nobounds
variadic bool // go:variadic (CGo only) variadic bool // go:variadic (CGo only)
inline inlineType // go:inline inline inlineType // go:inline
@@ -109,7 +107,6 @@ func (c *compilerContext) getFunction(fn *ssa.Function) llvm.Value {
llvmFn.AddFunctionAttr(attr) llvmFn.AddFunctionAttr(attr)
} }
} }
c.addStandardDeclaredAttributes(llvmFn)
dereferenceableOrNullKind := llvm.AttributeKindID("dereferenceable_or_null") dereferenceableOrNullKind := llvm.AttributeKindID("dereferenceable_or_null")
for i, info := range paramInfos { for i, info := range paramInfos {
@@ -142,17 +139,6 @@ func (c *compilerContext) getFunction(fn *ssa.Function) llvm.Value {
for _, attrName := range []string{"noalias", "nonnull"} { for _, attrName := range []string{"noalias", "nonnull"} {
llvmFn.AddAttributeAtIndex(0, c.ctx.CreateEnumAttribute(llvm.AttributeKindID(attrName), 0)) llvmFn.AddAttributeAtIndex(0, c.ctx.CreateEnumAttribute(llvm.AttributeKindID(attrName), 0))
} }
case "runtime.sliceAppend":
// Appending a slice will only read the to-be-appended slice, it won't
// be modified.
llvmFn.AddAttributeAtIndex(2, c.ctx.CreateEnumAttribute(llvm.AttributeKindID("nocapture"), 0))
llvmFn.AddAttributeAtIndex(2, c.ctx.CreateEnumAttribute(llvm.AttributeKindID("readonly"), 0))
case "runtime.sliceCopy":
// Copying a slice won't capture any of the parameters.
llvmFn.AddAttributeAtIndex(1, c.ctx.CreateEnumAttribute(llvm.AttributeKindID("writeonly"), 0))
llvmFn.AddAttributeAtIndex(1, c.ctx.CreateEnumAttribute(llvm.AttributeKindID("nocapture"), 0))
llvmFn.AddAttributeAtIndex(2, c.ctx.CreateEnumAttribute(llvm.AttributeKindID("readonly"), 0))
llvmFn.AddAttributeAtIndex(2, c.ctx.CreateEnumAttribute(llvm.AttributeKindID("nocapture"), 0))
case "runtime.trackPointer": case "runtime.trackPointer":
// This function is necessary for tracking pointers on the stack in a // This function is necessary for tracking pointers on the stack in a
// portable way (see gc_stack_portable.go). Indicate to the optimizer // portable way (see gc_stack_portable.go). Indicate to the optimizer
@@ -252,10 +238,6 @@ func (info *functionInfo) parsePragmas(f *ssa.Function) {
importName = parts[1] importName = parts[1]
info.exported = true info.exported = true
case "//go:interrupt":
if hasUnsafeImport(f.Pkg.Pkg) {
info.interrupt = true
}
case "//go:wasm-module": case "//go:wasm-module":
// Alternative comment for setting the import module. // Alternative comment for setting the import module.
if len(parts) != 2 { if len(parts) != 2 {
@@ -277,10 +259,6 @@ func (info *functionInfo) parsePragmas(f *ssa.Function) {
if hasUnsafeImport(f.Pkg.Pkg) { if hasUnsafeImport(f.Pkg.Pkg) {
info.linkName = parts[2] info.linkName = parts[2]
} }
case "//go:section":
if len(parts) == 2 && hasUnsafeImport(f.Pkg.Pkg) {
info.section = parts[1]
}
case "//go:nobounds": case "//go:nobounds":
// Skip bounds checking in this function. Useful for some // Skip bounds checking in this function. Useful for some
// runtime functions. // runtime functions.
@@ -329,47 +307,6 @@ func getParams(sig *types.Signature) []*types.Var {
return params return params
} }
// addStandardDeclaredAttributes adds attributes that are set for any function,
// whether declared or defined.
func (c *compilerContext) addStandardDeclaredAttributes(llvmFn llvm.Value) {
if c.SizeLevel >= 1 {
// Set the "optsize" attribute to make slightly smaller binaries at the
// cost of minimal performance loss (-Os in Clang).
kind := llvm.AttributeKindID("optsize")
attr := c.ctx.CreateEnumAttribute(kind, 0)
llvmFn.AddFunctionAttr(attr)
}
if c.SizeLevel >= 2 {
// Set the "minsize" attribute to reduce code size even further,
// regardless of performance loss (-Oz in Clang).
kind := llvm.AttributeKindID("minsize")
attr := c.ctx.CreateEnumAttribute(kind, 0)
llvmFn.AddFunctionAttr(attr)
}
}
// addStandardDefinedAttributes adds the set of attributes that are added to
// every function defined by TinyGo (even thunks/wrappers), possibly depending
// on the architecture. It does not set attributes only set for declared
// functions, use addStandardDeclaredAttributes for this.
func (c *compilerContext) addStandardDefinedAttributes(llvmFn llvm.Value) {
// TinyGo does not currently raise exceptions, so set the 'nounwind' flag.
// This behavior matches Clang when compiling C source files.
// It reduces binary size on Linux a little bit on non-x86_64 targets by
// eliminating exception tables for these functions.
llvmFn.AddFunctionAttr(c.ctx.CreateEnumAttribute(llvm.AttributeKindID("nounwind"), 0))
if strings.Split(c.Triple, "-")[0] == "x86_64" {
// Required by the ABI.
llvmFn.AddFunctionAttr(c.ctx.CreateEnumAttribute(llvm.AttributeKindID("uwtable"), 0))
}
}
// addStandardAttribute adds all attributes added to defined functions.
func (c *compilerContext) addStandardAttributes(llvmFn llvm.Value) {
c.addStandardDeclaredAttributes(llvmFn)
c.addStandardDefinedAttributes(llvmFn)
}
// 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).
@@ -377,7 +314,6 @@ type globalInfo struct {
linkName string // go:extern linkName string // go:extern
extern bool // go:extern extern bool // go:extern
align int // go:align align int // go:align
section string // go:section
} }
// loadASTComments loads comments on globals from the AST, for use later in the // loadASTComments loads comments on globals from the AST, for use later in the
@@ -419,18 +355,16 @@ func (c *compilerContext) getGlobal(g *ssa.Global) llvm.Value {
// Set alignment from the //go:align comment. // Set alignment from the //go:align comment.
var alignInBits uint32 var alignInBits uint32
alignment := c.targetData.ABITypeAlignment(llvmType) if info.align < 0 || info.align&(info.align-1) != 0 {
if info.align > alignment {
alignment = info.align
}
if alignment <= 0 || alignment&(alignment-1) != 0 {
// Check for power-of-two (or 0). // Check for power-of-two (or 0).
// See: https://stackoverflow.com/a/108360 // See: https://stackoverflow.com/a/108360
c.addError(g.Pos(), "global variable alignment must be a positive power of two") c.addError(g.Pos(), "global variable alignment must be a positive power of two")
} else { } else {
// Set the alignment only when it is a power of two. // Set the alignment only when it is a power of two.
alignInBits = uint32(alignment) ^ uint32(alignment-1) alignInBits = uint32(info.align) ^ uint32(info.align-1)
llvmGlobal.SetAlignment(alignment) if info.align > c.targetData.ABITypeAlignment(llvmType) {
llvmGlobal.SetAlignment(info.align)
}
} }
if c.Debug && !info.extern { if c.Debug && !info.extern {
@@ -491,10 +425,6 @@ func (info *globalInfo) parsePragmas(doc *ast.CommentGroup) {
if err == nil { if err == nil {
info.align = align info.align = align
} }
case "//go:section":
if len(parts) == 2 {
info.section = parts[1]
}
} }
} }
} }
+8 -30
View File
@@ -10,11 +10,11 @@ import (
"tinygo.org/x/go-llvm" "tinygo.org/x/go-llvm"
) )
// createRawSyscall creates a system call with the provided system call number // createSyscall emits an inline system call instruction, depending on the
// and returns the result as a single integer (the system call result). The // target OS/arch.
// result is not further interpreted. func (b *builder) createSyscall(call *ssa.CallCommon) (llvm.Value, error) {
func (b *builder) createRawSyscall(call *ssa.CallCommon) (llvm.Value, error) {
num := b.getValue(call.Args[0]) num := b.getValue(call.Args[0])
var syscallResult llvm.Value
switch { switch {
case b.GOARCH == "amd64": case b.GOARCH == "amd64":
if b.GOOS == "darwin" { if b.GOOS == "darwin" {
@@ -57,7 +57,7 @@ func (b *builder) createRawSyscall(call *ssa.CallCommon) (llvm.Value, error) {
constraints += ",~{rcx},~{r11}" constraints += ",~{rcx},~{r11}"
fnType := llvm.FunctionType(b.uintptrType, argTypes, false) fnType := llvm.FunctionType(b.uintptrType, argTypes, false)
target := llvm.InlineAsm(fnType, "syscall", constraints, true, false, llvm.InlineAsmDialectIntel) target := llvm.InlineAsm(fnType, "syscall", constraints, true, false, llvm.InlineAsmDialectIntel)
return b.CreateCall(target, args, ""), nil syscallResult = b.CreateCall(target, args, "")
case b.GOARCH == "386" && b.GOOS == "linux": case b.GOARCH == "386" && b.GOOS == "linux":
// Sources: // Sources:
// syscall(2) man page // syscall(2) man page
@@ -83,7 +83,7 @@ func (b *builder) createRawSyscall(call *ssa.CallCommon) (llvm.Value, error) {
} }
fnType := llvm.FunctionType(b.uintptrType, argTypes, false) fnType := llvm.FunctionType(b.uintptrType, argTypes, false)
target := llvm.InlineAsm(fnType, "int 0x80", constraints, true, false, llvm.InlineAsmDialectIntel) target := llvm.InlineAsm(fnType, "int 0x80", constraints, true, false, llvm.InlineAsmDialectIntel)
return b.CreateCall(target, args, ""), nil syscallResult = b.CreateCall(target, args, "")
case b.GOARCH == "arm" && b.GOOS == "linux": case b.GOARCH == "arm" && b.GOOS == "linux":
// Implement the EABI system call convention for Linux. // Implement the EABI system call convention for Linux.
// Source: syscall(2) man page. // Source: syscall(2) man page.
@@ -115,7 +115,7 @@ func (b *builder) createRawSyscall(call *ssa.CallCommon) (llvm.Value, error) {
} }
fnType := llvm.FunctionType(b.uintptrType, argTypes, false) fnType := llvm.FunctionType(b.uintptrType, argTypes, false)
target := llvm.InlineAsm(fnType, "svc #0", constraints, true, false, 0) target := llvm.InlineAsm(fnType, "svc #0", constraints, true, false, 0)
return b.CreateCall(target, args, ""), nil syscallResult = b.CreateCall(target, args, "")
case b.GOARCH == "arm64" && b.GOOS == "linux": case b.GOARCH == "arm64" && b.GOOS == "linux":
// Source: syscall(2) man page. // Source: syscall(2) man page.
args := []llvm.Value{} args := []llvm.Value{}
@@ -147,19 +147,10 @@ func (b *builder) createRawSyscall(call *ssa.CallCommon) (llvm.Value, error) {
constraints += ",~{x16},~{x17}" // scratch registers constraints += ",~{x16},~{x17}" // scratch registers
fnType := llvm.FunctionType(b.uintptrType, argTypes, false) fnType := llvm.FunctionType(b.uintptrType, argTypes, false)
target := llvm.InlineAsm(fnType, "svc #0", constraints, true, false, 0) target := llvm.InlineAsm(fnType, "svc #0", constraints, true, false, 0)
return b.CreateCall(target, args, ""), nil syscallResult = b.CreateCall(target, args, "")
default: default:
return llvm.Value{}, b.makeError(call.Pos(), "unknown GOOS/GOARCH for syscall: "+b.GOOS+"/"+b.GOARCH) return llvm.Value{}, b.makeError(call.Pos(), "unknown GOOS/GOARCH for syscall: "+b.GOOS+"/"+b.GOARCH)
} }
}
// createSyscall emits instructions for the syscall.Syscall* family of
// functions, depending on the target OS/arch.
func (b *builder) createSyscall(call *ssa.CallCommon) (llvm.Value, error) {
syscallResult, err := b.createRawSyscall(call)
if err != nil {
return syscallResult, err
}
switch b.GOOS { switch b.GOOS {
case "linux", "freebsd": case "linux", "freebsd":
// Return values: r0, r1 uintptr, err Errno // Return values: r0, r1 uintptr, err Errno
@@ -199,16 +190,3 @@ func (b *builder) createSyscall(call *ssa.CallCommon) (llvm.Value, error) {
return llvm.Value{}, b.makeError(call.Pos(), "unknown GOOS/GOARCH for syscall: "+b.GOOS+"/"+b.GOARCH) return llvm.Value{}, b.makeError(call.Pos(), "unknown GOOS/GOARCH for syscall: "+b.GOOS+"/"+b.GOARCH)
} }
} }
// createRawSyscallNoError emits instructions for the Linux-specific
// syscall.rawSyscallNoError function.
func (b *builder) createRawSyscallNoError(call *ssa.CallCommon) (llvm.Value, error) {
syscallResult, err := b.createRawSyscall(call)
if err != nil {
return syscallResult, err
}
retval := llvm.ConstNull(b.ctx.StructType([]llvm.Type{b.uintptrType, b.uintptrType}, false))
retval = b.CreateInsertValue(retval, syscallResult, 0, "")
retval = b.CreateInsertValue(retval, llvm.ConstInt(b.uintptrType, 0, false), 1, "")
return retval, nil
}
-31
View File
@@ -10,22 +10,6 @@ func equalInt(x, y int) bool {
return x == y return x == y
} }
func divInt(x, y int) int {
return x / y
}
func divUint(x, y uint) uint {
return x / y
}
func remInt(x, y int) int {
return x % y
}
func remUint(x, y uint) uint {
return x % y
}
func floatEQ(x, y float32) bool { func floatEQ(x, y float32) bool {
return x == y return x == y
} }
@@ -71,18 +55,3 @@ func complexMul(x, y complex64) complex64 {
} }
// TODO: complexDiv (requires runtime call) // TODO: complexDiv (requires runtime call)
// A type 'kv' also exists in function foo. Test that these two types don't
// conflict with each other.
type kv struct {
v float32
}
func foo(a *kv) {
// Define a new 'kv' type.
type kv struct {
v byte
}
// Use this type.
func(b *kv) {}(nil)
}
+17 -119
View File
@@ -1,159 +1,74 @@
; ModuleID = 'basic.go' ; ModuleID = 'basic.go'
source_filename = "basic.go" source_filename = "basic.go"
target datalayout = "e-m:e-p:32:32-i64:64-n32:64-S128" target datalayout = "e-m:e-p:32:32-p270:32:32-p271:32:32-p272:64:64-f64:32:64-f80:32-n8:16:32-S128"
target triple = "wasm32-unknown-wasi" target triple = "i686--linux"
%main.kv = type { float } declare noalias nonnull i8* @runtime.alloc(i32, i8*, i8*)
%main.kv.0 = type { i8 }
declare noalias nonnull i8* @runtime.alloc(i32, i8*, i8*, i8*) define hidden void @main.init(i8* %context, i8* %parentHandle) unnamed_addr {
; Function Attrs: nounwind
define hidden void @main.init(i8* %context, i8* %parentHandle) unnamed_addr #0 {
entry: entry:
ret void ret void
} }
; Function Attrs: nounwind define hidden i32 @main.addInt(i32 %x, i32 %y, i8* %context, i8* %parentHandle) unnamed_addr {
define hidden i32 @main.addInt(i32 %x, i32 %y, i8* %context, i8* %parentHandle) unnamed_addr #0 {
entry: entry:
%0 = add i32 %x, %y %0 = add i32 %x, %y
ret i32 %0 ret i32 %0
} }
; Function Attrs: nounwind define hidden i1 @main.equalInt(i32 %x, i32 %y, i8* %context, i8* %parentHandle) unnamed_addr {
define hidden i1 @main.equalInt(i32 %x, i32 %y, i8* %context, i8* %parentHandle) unnamed_addr #0 {
entry: entry:
%0 = icmp eq i32 %x, %y %0 = icmp eq i32 %x, %y
ret i1 %0 ret i1 %0
} }
; Function Attrs: nounwind define hidden i1 @main.floatEQ(float %x, float %y, i8* %context, i8* %parentHandle) unnamed_addr {
define hidden i32 @main.divInt(i32 %x, i32 %y, i8* %context, i8* %parentHandle) unnamed_addr #0 {
entry:
%0 = icmp eq i32 %y, 0
br i1 %0, label %divbyzero.throw, label %divbyzero.next
divbyzero.throw: ; preds = %entry
call void @runtime.divideByZeroPanic(i8* undef, i8* null) #0
unreachable
divbyzero.next: ; preds = %entry
%1 = icmp eq i32 %y, -1
%2 = icmp eq i32 %x, -2147483648
%3 = and i1 %1, %2
%4 = select i1 %3, i32 1, i32 %y
%5 = sdiv i32 %x, %4
ret i32 %5
}
declare void @runtime.divideByZeroPanic(i8*, i8*)
; Function Attrs: nounwind
define hidden i32 @main.divUint(i32 %x, i32 %y, i8* %context, i8* %parentHandle) unnamed_addr #0 {
entry:
%0 = icmp eq i32 %y, 0
br i1 %0, label %divbyzero.throw, label %divbyzero.next
divbyzero.throw: ; preds = %entry
call void @runtime.divideByZeroPanic(i8* undef, i8* null) #0
unreachable
divbyzero.next: ; preds = %entry
%1 = udiv i32 %x, %y
ret i32 %1
}
; Function Attrs: nounwind
define hidden i32 @main.remInt(i32 %x, i32 %y, i8* %context, i8* %parentHandle) unnamed_addr #0 {
entry:
%0 = icmp eq i32 %y, 0
br i1 %0, label %divbyzero.throw, label %divbyzero.next
divbyzero.throw: ; preds = %entry
call void @runtime.divideByZeroPanic(i8* undef, i8* null) #0
unreachable
divbyzero.next: ; preds = %entry
%1 = icmp eq i32 %y, -1
%2 = icmp eq i32 %x, -2147483648
%3 = and i1 %1, %2
%4 = select i1 %3, i32 1, i32 %y
%5 = srem i32 %x, %4
ret i32 %5
}
; Function Attrs: nounwind
define hidden i32 @main.remUint(i32 %x, i32 %y, i8* %context, i8* %parentHandle) unnamed_addr #0 {
entry:
%0 = icmp eq i32 %y, 0
br i1 %0, label %divbyzero.throw, label %divbyzero.next
divbyzero.throw: ; preds = %entry
call void @runtime.divideByZeroPanic(i8* undef, i8* null) #0
unreachable
divbyzero.next: ; preds = %entry
%1 = urem i32 %x, %y
ret i32 %1
}
; Function Attrs: nounwind
define hidden i1 @main.floatEQ(float %x, float %y, i8* %context, i8* %parentHandle) unnamed_addr #0 {
entry: entry:
%0 = fcmp oeq float %x, %y %0 = fcmp oeq float %x, %y
ret i1 %0 ret i1 %0
} }
; Function Attrs: nounwind define hidden i1 @main.floatNE(float %x, float %y, i8* %context, i8* %parentHandle) unnamed_addr {
define hidden i1 @main.floatNE(float %x, float %y, i8* %context, i8* %parentHandle) unnamed_addr #0 {
entry: entry:
%0 = fcmp une float %x, %y %0 = fcmp une float %x, %y
ret i1 %0 ret i1 %0
} }
; Function Attrs: nounwind define hidden i1 @main.floatLower(float %x, float %y, i8* %context, i8* %parentHandle) unnamed_addr {
define hidden i1 @main.floatLower(float %x, float %y, i8* %context, i8* %parentHandle) unnamed_addr #0 {
entry: entry:
%0 = fcmp olt float %x, %y %0 = fcmp olt float %x, %y
ret i1 %0 ret i1 %0
} }
; Function Attrs: nounwind define hidden i1 @main.floatLowerEqual(float %x, float %y, i8* %context, i8* %parentHandle) unnamed_addr {
define hidden i1 @main.floatLowerEqual(float %x, float %y, i8* %context, i8* %parentHandle) unnamed_addr #0 {
entry: entry:
%0 = fcmp ole float %x, %y %0 = fcmp ole float %x, %y
ret i1 %0 ret i1 %0
} }
; Function Attrs: nounwind define hidden i1 @main.floatGreater(float %x, float %y, i8* %context, i8* %parentHandle) unnamed_addr {
define hidden i1 @main.floatGreater(float %x, float %y, i8* %context, i8* %parentHandle) unnamed_addr #0 {
entry: entry:
%0 = fcmp ogt float %x, %y %0 = fcmp ogt float %x, %y
ret i1 %0 ret i1 %0
} }
; Function Attrs: nounwind define hidden i1 @main.floatGreaterEqual(float %x, float %y, i8* %context, i8* %parentHandle) unnamed_addr {
define hidden i1 @main.floatGreaterEqual(float %x, float %y, i8* %context, i8* %parentHandle) unnamed_addr #0 {
entry: entry:
%0 = fcmp oge float %x, %y %0 = fcmp oge float %x, %y
ret i1 %0 ret i1 %0
} }
; Function Attrs: nounwind define hidden float @main.complexReal(float %x.r, float %x.i, i8* %context, i8* %parentHandle) unnamed_addr {
define hidden float @main.complexReal(float %x.r, float %x.i, i8* %context, i8* %parentHandle) unnamed_addr #0 {
entry: entry:
ret float %x.r ret float %x.r
} }
; Function Attrs: nounwind define hidden float @main.complexImag(float %x.r, float %x.i, i8* %context, i8* %parentHandle) unnamed_addr {
define hidden float @main.complexImag(float %x.r, float %x.i, i8* %context, i8* %parentHandle) unnamed_addr #0 {
entry: entry:
ret float %x.i ret float %x.i
} }
; Function Attrs: nounwind define hidden { float, float } @main.complexAdd(float %x.r, float %x.i, float %y.r, float %y.i, i8* %context, i8* %parentHandle) unnamed_addr {
define hidden { float, float } @main.complexAdd(float %x.r, float %x.i, float %y.r, float %y.i, i8* %context, i8* %parentHandle) unnamed_addr #0 {
entry: entry:
%0 = fadd float %x.r, %y.r %0 = fadd float %x.r, %y.r
%1 = fadd float %x.i, %y.i %1 = fadd float %x.i, %y.i
@@ -162,8 +77,7 @@ entry:
ret { float, float } %3 ret { float, float } %3
} }
; Function Attrs: nounwind define hidden { float, float } @main.complexSub(float %x.r, float %x.i, float %y.r, float %y.i, i8* %context, i8* %parentHandle) unnamed_addr {
define hidden { float, float } @main.complexSub(float %x.r, float %x.i, float %y.r, float %y.i, i8* %context, i8* %parentHandle) unnamed_addr #0 {
entry: entry:
%0 = fsub float %x.r, %y.r %0 = fsub float %x.r, %y.r
%1 = fsub float %x.i, %y.i %1 = fsub float %x.i, %y.i
@@ -172,8 +86,7 @@ entry:
ret { float, float } %3 ret { float, float } %3
} }
; Function Attrs: nounwind define hidden { float, float } @main.complexMul(float %x.r, float %x.i, float %y.r, float %y.i, i8* %context, i8* %parentHandle) unnamed_addr {
define hidden { float, float } @main.complexMul(float %x.r, float %x.i, float %y.r, float %y.i, i8* %context, i8* %parentHandle) unnamed_addr #0 {
entry: entry:
%0 = fmul float %x.r, %y.r %0 = fmul float %x.r, %y.r
%1 = fmul float %x.i, %y.i %1 = fmul float %x.i, %y.i
@@ -185,18 +98,3 @@ entry:
%7 = insertvalue { float, float } %6, float %5, 1 %7 = insertvalue { float, float } %6, float %5, 1
ret { float, float } %7 ret { float, float } %7
} }
; Function Attrs: nounwind
define hidden void @main.foo(%main.kv* dereferenceable_or_null(4) %a, i8* %context, i8* %parentHandle) unnamed_addr #0 {
entry:
call void @"main.foo$1"(%main.kv.0* null, i8* undef, i8* undef)
ret void
}
; Function Attrs: nounwind
define hidden void @"main.foo$1"(%main.kv.0* dereferenceable_or_null(1) %b, i8* %context, i8* %parentHandle) unnamed_addr #0 {
entry:
ret void
}
attributes #0 = { nounwind }
-25
View File
@@ -1,25 +0,0 @@
package main
func chanIntSend(ch chan int) {
ch <- 3
}
func chanIntRecv(ch chan int) {
<-ch
}
func chanZeroSend(ch chan struct{}) {
ch <- struct{}{}
}
func chanZeroRecv(ch chan struct{}) {
<-ch
}
func selectZeroRecv(ch1 chan int, ch2 chan struct{}) {
select {
case ch1 <- 1:
case <-ch2:
default:
}
}
-121
View File
@@ -1,121 +0,0 @@
; ModuleID = 'channel.go'
source_filename = "channel.go"
target datalayout = "e-m:e-p:32:32-i64:64-n32:64-S128"
target triple = "wasm32-unknown-wasi"
%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 { %"internal/task.Task"*, i8*, i64, %"internal/task.state" }
%"internal/task.state" = type { i8* }
%runtime.chanSelectState = type { %runtime.channel*, i8* }
declare noalias nonnull i8* @runtime.alloc(i32, i8*, i8*, i8*)
; Function Attrs: nounwind
define hidden void @main.init(i8* %context, i8* %parentHandle) unnamed_addr #0 {
entry:
ret void
}
; Function Attrs: nounwind
define hidden void @main.chanIntSend(%runtime.channel* dereferenceable_or_null(32) %ch, i8* %context, i8* %parentHandle) unnamed_addr #0 {
entry:
%chan.blockedList = alloca %runtime.channelBlockedList, align 8
%chan.value = alloca i32, align 4
%chan.value.bitcast = bitcast i32* %chan.value to i8*
call void @llvm.lifetime.start.p0i8(i64 4, i8* nonnull %chan.value.bitcast)
store i32 3, i32* %chan.value, align 4
%chan.blockedList.bitcast = bitcast %runtime.channelBlockedList* %chan.blockedList to i8*
call void @llvm.lifetime.start.p0i8(i64 24, i8* nonnull %chan.blockedList.bitcast)
call void @runtime.chanSend(%runtime.channel* %ch, i8* nonnull %chan.value.bitcast, %runtime.channelBlockedList* nonnull %chan.blockedList, i8* undef, i8* null) #0
call void @llvm.lifetime.end.p0i8(i64 24, i8* nonnull %chan.blockedList.bitcast)
call void @llvm.lifetime.end.p0i8(i64 4, i8* nonnull %chan.value.bitcast)
ret void
}
; Function Attrs: argmemonly nounwind willreturn
declare void @llvm.lifetime.start.p0i8(i64 immarg, i8* nocapture) #1
declare void @runtime.chanSend(%runtime.channel* dereferenceable_or_null(32), i8*, %runtime.channelBlockedList* dereferenceable_or_null(24), i8*, i8*)
; Function Attrs: argmemonly nounwind willreturn
declare void @llvm.lifetime.end.p0i8(i64 immarg, i8* nocapture) #1
; Function Attrs: nounwind
define hidden void @main.chanIntRecv(%runtime.channel* dereferenceable_or_null(32) %ch, i8* %context, i8* %parentHandle) unnamed_addr #0 {
entry:
%chan.blockedList = alloca %runtime.channelBlockedList, align 8
%chan.value = alloca i32, align 4
%chan.value.bitcast = bitcast i32* %chan.value to i8*
call void @llvm.lifetime.start.p0i8(i64 4, i8* nonnull %chan.value.bitcast)
%chan.blockedList.bitcast = bitcast %runtime.channelBlockedList* %chan.blockedList to i8*
call void @llvm.lifetime.start.p0i8(i64 24, i8* nonnull %chan.blockedList.bitcast)
%0 = call i1 @runtime.chanRecv(%runtime.channel* %ch, i8* nonnull %chan.value.bitcast, %runtime.channelBlockedList* nonnull %chan.blockedList, i8* undef, i8* null) #0
call void @llvm.lifetime.end.p0i8(i64 4, i8* nonnull %chan.value.bitcast)
call void @llvm.lifetime.end.p0i8(i64 24, i8* nonnull %chan.blockedList.bitcast)
ret void
}
declare i1 @runtime.chanRecv(%runtime.channel* dereferenceable_or_null(32), i8*, %runtime.channelBlockedList* dereferenceable_or_null(24), i8*, i8*)
; Function Attrs: nounwind
define hidden void @main.chanZeroSend(%runtime.channel* dereferenceable_or_null(32) %ch, i8* %context, i8* %parentHandle) unnamed_addr #0 {
entry:
%chan.blockedList = alloca %runtime.channelBlockedList, align 8
%chan.blockedList.bitcast = bitcast %runtime.channelBlockedList* %chan.blockedList to i8*
call void @llvm.lifetime.start.p0i8(i64 24, i8* nonnull %chan.blockedList.bitcast)
call void @runtime.chanSend(%runtime.channel* %ch, i8* null, %runtime.channelBlockedList* nonnull %chan.blockedList, i8* undef, i8* null) #0
call void @llvm.lifetime.end.p0i8(i64 24, i8* nonnull %chan.blockedList.bitcast)
ret void
}
; Function Attrs: nounwind
define hidden void @main.chanZeroRecv(%runtime.channel* dereferenceable_or_null(32) %ch, i8* %context, i8* %parentHandle) unnamed_addr #0 {
entry:
%chan.blockedList = alloca %runtime.channelBlockedList, align 8
%chan.blockedList.bitcast = bitcast %runtime.channelBlockedList* %chan.blockedList to i8*
call void @llvm.lifetime.start.p0i8(i64 24, i8* nonnull %chan.blockedList.bitcast)
%0 = call i1 @runtime.chanRecv(%runtime.channel* %ch, i8* null, %runtime.channelBlockedList* nonnull %chan.blockedList, i8* undef, i8* null) #0
call void @llvm.lifetime.end.p0i8(i64 24, i8* nonnull %chan.blockedList.bitcast)
ret void
}
; Function Attrs: nounwind
define hidden void @main.selectZeroRecv(%runtime.channel* dereferenceable_or_null(32) %ch1, %runtime.channel* dereferenceable_or_null(32) %ch2, i8* %context, i8* %parentHandle) unnamed_addr #0 {
entry:
%select.states.alloca = alloca [2 x %runtime.chanSelectState], align 8
%select.send.value = alloca i32, align 4
store i32 1, i32* %select.send.value, align 4
%select.states.alloca.bitcast = bitcast [2 x %runtime.chanSelectState]* %select.states.alloca to i8*
call void @llvm.lifetime.start.p0i8(i64 16, i8* nonnull %select.states.alloca.bitcast)
%.repack = getelementptr inbounds [2 x %runtime.chanSelectState], [2 x %runtime.chanSelectState]* %select.states.alloca, i32 0, i32 0, i32 0
store %runtime.channel* %ch1, %runtime.channel** %.repack, align 8
%.repack1 = getelementptr inbounds [2 x %runtime.chanSelectState], [2 x %runtime.chanSelectState]* %select.states.alloca, i32 0, i32 0, i32 1
%0 = bitcast i8** %.repack1 to i32**
store i32* %select.send.value, i32** %0, align 4
%.repack3 = getelementptr inbounds [2 x %runtime.chanSelectState], [2 x %runtime.chanSelectState]* %select.states.alloca, i32 0, i32 1, i32 0
store %runtime.channel* %ch2, %runtime.channel** %.repack3, align 8
%.repack4 = getelementptr inbounds [2 x %runtime.chanSelectState], [2 x %runtime.chanSelectState]* %select.states.alloca, i32 0, i32 1, i32 1
store i8* null, i8** %.repack4, align 4
%select.states = getelementptr inbounds [2 x %runtime.chanSelectState], [2 x %runtime.chanSelectState]* %select.states.alloca, i32 0, i32 0
%select.result = call { i32, i1 } @runtime.tryChanSelect(i8* undef, %runtime.chanSelectState* nonnull %select.states, i32 2, i32 2, i8* undef, i8* null) #0
call void @llvm.lifetime.end.p0i8(i64 16, i8* nonnull %select.states.alloca.bitcast)
%1 = extractvalue { i32, i1 } %select.result, 0
%2 = icmp eq i32 %1, 0
br i1 %2, label %select.done, label %select.next
select.done: ; preds = %select.body, %select.next, %entry
ret void
select.next: ; preds = %entry
%3 = icmp eq i32 %1, 1
br i1 %3, label %select.body, label %select.done
select.body: ; preds = %select.next
br label %select.done
}
declare { i32, i1 } @runtime.tryChanSelect(i8*, %runtime.chanSelectState*, i32, i32, i8*, i8*)
attributes #0 = { nounwind }
attributes #1 = { argmemonly nounwind willreturn }
+12 -23
View File
@@ -1,18 +1,16 @@
; ModuleID = 'float.go' ; ModuleID = 'float.go'
source_filename = "float.go" source_filename = "float.go"
target datalayout = "e-m:e-p:32:32-i64:64-n32:64-S128" target datalayout = "e-m:e-p:32:32-p270:32:32-p271:32:32-p272:64:64-f64:32:64-f80:32-n8:16:32-S128"
target triple = "wasm32-unknown-wasi" target triple = "i686--linux"
declare noalias nonnull i8* @runtime.alloc(i32, i8*, i8*, i8*) declare noalias nonnull i8* @runtime.alloc(i32, i8*, i8*)
; Function Attrs: nounwind define hidden void @main.init(i8* %context, i8* %parentHandle) unnamed_addr {
define hidden void @main.init(i8* %context, i8* %parentHandle) unnamed_addr #0 {
entry: entry:
ret void ret void
} }
; Function Attrs: nounwind define hidden i32 @main.f32tou32(float %v, i8* %context, i8* %parentHandle) unnamed_addr {
define hidden i32 @main.f32tou32(float %v, i8* %context, i8* %parentHandle) unnamed_addr #0 {
entry: entry:
%positive = fcmp oge float %v, 0.000000e+00 %positive = fcmp oge float %v, 0.000000e+00
%withinmax = fcmp ole float %v, 0x41EFFFFFC0000000 %withinmax = fcmp ole float %v, 0x41EFFFFFC0000000
@@ -23,26 +21,22 @@ entry:
ret i32 %0 ret i32 %0
} }
; Function Attrs: nounwind define hidden float @main.maxu32f(i8* %context, i8* %parentHandle) unnamed_addr {
define hidden float @main.maxu32f(i8* %context, i8* %parentHandle) unnamed_addr #0 {
entry: entry:
ret float 0x41F0000000000000 ret float 0x41F0000000000000
} }
; Function Attrs: nounwind define hidden i32 @main.maxu32tof32(i8* %context, i8* %parentHandle) unnamed_addr {
define hidden i32 @main.maxu32tof32(i8* %context, i8* %parentHandle) unnamed_addr #0 {
entry: entry:
ret i32 -1 ret i32 -1
} }
; Function Attrs: nounwind define hidden { i32, i32, i32, i32 } @main.inftoi32(i8* %context, i8* %parentHandle) unnamed_addr {
define hidden { i32, i32, i32, i32 } @main.inftoi32(i8* %context, i8* %parentHandle) unnamed_addr #0 {
entry: entry:
ret { i32, i32, i32, i32 } { i32 -1, i32 0, i32 2147483647, i32 -2147483648 } ret { i32, i32, i32, i32 } { i32 -1, i32 0, i32 2147483647, i32 -2147483648 }
} }
; Function Attrs: nounwind define hidden i32 @main.u32tof32tou32(i32 %v, i8* %context, i8* %parentHandle) unnamed_addr {
define hidden i32 @main.u32tof32tou32(i32 %v, i8* %context, i8* %parentHandle) unnamed_addr #0 {
entry: entry:
%0 = uitofp i32 %v to float %0 = uitofp i32 %v to float
%withinmax = fcmp ole float %0, 0x41EFFFFFC0000000 %withinmax = fcmp ole float %0, 0x41EFFFFFC0000000
@@ -51,8 +45,7 @@ entry:
ret i32 %1 ret i32 %1
} }
; Function Attrs: nounwind define hidden float @main.f32tou32tof32(float %v, i8* %context, i8* %parentHandle) unnamed_addr {
define hidden float @main.f32tou32tof32(float %v, i8* %context, i8* %parentHandle) unnamed_addr #0 {
entry: entry:
%positive = fcmp oge float %v, 0.000000e+00 %positive = fcmp oge float %v, 0.000000e+00
%withinmax = fcmp ole float %v, 0x41EFFFFFC0000000 %withinmax = fcmp ole float %v, 0x41EFFFFFC0000000
@@ -64,8 +57,7 @@ entry:
ret float %1 ret float %1
} }
; Function Attrs: nounwind define hidden i8 @main.f32tou8(float %v, i8* %context, i8* %parentHandle) unnamed_addr {
define hidden i8 @main.f32tou8(float %v, i8* %context, i8* %parentHandle) unnamed_addr #0 {
entry: entry:
%positive = fcmp oge float %v, 0.000000e+00 %positive = fcmp oge float %v, 0.000000e+00
%withinmax = fcmp ole float %v, 2.550000e+02 %withinmax = fcmp ole float %v, 2.550000e+02
@@ -76,8 +68,7 @@ entry:
ret i8 %0 ret i8 %0
} }
; Function Attrs: nounwind define hidden i8 @main.f32toi8(float %v, i8* %context, i8* %parentHandle) unnamed_addr {
define hidden i8 @main.f32toi8(float %v, i8* %context, i8* %parentHandle) unnamed_addr #0 {
entry: entry:
%abovemin = fcmp oge float %v, -1.280000e+02 %abovemin = fcmp oge float %v, -1.280000e+02
%belowmax = fcmp ole float %v, 1.270000e+02 %belowmax = fcmp ole float %v, 1.270000e+02
@@ -89,5 +80,3 @@ entry:
%0 = select i1 %inbounds, i8 %normal, i8 %remapped %0 = select i1 %inbounds, i8 %normal, i8 %remapped
ret i8 %0 ret i8 %0
} }
attributes #0 = { nounwind }
-12
View File
@@ -1,12 +0,0 @@
package main
func foo(callback func(int)) {
callback(3)
}
func bar() {
foo(someFunc)
}
func someFunc(int) {
}
-53
View File
@@ -1,53 +0,0 @@
; ModuleID = 'func.go'
source_filename = "func.go"
target datalayout = "e-m:e-p:32:32-i64:64-n32:64-S128"
target triple = "wasm32-unknown-wasi"
%runtime.funcValueWithSignature = type { i32, i8* }
@"reflect/types.funcid:func:{basic:int}{}" = external constant i8
@"main.someFunc$withSignature" = linkonce_odr constant %runtime.funcValueWithSignature { i32 ptrtoint (void (i32, i8*, i8*)* @main.someFunc to i32), i8* @"reflect/types.funcid:func:{basic:int}{}" }
declare noalias nonnull i8* @runtime.alloc(i32, i8*, i8*, i8*)
; Function Attrs: nounwind
define hidden void @main.init(i8* %context, i8* %parentHandle) unnamed_addr #0 {
entry:
ret void
}
; Function Attrs: nounwind
define hidden void @main.foo(i8* %callback.context, i32 %callback.funcptr, i8* %context, i8* %parentHandle) unnamed_addr #0 {
entry:
%0 = call i32 @runtime.getFuncPtr(i8* %callback.context, i32 %callback.funcptr, i8* nonnull @"reflect/types.funcid:func:{basic:int}{}", i8* undef, i8* null) #0
%1 = icmp eq i32 %0, 0
br i1 %1, label %fpcall.throw, label %fpcall.next
fpcall.throw: ; preds = %entry
call void @runtime.nilPanic(i8* undef, i8* null) #0
unreachable
fpcall.next: ; preds = %entry
%2 = inttoptr i32 %0 to void (i32, i8*, i8*)*
call void %2(i32 3, i8* %callback.context, i8* undef) #0
ret void
}
declare i32 @runtime.getFuncPtr(i8*, i32, i8* dereferenceable_or_null(1), i8*, i8*)
declare void @runtime.nilPanic(i8*, i8*)
; Function Attrs: nounwind
define hidden void @main.bar(i8* %context, i8* %parentHandle) unnamed_addr #0 {
entry:
call void @main.foo(i8* undef, i32 ptrtoint (%runtime.funcValueWithSignature* @"main.someFunc$withSignature" to i32), i8* undef, i8* undef)
ret void
}
; Function Attrs: nounwind
define hidden void @main.someFunc(i32 %arg0, i8* %context, i8* %parentHandle) unnamed_addr #0 {
entry:
ret void
}
attributes #0 = { nounwind }
-72
View File
@@ -1,72 +0,0 @@
package main
var (
scalar1 *byte
scalar2 *int32
scalar3 *int64
scalar4 *float32
array1 *[3]byte
array2 *[71]byte
array3 *[3]*byte
struct1 *struct{}
struct2 *struct {
x int
y int
}
struct3 *struct {
x *byte
y [60]uintptr
z *byte
}
slice1 []byte
slice2 []*int
slice3 [][]byte
)
func newScalar() {
scalar1 = new(byte)
scalar2 = new(int32)
scalar3 = new(int64)
scalar4 = new(float32)
}
func newArray() {
array1 = new([3]byte)
array2 = new([71]byte)
array3 = new([3]*byte)
}
func newStruct() {
struct1 = new(struct{})
struct2 = new(struct {
x int
y int
})
struct3 = new(struct {
x *byte
y [60]uintptr
z *byte
})
}
func newFuncValue() *func() {
// On some platforms that use runtime.funcValue ("switch" style) function
// values, a func value is allocated as having two pointer words while the
// struct looks like {unsafe.Pointer; uintptr}. This is so that the interp
// package won't get confused, see getPointerBitmap in compiler/llvm.go for
// details.
return new(func())
}
func makeSlice() {
slice1 = make([]byte, 5)
slice2 = make([]*int, 5)
slice3 = make([][]byte, 5)
}
func makeInterface(v complex128) interface{} {
return v // always stored in an allocation
}
-113
View File
@@ -1,113 +0,0 @@
; ModuleID = 'gc.go'
source_filename = "gc.go"
target datalayout = "e-m:e-p:32:32-i64:64-n32:64-S128"
target triple = "wasm32-unknown-wasi"
%runtime.typecodeID = type { %runtime.typecodeID*, i32, %runtime.interfaceMethodInfo*, %runtime.typecodeID*, i32 }
%runtime.interfaceMethodInfo = type { i8*, i32 }
%runtime.funcValue = type { i8*, i32 }
%runtime._interface = type { i32, i8* }
@main.scalar1 = hidden global i8* null, align 4
@main.scalar2 = hidden global i32* null, align 4
@main.scalar3 = hidden global i64* null, align 4
@main.scalar4 = hidden global float* null, align 4
@main.array1 = hidden global [3 x i8]* null, align 4
@main.array2 = hidden global [71 x i8]* null, align 4
@main.array3 = hidden global [3 x i8*]* null, align 4
@main.struct1 = hidden global {}* null, align 4
@main.struct2 = hidden global { i32, i32 }* null, align 4
@main.struct3 = hidden global { i8*, [60 x i32], i8* }* null, align 4
@main.slice1 = hidden global { i8*, i32, i32 } zeroinitializer, align 8
@main.slice2 = hidden global { i32**, i32, i32 } zeroinitializer, align 8
@main.slice3 = hidden global { { i8*, i32, i32 }*, i32, i32 } zeroinitializer, align 8
@"runtime/gc.layout:62-2000000000000001" = linkonce_odr unnamed_addr constant { i32, [8 x i8] } { i32 62, [8 x i8] c" \00\00\00\00\00\00\01" }
@"reflect/types.type:basic:complex128" = linkonce_odr constant %runtime.typecodeID { %runtime.typecodeID* null, i32 0, %runtime.interfaceMethodInfo* null, %runtime.typecodeID* @"reflect/types.type:pointer:basic:complex128", i32 0 }
@"reflect/types.type:pointer:basic:complex128" = linkonce_odr constant %runtime.typecodeID { %runtime.typecodeID* @"reflect/types.type:basic:complex128", i32 0, %runtime.interfaceMethodInfo* null, %runtime.typecodeID* null, i32 0 }
declare noalias nonnull i8* @runtime.alloc(i32, i8*, i8*, i8*)
; Function Attrs: nounwind
define hidden void @main.init(i8* %context, i8* %parentHandle) unnamed_addr #0 {
entry:
ret void
}
; Function Attrs: nounwind
define hidden void @main.newScalar(i8* %context, i8* %parentHandle) unnamed_addr #0 {
entry:
%new = call i8* @runtime.alloc(i32 1, i8* nonnull inttoptr (i32 3 to i8*), i8* undef, i8* null) #0
store i8* %new, i8** @main.scalar1, align 4
%new1 = call i8* @runtime.alloc(i32 4, i8* nonnull inttoptr (i32 3 to i8*), i8* undef, i8* null) #0
store i8* %new1, i8** bitcast (i32** @main.scalar2 to i8**), align 4
%new2 = call i8* @runtime.alloc(i32 8, i8* nonnull inttoptr (i32 3 to i8*), i8* undef, i8* null) #0
store i8* %new2, i8** bitcast (i64** @main.scalar3 to i8**), align 4
%new3 = call i8* @runtime.alloc(i32 4, i8* nonnull inttoptr (i32 3 to i8*), i8* undef, i8* null) #0
store i8* %new3, i8** bitcast (float** @main.scalar4 to i8**), align 4
ret void
}
; Function Attrs: nounwind
define hidden void @main.newArray(i8* %context, i8* %parentHandle) unnamed_addr #0 {
entry:
%new = call i8* @runtime.alloc(i32 3, i8* nonnull inttoptr (i32 3 to i8*), i8* undef, i8* null) #0
store i8* %new, i8** bitcast ([3 x i8]** @main.array1 to i8**), align 4
%new1 = call i8* @runtime.alloc(i32 71, i8* nonnull inttoptr (i32 3 to i8*), i8* undef, i8* null) #0
store i8* %new1, i8** bitcast ([71 x i8]** @main.array2 to i8**), align 4
%new2 = call i8* @runtime.alloc(i32 12, i8* nonnull inttoptr (i32 67 to i8*), i8* undef, i8* null) #0
store i8* %new2, i8** bitcast ([3 x i8*]** @main.array3 to i8**), align 4
ret void
}
; Function Attrs: nounwind
define hidden void @main.newStruct(i8* %context, i8* %parentHandle) unnamed_addr #0 {
entry:
%new = call i8* @runtime.alloc(i32 0, i8* nonnull inttoptr (i32 3 to i8*), i8* undef, i8* null) #0
store i8* %new, i8** bitcast ({}** @main.struct1 to i8**), align 4
%new1 = call i8* @runtime.alloc(i32 8, i8* nonnull inttoptr (i32 3 to i8*), i8* undef, i8* null) #0
store i8* %new1, i8** bitcast ({ i32, i32 }** @main.struct2 to i8**), align 4
%new2 = call i8* @runtime.alloc(i32 248, i8* bitcast ({ i32, [8 x i8] }* @"runtime/gc.layout:62-2000000000000001" to i8*), i8* undef, i8* null) #0
store i8* %new2, i8** bitcast ({ i8*, [60 x i32], i8* }** @main.struct3 to i8**), align 4
ret void
}
; Function Attrs: nounwind
define hidden %runtime.funcValue* @main.newFuncValue(i8* %context, i8* %parentHandle) unnamed_addr #0 {
entry:
%new = call i8* @runtime.alloc(i32 8, i8* nonnull inttoptr (i32 197 to i8*), i8* undef, i8* null) #0
%0 = bitcast i8* %new to %runtime.funcValue*
ret %runtime.funcValue* %0
}
; Function Attrs: nounwind
define hidden void @main.makeSlice(i8* %context, i8* %parentHandle) unnamed_addr #0 {
entry:
%makeslice = call i8* @runtime.alloc(i32 5, i8* nonnull inttoptr (i32 3 to i8*), i8* undef, i8* null) #0
store i8* %makeslice, i8** getelementptr inbounds ({ i8*, i32, i32 }, { i8*, i32, i32 }* @main.slice1, i32 0, i32 0), align 8
store i32 5, i32* getelementptr inbounds ({ i8*, i32, i32 }, { i8*, i32, i32 }* @main.slice1, i32 0, i32 1), align 4
store i32 5, i32* getelementptr inbounds ({ i8*, i32, i32 }, { i8*, i32, i32 }* @main.slice1, i32 0, i32 2), align 8
%makeslice1 = call i8* @runtime.alloc(i32 20, i8* nonnull inttoptr (i32 67 to i8*), i8* undef, i8* null) #0
store i8* %makeslice1, i8** bitcast ({ i32**, i32, i32 }* @main.slice2 to i8**), align 8
store i32 5, i32* getelementptr inbounds ({ i32**, i32, i32 }, { i32**, i32, i32 }* @main.slice2, i32 0, i32 1), align 4
store i32 5, i32* getelementptr inbounds ({ i32**, i32, i32 }, { i32**, i32, i32 }* @main.slice2, i32 0, i32 2), align 8
%makeslice3 = call i8* @runtime.alloc(i32 60, i8* nonnull inttoptr (i32 71 to i8*), i8* undef, i8* null) #0
store i8* %makeslice3, i8** bitcast ({ { i8*, i32, i32 }*, i32, i32 }* @main.slice3 to i8**), align 8
store i32 5, i32* getelementptr inbounds ({ { i8*, i32, i32 }*, i32, i32 }, { { i8*, i32, i32 }*, i32, i32 }* @main.slice3, i32 0, i32 1), align 4
store i32 5, i32* getelementptr inbounds ({ { i8*, i32, i32 }*, i32, i32 }, { { i8*, i32, i32 }*, i32, i32 }* @main.slice3, i32 0, i32 2), align 8
ret void
}
; Function Attrs: nounwind
define hidden %runtime._interface @main.makeInterface(double %v.r, double %v.i, i8* %context, i8* %parentHandle) unnamed_addr #0 {
entry:
%0 = call i8* @runtime.alloc(i32 16, i8* null, i8* undef, i8* null) #0
%.repack = bitcast i8* %0 to double*
store double %v.r, double* %.repack, align 8
%.repack1 = getelementptr inbounds i8, i8* %0, i32 8
%1 = bitcast i8* %.repack1 to double*
store double %v.i, double* %1, align 8
%2 = insertvalue %runtime._interface { i32 ptrtoint (%runtime.typecodeID* @"reflect/types.type:basic:complex128" to i32), i8* undef }, i8* %0, 1
ret %runtime._interface %2
}
attributes #0 = { nounwind }
-41
View File
@@ -1,41 +0,0 @@
package main
// Test changes to the language introduced in Go 1.17.
// For details, see: https://tip.golang.org/doc/go1.17#language
// These tests should be merged into the regular slice tests once Go 1.17 is the
// minimun Go version for TinyGo.
import "unsafe"
func Add32(p unsafe.Pointer, len int) unsafe.Pointer {
return unsafe.Add(p, len)
}
func Add64(p unsafe.Pointer, len int64) unsafe.Pointer {
return unsafe.Add(p, len)
}
func SliceToArray(s []int) *[4]int {
return (*[4]int)(s)
}
func SliceToArrayConst() *[4]int {
s := make([]int, 6)
return (*[4]int)(s)
}
func SliceInt(ptr *int, len int) []int {
return unsafe.Slice(ptr, len)
}
func SliceUint16(ptr *byte, len uint16) []byte {
return unsafe.Slice(ptr, len)
}
func SliceUint64(ptr *int, len uint64) []int {
return unsafe.Slice(ptr, len)
}
func SliceInt64(ptr *int, len int64) []int {
return unsafe.Slice(ptr, len)
}
-147
View File
@@ -1,147 +0,0 @@
; ModuleID = 'go1.17.go'
source_filename = "go1.17.go"
target datalayout = "e-m:e-p:32:32-i64:64-n32:64-S128"
target triple = "wasm32-unknown-wasi"
declare noalias nonnull i8* @runtime.alloc(i32, i8*, i8*, i8*)
; Function Attrs: nounwind
define hidden void @main.init(i8* %context, i8* %parentHandle) unnamed_addr #0 {
entry:
ret void
}
; Function Attrs: nounwind
define hidden i8* @main.Add32(i8* %p, i32 %len, i8* %context, i8* %parentHandle) unnamed_addr #0 {
entry:
%0 = getelementptr i8, i8* %p, i32 %len
ret i8* %0
}
; Function Attrs: nounwind
define hidden i8* @main.Add64(i8* %p, i64 %len, i8* %context, i8* %parentHandle) unnamed_addr #0 {
entry:
%0 = trunc i64 %len to i32
%1 = getelementptr i8, i8* %p, i32 %0
ret i8* %1
}
; Function Attrs: nounwind
define hidden [4 x i32]* @main.SliceToArray(i32* %s.data, i32 %s.len, i32 %s.cap, i8* %context, i8* %parentHandle) unnamed_addr #0 {
entry:
%0 = icmp ult i32 %s.len, 4
br i1 %0, label %slicetoarray.throw, label %slicetoarray.next
slicetoarray.throw: ; preds = %entry
call void @runtime.sliceToArrayPointerPanic(i8* undef, i8* null) #0
unreachable
slicetoarray.next: ; preds = %entry
%1 = bitcast i32* %s.data to [4 x i32]*
ret [4 x i32]* %1
}
declare void @runtime.sliceToArrayPointerPanic(i8*, i8*)
; Function Attrs: nounwind
define hidden [4 x i32]* @main.SliceToArrayConst(i8* %context, i8* %parentHandle) unnamed_addr #0 {
entry:
%makeslice = call i8* @runtime.alloc(i32 24, i8* nonnull inttoptr (i32 3 to i8*), i8* undef, i8* null) #0
br i1 false, label %slicetoarray.throw, label %slicetoarray.next
slicetoarray.throw: ; preds = %entry
unreachable
slicetoarray.next: ; preds = %entry
%0 = bitcast i8* %makeslice to [4 x i32]*
ret [4 x i32]* %0
}
; Function Attrs: nounwind
define hidden { i32*, i32, i32 } @main.SliceInt(i32* dereferenceable_or_null(4) %ptr, i32 %len, i8* %context, i8* %parentHandle) unnamed_addr #0 {
entry:
%0 = icmp ugt i32 %len, 1073741823
%1 = icmp eq i32* %ptr, null
%2 = icmp ne i32 %len, 0
%3 = and i1 %1, %2
%4 = or i1 %3, %0
br i1 %4, label %unsafe.Slice.throw, label %unsafe.Slice.next
unsafe.Slice.throw: ; preds = %entry
call void @runtime.unsafeSlicePanic(i8* undef, i8* null) #0
unreachable
unsafe.Slice.next: ; preds = %entry
%5 = insertvalue { i32*, i32, i32 } undef, i32* %ptr, 0
%6 = insertvalue { i32*, i32, i32 } %5, i32 %len, 1
%7 = insertvalue { i32*, i32, i32 } %6, i32 %len, 2
ret { i32*, i32, i32 } %7
}
declare void @runtime.unsafeSlicePanic(i8*, i8*)
; Function Attrs: nounwind
define hidden { i8*, i32, i32 } @main.SliceUint16(i8* dereferenceable_or_null(1) %ptr, i16 %len, i8* %context, i8* %parentHandle) unnamed_addr #0 {
entry:
%0 = icmp eq i8* %ptr, null
%1 = icmp ne i16 %len, 0
%2 = and i1 %0, %1
br i1 %2, label %unsafe.Slice.throw, label %unsafe.Slice.next
unsafe.Slice.throw: ; preds = %entry
call void @runtime.unsafeSlicePanic(i8* undef, i8* null) #0
unreachable
unsafe.Slice.next: ; preds = %entry
%3 = zext i16 %len to i32
%4 = insertvalue { i8*, i32, i32 } undef, i8* %ptr, 0
%5 = insertvalue { i8*, i32, i32 } %4, i32 %3, 1
%6 = insertvalue { i8*, i32, i32 } %5, i32 %3, 2
ret { i8*, i32, i32 } %6
}
; Function Attrs: nounwind
define hidden { i32*, i32, i32 } @main.SliceUint64(i32* dereferenceable_or_null(4) %ptr, i64 %len, i8* %context, i8* %parentHandle) unnamed_addr #0 {
entry:
%0 = icmp ugt i64 %len, 1073741823
%1 = icmp eq i32* %ptr, null
%2 = icmp ne i64 %len, 0
%3 = and i1 %1, %2
%4 = or i1 %3, %0
br i1 %4, label %unsafe.Slice.throw, label %unsafe.Slice.next
unsafe.Slice.throw: ; preds = %entry
call void @runtime.unsafeSlicePanic(i8* undef, i8* null) #0
unreachable
unsafe.Slice.next: ; preds = %entry
%5 = trunc i64 %len to i32
%6 = insertvalue { i32*, i32, i32 } undef, i32* %ptr, 0
%7 = insertvalue { i32*, i32, i32 } %6, i32 %5, 1
%8 = insertvalue { i32*, i32, i32 } %7, i32 %5, 2
ret { i32*, i32, i32 } %8
}
; Function Attrs: nounwind
define hidden { i32*, i32, i32 } @main.SliceInt64(i32* dereferenceable_or_null(4) %ptr, i64 %len, i8* %context, i8* %parentHandle) unnamed_addr #0 {
entry:
%0 = icmp ugt i64 %len, 1073741823
%1 = icmp eq i32* %ptr, null
%2 = icmp ne i64 %len, 0
%3 = and i1 %1, %2
%4 = or i1 %3, %0
br i1 %4, label %unsafe.Slice.throw, label %unsafe.Slice.next
unsafe.Slice.throw: ; preds = %entry
call void @runtime.unsafeSlicePanic(i8* undef, i8* null) #0
unreachable
unsafe.Slice.next: ; preds = %entry
%5 = trunc i64 %len to i32
%6 = insertvalue { i32*, i32, i32 } undef, i32* %ptr, 0
%7 = insertvalue { i32*, i32, i32 } %6, i32 %5, 1
%8 = insertvalue { i32*, i32, i32 } %7, i32 %5, 2
ret { i32*, i32, i32 } %8
}
attributes #0 = { nounwind }
-209
View File
@@ -1,209 +0,0 @@
; ModuleID = 'goroutine.go'
source_filename = "goroutine.go"
target datalayout = "e-m:e-p:32:32-Fi8-i64:64-v128:64:128-a:0:32-n32-S64"
target triple = "thumbv7m-unknown-unknown-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 { %"internal/task.Task"*, i8*, i64, %"internal/task.state" }
%"internal/task.state" = type { i32, i32* }
%runtime.chanSelectState = type { %runtime.channel*, i8* }
@"main$string" = internal unnamed_addr constant [4 x i8] c"test", align 1
declare noalias nonnull i8* @runtime.alloc(i32, i8*, i8*, i8*)
; Function Attrs: nounwind
define hidden void @main.init(i8* %context, i8* %parentHandle) unnamed_addr #0 {
entry:
ret void
}
; Function Attrs: nounwind
define hidden void @main.regularFunctionGoroutine(i8* %context, i8* %parentHandle) unnamed_addr #0 {
entry:
%stacksize = call i32 @"internal/task.getGoroutineStackSize"(i32 ptrtoint (void (i8*)* @"main.regularFunction$gowrapper" to i32), i8* undef, i8* undef) #0
call void @"internal/task.start"(i32 ptrtoint (void (i8*)* @"main.regularFunction$gowrapper" to i32), i8* nonnull inttoptr (i32 5 to i8*), i32 %stacksize, i8* undef, i8* null) #0
ret void
}
declare void @main.regularFunction(i32, i8*, i8*)
; Function Attrs: nounwind
define linkonce_odr void @"main.regularFunction$gowrapper"(i8* %0) unnamed_addr #1 {
entry:
%unpack.int = ptrtoint i8* %0 to i32
call void @main.regularFunction(i32 %unpack.int, i8* undef, i8* undef) #0
ret void
}
declare i32 @"internal/task.getGoroutineStackSize"(i32, i8*, i8*)
declare void @"internal/task.start"(i32, i8*, i32, i8*, i8*)
; Function Attrs: nounwind
define hidden void @main.inlineFunctionGoroutine(i8* %context, i8* %parentHandle) unnamed_addr #0 {
entry:
%stacksize = call i32 @"internal/task.getGoroutineStackSize"(i32 ptrtoint (void (i8*)* @"main.inlineFunctionGoroutine$1$gowrapper" to i32), i8* undef, i8* undef) #0
call void @"internal/task.start"(i32 ptrtoint (void (i8*)* @"main.inlineFunctionGoroutine$1$gowrapper" to i32), i8* nonnull inttoptr (i32 5 to i8*), i32 %stacksize, i8* undef, i8* null) #0
ret void
}
; Function Attrs: nounwind
define hidden void @"main.inlineFunctionGoroutine$1"(i32 %x, i8* %context, i8* %parentHandle) unnamed_addr #0 {
entry:
ret void
}
; Function Attrs: nounwind
define linkonce_odr void @"main.inlineFunctionGoroutine$1$gowrapper"(i8* %0) unnamed_addr #2 {
entry:
%unpack.int = ptrtoint i8* %0 to i32
call void @"main.inlineFunctionGoroutine$1"(i32 %unpack.int, i8* undef, i8* undef)
ret void
}
; Function Attrs: nounwind
define hidden void @main.closureFunctionGoroutine(i8* %context, i8* %parentHandle) unnamed_addr #0 {
entry:
%n = call i8* @runtime.alloc(i32 4, i8* nonnull inttoptr (i32 3 to i8*), i8* undef, i8* null) #0
%0 = bitcast i8* %n to i32*
store i32 3, i32* %0, align 4
%1 = call i8* @runtime.alloc(i32 8, i8* null, i8* undef, i8* null) #0
%2 = bitcast i8* %1 to i32*
store i32 5, i32* %2, align 4
%3 = getelementptr inbounds i8, i8* %1, i32 4
%4 = bitcast i8* %3 to i8**
store i8* %n, i8** %4, align 4
%stacksize = call i32 @"internal/task.getGoroutineStackSize"(i32 ptrtoint (void (i8*)* @"main.closureFunctionGoroutine$1$gowrapper" to i32), i8* undef, i8* undef) #0
call void @"internal/task.start"(i32 ptrtoint (void (i8*)* @"main.closureFunctionGoroutine$1$gowrapper" to i32), i8* nonnull %1, i32 %stacksize, i8* undef, i8* null) #0
%5 = load i32, i32* %0, align 4
call void @runtime.printint32(i32 %5, i8* undef, i8* null) #0
ret void
}
; Function Attrs: nounwind
define hidden void @"main.closureFunctionGoroutine$1"(i32 %x, i8* %context, i8* %parentHandle) unnamed_addr #0 {
entry:
%unpack.ptr = bitcast i8* %context to i32*
store i32 7, i32* %unpack.ptr, align 4
ret void
}
; Function Attrs: nounwind
define linkonce_odr void @"main.closureFunctionGoroutine$1$gowrapper"(i8* %0) unnamed_addr #3 {
entry:
%1 = bitcast i8* %0 to i32*
%2 = load i32, i32* %1, align 4
%3 = getelementptr inbounds i8, i8* %0, i32 4
%4 = bitcast i8* %3 to i8**
%5 = load i8*, i8** %4, align 4
call void @"main.closureFunctionGoroutine$1"(i32 %2, i8* %5, i8* undef)
ret void
}
declare void @runtime.printint32(i32, i8*, i8*)
; Function Attrs: nounwind
define hidden void @main.funcGoroutine(i8* %fn.context, void ()* %fn.funcptr, i8* %context, i8* %parentHandle) unnamed_addr #0 {
entry:
%0 = call i8* @runtime.alloc(i32 12, i8* null, i8* undef, i8* null) #0
%1 = bitcast i8* %0 to i32*
store i32 5, i32* %1, align 4
%2 = getelementptr inbounds i8, i8* %0, i32 4
%3 = bitcast i8* %2 to i8**
store i8* %fn.context, i8** %3, align 4
%4 = getelementptr inbounds i8, i8* %0, i32 8
%5 = bitcast i8* %4 to void ()**
store void ()* %fn.funcptr, void ()** %5, align 4
%stacksize = call i32 @"internal/task.getGoroutineStackSize"(i32 ptrtoint (void (i8*)* @main.funcGoroutine.gowrapper to i32), i8* undef, i8* undef) #0
call void @"internal/task.start"(i32 ptrtoint (void (i8*)* @main.funcGoroutine.gowrapper to i32), i8* nonnull %0, i32 %stacksize, i8* undef, i8* null) #0
ret void
}
; Function Attrs: nounwind
define linkonce_odr void @main.funcGoroutine.gowrapper(i8* %0) unnamed_addr #4 {
entry:
%1 = bitcast i8* %0 to i32*
%2 = load i32, i32* %1, align 4
%3 = getelementptr inbounds i8, i8* %0, i32 4
%4 = bitcast i8* %3 to i8**
%5 = load i8*, i8** %4, align 4
%6 = getelementptr inbounds i8, i8* %0, i32 8
%7 = bitcast i8* %6 to void (i32, i8*, i8*)**
%8 = load void (i32, i8*, i8*)*, void (i32, i8*, i8*)** %7, align 4
call void %8(i32 %2, i8* %5, i8* undef) #0
ret void
}
; Function Attrs: nounwind
define hidden void @main.recoverBuiltinGoroutine(i8* %context, i8* %parentHandle) unnamed_addr #0 {
entry:
ret void
}
; Function Attrs: nounwind
define hidden void @main.copyBuiltinGoroutine(i8* %dst.data, i32 %dst.len, i32 %dst.cap, i8* %src.data, i32 %src.len, i32 %src.cap, i8* %context, i8* %parentHandle) unnamed_addr #0 {
entry:
%copy.n = call i32 @runtime.sliceCopy(i8* %dst.data, i8* %src.data, i32 %dst.len, i32 %src.len, i32 1, i8* undef, i8* null) #0
ret void
}
declare i32 @runtime.sliceCopy(i8* nocapture writeonly, i8* nocapture readonly, i32, i32, i32, i8*, i8*)
; Function Attrs: nounwind
define hidden void @main.closeBuiltinGoroutine(%runtime.channel* dereferenceable_or_null(32) %ch, i8* %context, i8* %parentHandle) unnamed_addr #0 {
entry:
call void @runtime.chanClose(%runtime.channel* %ch, i8* undef, i8* null) #0
ret void
}
declare void @runtime.chanClose(%runtime.channel* dereferenceable_or_null(32), i8*, i8*)
; Function Attrs: nounwind
define hidden void @main.startInterfaceMethod(i32 %itf.typecode, i8* %itf.value, i8* %context, i8* %parentHandle) unnamed_addr #0 {
entry:
%0 = call i8* @runtime.alloc(i32 16, i8* null, i8* undef, i8* null) #0
%1 = bitcast i8* %0 to i8**
store i8* %itf.value, i8** %1, align 4
%2 = getelementptr inbounds i8, i8* %0, i32 4
%.repack = bitcast i8* %2 to i8**
store i8* getelementptr inbounds ([4 x i8], [4 x i8]* @"main$string", i32 0, i32 0), i8** %.repack, align 4
%.repack1 = getelementptr inbounds i8, i8* %0, i32 8
%3 = bitcast i8* %.repack1 to i32*
store i32 4, i32* %3, align 4
%4 = getelementptr inbounds i8, i8* %0, i32 12
%5 = bitcast i8* %4 to i32*
store i32 %itf.typecode, i32* %5, align 4
%stacksize = call i32 @"internal/task.getGoroutineStackSize"(i32 ptrtoint (void (i8*)* @"interface:{Print:func:{basic:string}{}}.Print$invoke$gowrapper" to i32), i8* undef, i8* undef) #0
call void @"internal/task.start"(i32 ptrtoint (void (i8*)* @"interface:{Print:func:{basic:string}{}}.Print$invoke$gowrapper" to i32), i8* nonnull %0, i32 %stacksize, i8* undef, i8* null) #0
ret void
}
declare void @"interface:{Print:func:{basic:string}{}}.Print$invoke"(i8*, i8*, i32, i32, i8*, i8*) #5
; Function Attrs: nounwind
define linkonce_odr void @"interface:{Print:func:{basic:string}{}}.Print$invoke$gowrapper"(i8* %0) unnamed_addr #6 {
entry:
%1 = bitcast i8* %0 to i8**
%2 = load i8*, i8** %1, align 4
%3 = getelementptr inbounds i8, i8* %0, i32 4
%4 = bitcast i8* %3 to i8**
%5 = load i8*, i8** %4, align 4
%6 = getelementptr inbounds i8, i8* %0, i32 8
%7 = bitcast i8* %6 to i32*
%8 = load i32, i32* %7, align 4
%9 = getelementptr inbounds i8, i8* %0, i32 12
%10 = bitcast i8* %9 to i32*
%11 = load i32, i32* %10, align 4
call void @"interface:{Print:func:{basic:string}{}}.Print$invoke"(i8* %2, i8* %5, i32 %8, i32 %11, i8* undef, i8* undef) #0
ret void
}
attributes #0 = { nounwind }
attributes #1 = { nounwind "tinygo-gowrapper"="main.regularFunction" }
attributes #2 = { nounwind "tinygo-gowrapper"="main.inlineFunctionGoroutine$1" }
attributes #3 = { nounwind "tinygo-gowrapper"="main.closureFunctionGoroutine$1" }
attributes #4 = { nounwind "tinygo-gowrapper" }
attributes #5 = { "tinygo-invoke"="reflect/methods.Print(string)" "tinygo-methods"="reflect/methods.Print(string)" }
attributes #6 = { nounwind "tinygo-gowrapper"="interface:{Print:func:{basic:string}{}}.Print$invoke" }
-141
View File
@@ -1,141 +0,0 @@
; ModuleID = 'goroutine.go'
source_filename = "goroutine.go"
target datalayout = "e-m:e-p:32:32-i64:64-n32:64-S128"
target triple = "wasm32-unknown-wasi"
%runtime.funcValueWithSignature = type { i32, i8* }
%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 { %"internal/task.Task"*, i8*, i64, %"internal/task.state" }
%"internal/task.state" = type { i8* }
%runtime.chanSelectState = type { %runtime.channel*, i8* }
@"main$pack" = internal unnamed_addr constant { i32, i8* } { i32 5, i8* undef }
@"main$pack.1" = internal unnamed_addr constant { i32, i8* } { i32 5, i8* undef }
@"reflect/types.funcid:func:{basic:int}{}" = external constant i8
@"main.closureFunctionGoroutine$1$withSignature" = linkonce_odr constant %runtime.funcValueWithSignature { i32 ptrtoint (void (i32, i8*, i8*)* @"main.closureFunctionGoroutine$1" to i32), i8* @"reflect/types.funcid:func:{basic:int}{}" }
@"main$string" = internal unnamed_addr constant [4 x i8] c"test", align 1
declare noalias nonnull i8* @runtime.alloc(i32, i8*, i8*, i8*)
; Function Attrs: nounwind
define hidden void @main.init(i8* %context, i8* %parentHandle) unnamed_addr #0 {
entry:
ret void
}
; Function Attrs: nounwind
define hidden void @main.regularFunctionGoroutine(i8* %context, i8* %parentHandle) unnamed_addr #0 {
entry:
call void @"internal/task.start"(i32 ptrtoint (void (i32, i8*, i8*)* @main.regularFunction to i32), i8* bitcast ({ i32, i8* }* @"main$pack" to i8*), i32 undef, i8* undef, i8* null) #0
ret void
}
declare void @main.regularFunction(i32, i8*, i8*)
declare void @"internal/task.start"(i32, i8*, i32, i8*, i8*)
; Function Attrs: nounwind
define hidden void @main.inlineFunctionGoroutine(i8* %context, i8* %parentHandle) unnamed_addr #0 {
entry:
call void @"internal/task.start"(i32 ptrtoint (void (i32, i8*, i8*)* @"main.inlineFunctionGoroutine$1" to i32), i8* bitcast ({ i32, i8* }* @"main$pack.1" to i8*), i32 undef, i8* undef, i8* null) #0
ret void
}
; Function Attrs: nounwind
define hidden void @"main.inlineFunctionGoroutine$1"(i32 %x, i8* %context, i8* %parentHandle) unnamed_addr #0 {
entry:
ret void
}
; Function Attrs: nounwind
define hidden void @main.closureFunctionGoroutine(i8* %context, i8* %parentHandle) unnamed_addr #0 {
entry:
%n = call i8* @runtime.alloc(i32 4, i8* nonnull inttoptr (i32 3 to i8*), i8* undef, i8* null) #0
%0 = bitcast i8* %n to i32*
store i32 3, i32* %0, align 4
%1 = call i8* @runtime.alloc(i32 8, i8* null, i8* undef, i8* null) #0
%2 = bitcast i8* %1 to i32*
store i32 5, i32* %2, align 4
%3 = getelementptr inbounds i8, i8* %1, i32 4
%4 = bitcast i8* %3 to i8**
store i8* %n, i8** %4, align 4
call void @"internal/task.start"(i32 ptrtoint (void (i32, i8*, i8*)* @"main.closureFunctionGoroutine$1" to i32), i8* nonnull %1, i32 undef, i8* undef, i8* null) #0
%5 = load i32, i32* %0, align 4
call void @runtime.printint32(i32 %5, i8* undef, i8* null) #0
ret void
}
; Function Attrs: nounwind
define hidden void @"main.closureFunctionGoroutine$1"(i32 %x, i8* %context, i8* %parentHandle) unnamed_addr #0 {
entry:
%unpack.ptr = bitcast i8* %context to i32*
store i32 7, i32* %unpack.ptr, align 4
ret void
}
declare void @runtime.printint32(i32, i8*, i8*)
; Function Attrs: nounwind
define hidden void @main.funcGoroutine(i8* %fn.context, i32 %fn.funcptr, i8* %context, i8* %parentHandle) unnamed_addr #0 {
entry:
%0 = call i32 @runtime.getFuncPtr(i8* %fn.context, i32 %fn.funcptr, i8* nonnull @"reflect/types.funcid:func:{basic:int}{}", i8* undef, i8* null) #0
%1 = call i8* @runtime.alloc(i32 8, i8* null, i8* undef, i8* null) #0
%2 = bitcast i8* %1 to i32*
store i32 5, i32* %2, align 4
%3 = getelementptr inbounds i8, i8* %1, i32 4
%4 = bitcast i8* %3 to i8**
store i8* %fn.context, i8** %4, align 4
call void @"internal/task.start"(i32 %0, i8* nonnull %1, i32 undef, i8* undef, i8* null) #0
ret void
}
declare i32 @runtime.getFuncPtr(i8*, i32, i8* dereferenceable_or_null(1), i8*, i8*)
; Function Attrs: nounwind
define hidden void @main.recoverBuiltinGoroutine(i8* %context, i8* %parentHandle) unnamed_addr #0 {
entry:
ret void
}
; Function Attrs: nounwind
define hidden void @main.copyBuiltinGoroutine(i8* %dst.data, i32 %dst.len, i32 %dst.cap, i8* %src.data, i32 %src.len, i32 %src.cap, i8* %context, i8* %parentHandle) unnamed_addr #0 {
entry:
%copy.n = call i32 @runtime.sliceCopy(i8* %dst.data, i8* %src.data, i32 %dst.len, i32 %src.len, i32 1, i8* undef, i8* null) #0
ret void
}
declare i32 @runtime.sliceCopy(i8* nocapture writeonly, i8* nocapture readonly, i32, i32, i32, i8*, i8*)
; Function Attrs: nounwind
define hidden void @main.closeBuiltinGoroutine(%runtime.channel* dereferenceable_or_null(32) %ch, i8* %context, i8* %parentHandle) unnamed_addr #0 {
entry:
call void @runtime.chanClose(%runtime.channel* %ch, i8* undef, i8* null) #0
ret void
}
declare void @runtime.chanClose(%runtime.channel* dereferenceable_or_null(32), i8*, i8*)
; Function Attrs: nounwind
define hidden void @main.startInterfaceMethod(i32 %itf.typecode, i8* %itf.value, i8* %context, i8* %parentHandle) unnamed_addr #0 {
entry:
%0 = call i8* @runtime.alloc(i32 16, i8* null, i8* undef, i8* null) #0
%1 = bitcast i8* %0 to i8**
store i8* %itf.value, i8** %1, align 4
%2 = getelementptr inbounds i8, i8* %0, i32 4
%.repack = bitcast i8* %2 to i8**
store i8* getelementptr inbounds ([4 x i8], [4 x i8]* @"main$string", i32 0, i32 0), i8** %.repack, align 4
%.repack1 = getelementptr inbounds i8, i8* %0, i32 8
%3 = bitcast i8* %.repack1 to i32*
store i32 4, i32* %3, align 4
%4 = getelementptr inbounds i8, i8* %0, i32 12
%5 = bitcast i8* %4 to i32*
store i32 %itf.typecode, i32* %5, align 4
call void @"internal/task.start"(i32 ptrtoint (void (i8*, i8*, i32, i32, i8*, i8*)* @"interface:{Print:func:{basic:string}{}}.Print$invoke" to i32), i8* nonnull %0, i32 undef, i8* undef, i8* null) #0
ret void
}
declare void @"interface:{Print:func:{basic:string}{}}.Print$invoke"(i8*, i8*, i32, i32, i8*, i8*) #1
attributes #0 = { nounwind }
attributes #1 = { "tinygo-invoke"="reflect/methods.Print(string)" "tinygo-methods"="reflect/methods.Print(string)" }
-51
View File
@@ -1,51 +0,0 @@
package main
func regularFunctionGoroutine() {
go regularFunction(5)
}
func inlineFunctionGoroutine() {
go func(x int) {
}(5)
}
func closureFunctionGoroutine() {
n := 3
go func(x int) {
n = 7
}(5)
print(n) // note: this is racy (but good enough for this test)
}
func funcGoroutine(fn func(x int)) {
go fn(5)
}
func recoverBuiltinGoroutine() {
// This is a no-op.
go recover()
}
func copyBuiltinGoroutine(dst, src []byte) {
// This is not run in a goroutine. While this copy operation can indeed take
// some time (if there is a lot of data to copy), there is no race-free way
// to make use of the result so it's unlikely applications will make use of
// it. And doing it this way should be just within the Go specification.
go copy(dst, src)
}
func closeBuiltinGoroutine(ch chan int) {
// This builtin is executed directly, not in a goroutine.
// The observed behavior is the same.
go close(ch)
}
func regularFunction(x int)
type simpleInterface interface {
Print(string)
}
func startInterfaceMethod(itf simpleInterface) {
go itf.Print("test")
}
-9
View File
@@ -49,15 +49,6 @@ func isStringer(itf interface{}) bool {
return ok return ok
} }
type fooInterface interface {
String() string
foo(int) byte
}
func callFooMethod(itf fooInterface) uint8 {
return itf.foo(3)
}
func callErrorMethod(itf error) string { func callErrorMethod(itf error) string {
return itf.Error() return itf.Error()
} }
+35 -59
View File
@@ -1,67 +1,57 @@
; ModuleID = 'interface.go' ; ModuleID = 'interface.go'
source_filename = "interface.go" source_filename = "interface.go"
target datalayout = "e-m:e-p:32:32-i64:64-n32:64-S128" target datalayout = "e-m:e-p:32:32-p270:32:32-p271:32:32-p272:64:64-f64:32:64-f80:32-n8:16:32-S128"
target triple = "wasm32-unknown-wasi" target triple = "i686--linux"
%runtime.typecodeID = type { %runtime.typecodeID*, i32, %runtime.interfaceMethodInfo*, %runtime.typecodeID*, i32 } %runtime.typecodeID = type { %runtime.typecodeID*, i32, %runtime.interfaceMethodInfo* }
%runtime.interfaceMethodInfo = type { i8*, i32 } %runtime.interfaceMethodInfo = type { i8*, i32 }
%runtime._interface = type { i32, i8* } %runtime._interface = type { i32, i8* }
%runtime._string = type { i8*, i32 } %runtime._string = type { i8*, i32 }
@"reflect/types.type:basic:int" = linkonce_odr constant %runtime.typecodeID { %runtime.typecodeID* null, i32 0, %runtime.interfaceMethodInfo* null, %runtime.typecodeID* @"reflect/types.type:pointer:basic:int", i32 0 } @"reflect/types.type:basic:int" = linkonce_odr constant %runtime.typecodeID zeroinitializer
@"reflect/types.type:pointer:basic:int" = linkonce_odr constant %runtime.typecodeID { %runtime.typecodeID* @"reflect/types.type:basic:int", i32 0, %runtime.interfaceMethodInfo* null, %runtime.typecodeID* null, i32 0 } @"reflect/types.type:pointer:basic:int" = linkonce_odr constant %runtime.typecodeID { %runtime.typecodeID* @"reflect/types.type:basic:int", i32 0, %runtime.interfaceMethodInfo* null }
@"reflect/types.type:pointer:named:error" = linkonce_odr constant %runtime.typecodeID { %runtime.typecodeID* @"reflect/types.type:named:error", i32 0, %runtime.interfaceMethodInfo* null, %runtime.typecodeID* null, i32 0 } @"reflect/types.type:pointer:named:error" = linkonce_odr constant %runtime.typecodeID { %runtime.typecodeID* @"reflect/types.type:named:error", i32 0, %runtime.interfaceMethodInfo* null }
@"reflect/types.type:named:error" = linkonce_odr constant %runtime.typecodeID { %runtime.typecodeID* @"reflect/types.type:interface:{Error:func:{}{basic:string}}", i32 0, %runtime.interfaceMethodInfo* null, %runtime.typecodeID* @"reflect/types.type:pointer:named:error", i32 ptrtoint (i1 (i32)* @"interface:{Error:func:{}{basic:string}}.$typeassert" to i32) } @"reflect/types.type:named:error" = linkonce_odr constant %runtime.typecodeID { %runtime.typecodeID* @"reflect/types.type:interface:{Error:func:{}{basic:string}}", i32 0, %runtime.interfaceMethodInfo* null }
@"reflect/types.type:interface:{Error:func:{}{basic:string}}" = linkonce_odr constant %runtime.typecodeID { %runtime.typecodeID* bitcast ([1 x i8*]* @"reflect/types.interface:interface{Error() string}$interface" to %runtime.typecodeID*), i32 0, %runtime.interfaceMethodInfo* null, %runtime.typecodeID* @"reflect/types.type:pointer:interface:{Error:func:{}{basic:string}}", i32 ptrtoint (i1 (i32)* @"interface:{Error:func:{}{basic:string}}.$typeassert" to i32) } @"reflect/types.type:interface:{Error:func:{}{basic:string}}" = linkonce_odr constant %runtime.typecodeID { %runtime.typecodeID* bitcast ([1 x i8*]* @"reflect/types.interface:interface{Error() string}$interface" to %runtime.typecodeID*), i32 0, %runtime.interfaceMethodInfo* null }
@"reflect/methods.Error() string" = linkonce_odr constant i8 0, align 1 @"func Error() string" = external constant i8
@"reflect/types.interface:interface{Error() string}$interface" = linkonce_odr constant [1 x i8*] [i8* @"reflect/methods.Error() string"] @"reflect/types.interface:interface{Error() string}$interface" = linkonce_odr constant [1 x i8*] [i8* @"func Error() string"]
@"reflect/types.type:pointer:interface:{Error:func:{}{basic:string}}" = linkonce_odr constant %runtime.typecodeID { %runtime.typecodeID* @"reflect/types.type:interface:{Error:func:{}{basic:string}}", i32 0, %runtime.interfaceMethodInfo* null, %runtime.typecodeID* null, i32 0 } @"reflect/types.type:pointer:interface:{String:func:{}{basic:string}}" = linkonce_odr constant %runtime.typecodeID { %runtime.typecodeID* @"reflect/types.type:interface:{String:func:{}{basic:string}}", i32 0, %runtime.interfaceMethodInfo* null }
@"reflect/types.type:pointer:interface:{String:func:{}{basic:string}}" = linkonce_odr constant %runtime.typecodeID { %runtime.typecodeID* @"reflect/types.type:interface:{String:func:{}{basic:string}}", i32 0, %runtime.interfaceMethodInfo* null, %runtime.typecodeID* null, i32 0 } @"reflect/types.type:interface:{String:func:{}{basic:string}}" = linkonce_odr constant %runtime.typecodeID { %runtime.typecodeID* bitcast ([1 x i8*]* @"reflect/types.interface:interface{String() string}$interface" to %runtime.typecodeID*), i32 0, %runtime.interfaceMethodInfo* null }
@"reflect/types.type:interface:{String:func:{}{basic:string}}" = linkonce_odr constant %runtime.typecodeID { %runtime.typecodeID* bitcast ([1 x i8*]* @"reflect/types.interface:interface{String() string}$interface" to %runtime.typecodeID*), i32 0, %runtime.interfaceMethodInfo* null, %runtime.typecodeID* @"reflect/types.type:pointer:interface:{String:func:{}{basic:string}}", i32 ptrtoint (i1 (i32)* @"interface:{String:func:{}{basic:string}}.$typeassert" to i32) } @"func String() string" = external constant i8
@"reflect/methods.String() string" = linkonce_odr constant i8 0, align 1 @"reflect/types.interface:interface{String() string}$interface" = linkonce_odr constant [1 x i8*] [i8* @"func String() string"]
@"reflect/types.interface:interface{String() string}$interface" = linkonce_odr constant [1 x i8*] [i8* @"reflect/methods.String() string"] @"reflect/types.type:basic:int$id" = external constant i8
@"reflect/types.typeid:basic:int" = external constant i8 @"error$interface" = linkonce_odr constant [1 x i8*] [i8* @"func Error() string"]
declare noalias nonnull i8* @runtime.alloc(i32, i8*, i8*, i8*) declare noalias nonnull i8* @runtime.alloc(i32, i8*, i8*)
; Function Attrs: nounwind define hidden void @main.init(i8* %context, i8* %parentHandle) unnamed_addr {
define hidden void @main.init(i8* %context, i8* %parentHandle) unnamed_addr #0 {
entry: entry:
ret void ret void
} }
; Function Attrs: nounwind define hidden %runtime._interface @main.simpleType(i8* %context, i8* %parentHandle) unnamed_addr {
define hidden %runtime._interface @main.simpleType(i8* %context, i8* %parentHandle) unnamed_addr #0 {
entry: entry:
ret %runtime._interface { i32 ptrtoint (%runtime.typecodeID* @"reflect/types.type:basic:int" to i32), i8* null } ret %runtime._interface { i32 ptrtoint (%runtime.typecodeID* @"reflect/types.type:basic:int" to i32), i8* null }
} }
; Function Attrs: nounwind define hidden %runtime._interface @main.pointerType(i8* %context, i8* %parentHandle) unnamed_addr {
define hidden %runtime._interface @main.pointerType(i8* %context, i8* %parentHandle) unnamed_addr #0 {
entry: entry:
ret %runtime._interface { i32 ptrtoint (%runtime.typecodeID* @"reflect/types.type:pointer:basic:int" to i32), i8* null } ret %runtime._interface { i32 ptrtoint (%runtime.typecodeID* @"reflect/types.type:pointer:basic:int" to i32), i8* null }
} }
; Function Attrs: nounwind define hidden %runtime._interface @main.interfaceType(i8* %context, i8* %parentHandle) unnamed_addr {
define hidden %runtime._interface @main.interfaceType(i8* %context, i8* %parentHandle) unnamed_addr #0 {
entry: entry:
ret %runtime._interface { i32 ptrtoint (%runtime.typecodeID* @"reflect/types.type:pointer:named:error" to i32), i8* null } ret %runtime._interface { i32 ptrtoint (%runtime.typecodeID* @"reflect/types.type:pointer:named:error" to i32), i8* null }
} }
declare i1 @"interface:{Error:func:{}{basic:string}}.$typeassert"(i32) #1 define hidden %runtime._interface @main.anonymousInterfaceType(i8* %context, i8* %parentHandle) unnamed_addr {
; Function Attrs: nounwind
define hidden %runtime._interface @main.anonymousInterfaceType(i8* %context, i8* %parentHandle) unnamed_addr #0 {
entry: entry:
ret %runtime._interface { i32 ptrtoint (%runtime.typecodeID* @"reflect/types.type:pointer:interface:{String:func:{}{basic:string}}" to i32), i8* null } ret %runtime._interface { i32 ptrtoint (%runtime.typecodeID* @"reflect/types.type:pointer:interface:{String:func:{}{basic:string}}" to i32), i8* null }
} }
declare i1 @"interface:{String:func:{}{basic:string}}.$typeassert"(i32) #2 define hidden i1 @main.isInt(i32 %itf.typecode, i8* %itf.value, i8* %context, i8* %parentHandle) unnamed_addr {
; Function Attrs: nounwind
define hidden i1 @main.isInt(i32 %itf.typecode, i8* %itf.value, i8* %context, i8* %parentHandle) unnamed_addr #0 {
entry: entry:
%typecode = call i1 @runtime.typeAssert(i32 %itf.typecode, i8* nonnull @"reflect/types.typeid:basic:int", i8* undef, i8* null) #0 %typecode = call i1 @runtime.typeAssert(i32 %itf.typecode, i8* nonnull @"reflect/types.type:basic:int$id", i8* undef, i8* null)
br i1 %typecode, label %typeassert.ok, label %typeassert.next br i1 %typecode, label %typeassert.ok, label %typeassert.next
typeassert.ok: ; preds = %entry typeassert.ok: ; preds = %entry
@@ -73,10 +63,9 @@ typeassert.next: ; preds = %typeassert.ok, %ent
declare i1 @runtime.typeAssert(i32, i8* dereferenceable_or_null(1), i8*, i8*) declare i1 @runtime.typeAssert(i32, i8* dereferenceable_or_null(1), i8*, i8*)
; Function Attrs: nounwind define hidden i1 @main.isError(i32 %itf.typecode, i8* %itf.value, i8* %context, i8* %parentHandle) unnamed_addr {
define hidden i1 @main.isError(i32 %itf.typecode, i8* %itf.value, i8* %context, i8* %parentHandle) unnamed_addr #0 {
entry: entry:
%0 = call i1 @"interface:{Error:func:{}{basic:string}}.$typeassert"(i32 %itf.typecode) #0 %0 = call i1 @runtime.interfaceImplements(i32 %itf.typecode, i8** getelementptr inbounds ([1 x i8*], [1 x i8*]* @"error$interface", i32 0, i32 0), i8* undef, i8* null)
br i1 %0, label %typeassert.ok, label %typeassert.next br i1 %0, label %typeassert.ok, label %typeassert.next
typeassert.ok: ; preds = %entry typeassert.ok: ; preds = %entry
@@ -86,10 +75,11 @@ typeassert.next: ; preds = %typeassert.ok, %ent
ret i1 %0 ret i1 %0
} }
; Function Attrs: nounwind declare i1 @runtime.interfaceImplements(i32, i8** dereferenceable_or_null(4), i8*, i8*)
define hidden i1 @main.isStringer(i32 %itf.typecode, i8* %itf.value, i8* %context, i8* %parentHandle) unnamed_addr #0 {
define hidden i1 @main.isStringer(i32 %itf.typecode, i8* %itf.value, i8* %context, i8* %parentHandle) unnamed_addr {
entry: entry:
%0 = call i1 @"interface:{String:func:{}{basic:string}}.$typeassert"(i32 %itf.typecode) #0 %0 = call i1 @runtime.interfaceImplements(i32 %itf.typecode, i8** getelementptr inbounds ([1 x i8*], [1 x i8*]* @"reflect/types.interface:interface{String() string}$interface", i32 0, i32 0), i8* undef, i8* null)
br i1 %0, label %typeassert.ok, label %typeassert.next br i1 %0, label %typeassert.ok, label %typeassert.next
typeassert.ok: ; preds = %entry typeassert.ok: ; preds = %entry
@@ -99,26 +89,12 @@ typeassert.next: ; preds = %typeassert.ok, %ent
ret i1 %0 ret i1 %0
} }
; Function Attrs: nounwind define hidden %runtime._string @main.callErrorMethod(i32 %itf.typecode, i8* %itf.value, i8* %context, i8* %parentHandle) unnamed_addr {
define hidden i8 @main.callFooMethod(i32 %itf.typecode, i8* %itf.value, i8* %context, i8* %parentHandle) unnamed_addr #0 {
entry: entry:
%0 = call i8 @"interface:{String:func:{}{basic:string},main.foo:func:{basic:int}{basic:uint8}}.foo$invoke"(i8* %itf.value, i32 3, i32 %itf.typecode, i8* undef, i8* undef) #0 %invoke.func = call i32 @runtime.interfaceMethod(i32 %itf.typecode, i8** getelementptr inbounds ([1 x i8*], [1 x i8*]* @"error$interface", i32 0, i32 0), i8* nonnull @"func Error() string", i8* undef, i8* null)
ret i8 %0 %invoke.func.cast = inttoptr i32 %invoke.func to %runtime._string (i8*, i8*, i8*)*
} %0 = call %runtime._string %invoke.func.cast(i8* %itf.value, i8* undef, i8* undef)
declare i8 @"interface:{String:func:{}{basic:string},main.foo:func:{basic:int}{basic:uint8}}.foo$invoke"(i8*, i32, i32, i8*, i8*) #3
; Function Attrs: nounwind
define hidden %runtime._string @main.callErrorMethod(i32 %itf.typecode, i8* %itf.value, i8* %context, i8* %parentHandle) unnamed_addr #0 {
entry:
%0 = call %runtime._string @"interface:{Error:func:{}{basic:string}}.Error$invoke"(i8* %itf.value, i32 %itf.typecode, i8* undef, i8* undef) #0
ret %runtime._string %0 ret %runtime._string %0
} }
declare %runtime._string @"interface:{Error:func:{}{basic:string}}.Error$invoke"(i8*, i32, i8*, i8*) #4 declare i32 @runtime.interfaceMethod(i32, i8** dereferenceable_or_null(4), i8* dereferenceable_or_null(1), i8*, i8*)
attributes #0 = { nounwind }
attributes #1 = { "tinygo-methods"="reflect/methods.Error() string" }
attributes #2 = { "tinygo-methods"="reflect/methods.String() string" }
attributes #3 = { "tinygo-invoke"="main.$methods.foo(int) byte" "tinygo-methods"="reflect/methods.String() string; main.$methods.foo(int) byte" }
attributes #4 = { "tinygo-invoke"="reflect/methods.Error() string" "tinygo-methods"="reflect/methods.Error() string" }
-32
View File
@@ -1,32 +0,0 @@
; ModuleID = 'intrinsics.go'
source_filename = "intrinsics.go"
target datalayout = "e-m:e-p:32:32-Fi8-i64:64-v128:64:128-a:0:32-n32-S64"
target triple = "thumbv7m-unknown-unknown-eabi"
declare noalias nonnull i8* @runtime.alloc(i32, i8*, i8*, i8*)
; Function Attrs: nounwind
define hidden void @main.init(i8* %context, i8* %parentHandle) unnamed_addr #0 {
entry:
ret void
}
; Function Attrs: nounwind
define hidden double @main.mySqrt(double %x, i8* %context, i8* %parentHandle) unnamed_addr #0 {
entry:
%0 = call double @math.Sqrt(double %x, i8* undef, i8* undef) #0
ret double %0
}
declare double @math.Sqrt(double, i8*, i8*)
; Function Attrs: nounwind
define hidden double @main.myTrunc(double %x, i8* %context, i8* %parentHandle) unnamed_addr #0 {
entry:
%0 = call double @math.Trunc(double %x, i8* undef, i8* undef) #0
ret double %0
}
declare double @math.Trunc(double, i8*, i8*)
attributes #0 = { nounwind }
-35
View File
@@ -1,35 +0,0 @@
; ModuleID = 'intrinsics.go'
source_filename = "intrinsics.go"
target datalayout = "e-m:e-p:32:32-i64:64-n32:64-S128"
target triple = "wasm32-unknown-wasi"
declare noalias nonnull i8* @runtime.alloc(i32, i8*, i8*, i8*)
; Function Attrs: nounwind
define hidden void @main.init(i8* %context, i8* %parentHandle) unnamed_addr #0 {
entry:
ret void
}
; Function Attrs: nounwind
define hidden double @main.mySqrt(double %x, i8* %context, i8* %parentHandle) unnamed_addr #0 {
entry:
%0 = call double @llvm.sqrt.f64(double %x)
ret double %0
}
; Function Attrs: nounwind readnone speculatable willreturn
declare double @llvm.sqrt.f64(double) #1
; Function Attrs: nounwind
define hidden double @main.myTrunc(double %x, i8* %context, i8* %parentHandle) unnamed_addr #0 {
entry:
%0 = call double @llvm.trunc.f64(double %x)
ret double %0
}
; Function Attrs: nounwind readnone speculatable willreturn
declare double @llvm.trunc.f64(double) #1
attributes #0 = { nounwind }
attributes #1 = { nounwind readnone speculatable willreturn }
-14
View File
@@ -1,14 +0,0 @@
package main
// Test how intrinsics are lowered: either as regular calls to the math
// functions or as LLVM builtins (such as llvm.sqrt.f64).
import "math"
func mySqrt(x float64) float64 {
return math.Sqrt(x)
}
func myTrunc(x float64) float64 {
return math.Trunc(x)
}
+11 -21
View File
@@ -1,61 +1,51 @@
; ModuleID = 'pointer.go' ; ModuleID = 'pointer.go'
source_filename = "pointer.go" source_filename = "pointer.go"
target datalayout = "e-m:e-p:32:32-i64:64-n32:64-S128" target datalayout = "e-m:e-p:32:32-p270:32:32-p271:32:32-p272:64:64-f64:32:64-f80:32-n8:16:32-S128"
target triple = "wasm32-unknown-wasi" target triple = "i686--linux"
declare noalias nonnull i8* @runtime.alloc(i32, i8*, i8*, i8*) declare noalias nonnull i8* @runtime.alloc(i32, i8*, i8*)
; Function Attrs: nounwind define hidden void @main.init(i8* %context, i8* %parentHandle) unnamed_addr {
define hidden void @main.init(i8* %context, i8* %parentHandle) unnamed_addr #0 {
entry: entry:
ret void ret void
} }
; Function Attrs: nounwind define hidden [0 x i32] @main.pointerDerefZero([0 x i32]* %x, i8* %context, i8* %parentHandle) unnamed_addr {
define hidden [0 x i32] @main.pointerDerefZero([0 x i32]* %x, i8* %context, i8* %parentHandle) unnamed_addr #0 {
entry: entry:
ret [0 x i32] zeroinitializer ret [0 x i32] zeroinitializer
} }
; Function Attrs: nounwind define hidden i32* @main.pointerCastFromUnsafe(i8* %x, i8* %context, i8* %parentHandle) unnamed_addr {
define hidden i32* @main.pointerCastFromUnsafe(i8* %x, i8* %context, i8* %parentHandle) unnamed_addr #0 {
entry: entry:
%0 = bitcast i8* %x to i32* %0 = bitcast i8* %x to i32*
ret i32* %0 ret i32* %0
} }
; Function Attrs: nounwind define hidden i8* @main.pointerCastToUnsafe(i32* dereferenceable_or_null(4) %x, i8* %context, i8* %parentHandle) unnamed_addr {
define hidden i8* @main.pointerCastToUnsafe(i32* dereferenceable_or_null(4) %x, i8* %context, i8* %parentHandle) unnamed_addr #0 {
entry: entry:
%0 = bitcast i32* %x to i8* %0 = bitcast i32* %x to i8*
ret i8* %0 ret i8* %0
} }
; Function Attrs: nounwind define hidden i8* @main.pointerCastToUnsafeNoop(i8* dereferenceable_or_null(1) %x, i8* %context, i8* %parentHandle) unnamed_addr {
define hidden i8* @main.pointerCastToUnsafeNoop(i8* dereferenceable_or_null(1) %x, i8* %context, i8* %parentHandle) unnamed_addr #0 {
entry: entry:
ret i8* %x ret i8* %x
} }
; Function Attrs: nounwind define hidden i8* @main.pointerUnsafeGEPFixedOffset(i8* dereferenceable_or_null(1) %ptr, i8* %context, i8* %parentHandle) unnamed_addr {
define hidden i8* @main.pointerUnsafeGEPFixedOffset(i8* dereferenceable_or_null(1) %ptr, i8* %context, i8* %parentHandle) unnamed_addr #0 {
entry: entry:
%0 = getelementptr inbounds i8, i8* %ptr, i32 10 %0 = getelementptr inbounds i8, i8* %ptr, i32 10
ret i8* %0 ret i8* %0
} }
; Function Attrs: nounwind define hidden i8* @main.pointerUnsafeGEPByteOffset(i8* dereferenceable_or_null(1) %ptr, i32 %offset, i8* %context, i8* %parentHandle) unnamed_addr {
define hidden i8* @main.pointerUnsafeGEPByteOffset(i8* dereferenceable_or_null(1) %ptr, i32 %offset, i8* %context, i8* %parentHandle) unnamed_addr #0 {
entry: entry:
%0 = getelementptr inbounds i8, i8* %ptr, i32 %offset %0 = getelementptr inbounds i8, i8* %ptr, i32 %offset
ret i8* %0 ret i8* %0
} }
; Function Attrs: nounwind define hidden i32* @main.pointerUnsafeGEPIntOffset(i32* dereferenceable_or_null(4) %ptr, i32 %offset, i8* %context, i8* %parentHandle) unnamed_addr {
define hidden i32* @main.pointerUnsafeGEPIntOffset(i32* dereferenceable_or_null(4) %ptr, i32 %offset, i8* %context, i8* %parentHandle) unnamed_addr #0 {
entry: entry:
%0 = getelementptr i32, i32* %ptr, i32 %offset %0 = getelementptr i32, i32* %ptr, i32 %offset
ret i32* %0 ret i32* %0
} }
attributes #0 = { nounwind }
-66
View File
@@ -1,66 +0,0 @@
package main
import _ "unsafe"
// Creates an external global with name extern_global.
//go:extern extern_global
var externGlobal [0]byte
// Creates a
//go:align 32
var alignedGlobal [4]uint32
// Test conflicting pragmas (the last one counts).
//go:align 64
//go:align 16
var alignedGlobal16 [4]uint32
// Test exported functions.
//export extern_func
func externFunc() {
}
// Define a function in a different package using go:linkname.
//go:linkname withLinkageName1 somepkg.someFunction1
func withLinkageName1() {
}
// Import a function from a different package using go:linkname.
//go:linkname withLinkageName2 somepkg.someFunction2
func withLinkageName2()
// Function has an 'inline hint', similar to the inline keyword in C.
//go:inline
func inlineFunc() {
}
// Function should never be inlined, equivalent to GCC
// __attribute__((noinline)).
//go:noinline
func noinlineFunc() {
}
// This function should have the specified section.
//go:section .special_function_section
func functionInSection() {
}
//export exportedFunctionInSection
//go:section .special_function_section
func exportedFunctionInSection() {
}
// This function should not: it's only a declaration and not a definition.
//go:section .special_function_section
func undefinedFunctionNotInSection()
//go:section .special_global_section
var globalInSection uint32
//go:section .special_global_section
//go:extern undefinedGlobalNotInSection
var undefinedGlobalNotInSection uint32
//go:align 1024
//go:section .global_section
var multipleGlobalPragmas uint32
-65
View File
@@ -1,65 +0,0 @@
; ModuleID = 'pragma.go'
source_filename = "pragma.go"
target datalayout = "e-m:e-p:32:32-i64:64-n32:64-S128"
target triple = "wasm32-unknown-wasi"
@extern_global = external global [0 x i8], align 1
@main.alignedGlobal = hidden global [4 x i32] zeroinitializer, align 32
@main.alignedGlobal16 = hidden global [4 x i32] zeroinitializer, align 16
@main.globalInSection = hidden global i32 0, section ".special_global_section", align 4
@undefinedGlobalNotInSection = external global i32, align 4
@main.multipleGlobalPragmas = hidden global i32 0, section ".global_section", align 1024
declare noalias nonnull i8* @runtime.alloc(i32, i8*, i8*, i8*)
; Function Attrs: nounwind
define hidden void @main.init(i8* %context, i8* %parentHandle) unnamed_addr #0 {
entry:
ret void
}
; Function Attrs: nounwind
define void @extern_func() #1 {
entry:
ret void
}
; Function Attrs: nounwind
define hidden void @somepkg.someFunction1(i8* %context, i8* %parentHandle) unnamed_addr #0 {
entry:
ret void
}
declare void @somepkg.someFunction2(i8*, i8*)
; Function Attrs: inlinehint nounwind
define hidden void @main.inlineFunc(i8* %context, i8* %parentHandle) unnamed_addr #2 {
entry:
ret void
}
; Function Attrs: noinline nounwind
define hidden void @main.noinlineFunc(i8* %context, i8* %parentHandle) unnamed_addr #3 {
entry:
ret void
}
; Function Attrs: nounwind
define hidden void @main.functionInSection(i8* %context, i8* %parentHandle) unnamed_addr #0 section ".special_function_section" {
entry:
ret void
}
; Function Attrs: nounwind
define void @exportedFunctionInSection() #4 section ".special_function_section" {
entry:
ret void
}
declare void @main.undefinedFunctionNotInSection(i8*, i8*)
attributes #0 = { nounwind }
attributes #1 = { nounwind "wasm-export-name"="extern_func" }
attributes #2 = { inlinehint nounwind }
attributes #3 = { noinline nounwind }
attributes #4 = { nounwind "wasm-export-name"="exportedFunctionInSection" }
-18
View File
@@ -23,21 +23,3 @@ func sliceAppendSlice(ints, added []int) []int {
func sliceCopy(dst, src []int) int { func sliceCopy(dst, src []int) int {
return copy(dst, src) return copy(dst, src)
} }
// Test bounds checking in *ssa.MakeSlice instruction.
func makeByteSlice(len int) []byte {
return make([]byte, len)
}
func makeInt16Slice(len int) []int16 {
return make([]int16, len)
}
func makeArraySlice(len int) [][3]byte {
return make([][3]byte, len) // slice with element size of 3
}
func makeInt32Slice(len int) []int32 {
return make([]int32, len)
}
+17 -106
View File
@@ -1,36 +1,32 @@
; ModuleID = 'slice.go' ; ModuleID = 'slice.go'
source_filename = "slice.go" source_filename = "slice.go"
target datalayout = "e-m:e-p:32:32-i64:64-n32:64-S128" target datalayout = "e-m:e-p:32:32-p270:32:32-p271:32:32-p272:64:64-f64:32:64-f80:32-n8:16:32-S128"
target triple = "wasm32-unknown-wasi" target triple = "i686--linux"
declare noalias nonnull i8* @runtime.alloc(i32, i8*, i8*, i8*) declare noalias nonnull i8* @runtime.alloc(i32, i8*, i8*)
; Function Attrs: nounwind define hidden void @main.init(i8* %context, i8* %parentHandle) unnamed_addr {
define hidden void @main.init(i8* %context, i8* %parentHandle) unnamed_addr #0 {
entry: entry:
ret void ret void
} }
; Function Attrs: nounwind define hidden i32 @main.sliceLen(i32* %ints.data, i32 %ints.len, i32 %ints.cap, i8* %context, i8* %parentHandle) unnamed_addr {
define hidden i32 @main.sliceLen(i32* %ints.data, i32 %ints.len, i32 %ints.cap, i8* %context, i8* %parentHandle) unnamed_addr #0 {
entry: entry:
ret i32 %ints.len ret i32 %ints.len
} }
; Function Attrs: nounwind define hidden i32 @main.sliceCap(i32* %ints.data, i32 %ints.len, i32 %ints.cap, i8* %context, i8* %parentHandle) unnamed_addr {
define hidden i32 @main.sliceCap(i32* %ints.data, i32 %ints.len, i32 %ints.cap, i8* %context, i8* %parentHandle) unnamed_addr #0 {
entry: entry:
ret i32 %ints.cap ret i32 %ints.cap
} }
; Function Attrs: nounwind define hidden i32 @main.sliceElement(i32* %ints.data, i32 %ints.len, i32 %ints.cap, i32 %index, i8* %context, i8* %parentHandle) unnamed_addr {
define hidden i32 @main.sliceElement(i32* %ints.data, i32 %ints.len, i32 %ints.cap, i32 %index, i8* %context, i8* %parentHandle) unnamed_addr #0 {
entry: entry:
%.not = icmp ult i32 %index, %ints.len %.not = icmp ult i32 %index, %ints.len
br i1 %.not, label %lookup.next, label %lookup.throw br i1 %.not, label %lookup.next, label %lookup.throw
lookup.throw: ; preds = %entry lookup.throw: ; preds = %entry
call void @runtime.lookupPanic(i8* undef, i8* null) #0 call void @runtime.lookupPanic(i8* undef, i8* null)
unreachable unreachable
lookup.next: ; preds = %entry lookup.next: ; preds = %entry
@@ -41,10 +37,9 @@ lookup.next: ; preds = %entry
declare void @runtime.lookupPanic(i8*, i8*) declare void @runtime.lookupPanic(i8*, i8*)
; Function Attrs: nounwind define hidden { i32*, i32, i32 } @main.sliceAppendValues(i32* %ints.data, i32 %ints.len, i32 %ints.cap, i8* %context, i8* %parentHandle) unnamed_addr {
define hidden { i32*, i32, i32 } @main.sliceAppendValues(i32* %ints.data, i32 %ints.len, i32 %ints.cap, i8* %context, i8* %parentHandle) unnamed_addr #0 {
entry: entry:
%varargs = call i8* @runtime.alloc(i32 12, i8* nonnull inttoptr (i32 3 to i8*), i8* undef, i8* null) #0 %varargs = call i8* @runtime.alloc(i32 12, i8* undef, i8* null)
%0 = bitcast i8* %varargs to i32* %0 = bitcast i8* %varargs to i32*
store i32 1, i32* %0, align 4 store i32 1, i32* %0, align 4
%1 = getelementptr inbounds i8, i8* %varargs, i32 4 %1 = getelementptr inbounds i8, i8* %varargs, i32 4
@@ -54,7 +49,7 @@ entry:
%4 = bitcast i8* %3 to i32* %4 = bitcast i8* %3 to i32*
store i32 3, i32* %4, align 4 store i32 3, i32* %4, align 4
%append.srcPtr = bitcast i32* %ints.data to i8* %append.srcPtr = bitcast i32* %ints.data to i8*
%append.new = call { i8*, i32, i32 } @runtime.sliceAppend(i8* %append.srcPtr, i8* nonnull %varargs, i32 %ints.len, i32 %ints.cap, i32 3, i32 4, i8* undef, i8* null) #0 %append.new = call { i8*, i32, i32 } @runtime.sliceAppend(i8* %append.srcPtr, i8* nonnull %varargs, i32 %ints.len, i32 %ints.cap, i32 3, i32 4, i8* undef, i8* null)
%append.newPtr = extractvalue { i8*, i32, i32 } %append.new, 0 %append.newPtr = extractvalue { i8*, i32, i32 } %append.new, 0
%append.newBuf = bitcast i8* %append.newPtr to i32* %append.newBuf = bitcast i8* %append.newPtr to i32*
%append.newLen = extractvalue { i8*, i32, i32 } %append.new, 1 %append.newLen = extractvalue { i8*, i32, i32 } %append.new, 1
@@ -65,14 +60,13 @@ entry:
ret { i32*, i32, i32 } %7 ret { i32*, i32, i32 } %7
} }
declare { i8*, i32, i32 } @runtime.sliceAppend(i8*, i8* nocapture readonly, i32, i32, i32, i32, i8*, i8*) declare { i8*, i32, i32 } @runtime.sliceAppend(i8*, i8*, i32, i32, i32, i32, i8*, i8*)
; Function Attrs: nounwind define hidden { i32*, i32, i32 } @main.sliceAppendSlice(i32* %ints.data, i32 %ints.len, i32 %ints.cap, i32* %added.data, i32 %added.len, i32 %added.cap, i8* %context, i8* %parentHandle) unnamed_addr {
define hidden { i32*, i32, i32 } @main.sliceAppendSlice(i32* %ints.data, i32 %ints.len, i32 %ints.cap, i32* %added.data, i32 %added.len, i32 %added.cap, i8* %context, i8* %parentHandle) unnamed_addr #0 {
entry: entry:
%append.srcPtr = bitcast i32* %ints.data to i8* %append.srcPtr = bitcast i32* %ints.data to i8*
%append.srcPtr1 = bitcast i32* %added.data to i8* %append.srcPtr1 = bitcast i32* %added.data to i8*
%append.new = call { i8*, i32, i32 } @runtime.sliceAppend(i8* %append.srcPtr, i8* %append.srcPtr1, i32 %ints.len, i32 %ints.cap, i32 %added.len, i32 4, i8* undef, i8* null) #0 %append.new = call { i8*, i32, i32 } @runtime.sliceAppend(i8* %append.srcPtr, i8* %append.srcPtr1, i32 %ints.len, i32 %ints.cap, i32 %added.len, i32 4, i8* undef, i8* null)
%append.newPtr = extractvalue { i8*, i32, i32 } %append.new, 0 %append.newPtr = extractvalue { i8*, i32, i32 } %append.new, 0
%append.newBuf = bitcast i8* %append.newPtr to i32* %append.newBuf = bitcast i8* %append.newPtr to i32*
%append.newLen = extractvalue { i8*, i32, i32 } %append.new, 1 %append.newLen = extractvalue { i8*, i32, i32 } %append.new, 1
@@ -83,95 +77,12 @@ entry:
ret { i32*, i32, i32 } %2 ret { i32*, i32, i32 } %2
} }
; Function Attrs: nounwind define hidden i32 @main.sliceCopy(i32* %dst.data, i32 %dst.len, i32 %dst.cap, i32* %src.data, i32 %src.len, i32 %src.cap, i8* %context, i8* %parentHandle) unnamed_addr {
define hidden i32 @main.sliceCopy(i32* %dst.data, i32 %dst.len, i32 %dst.cap, i32* %src.data, i32 %src.len, i32 %src.cap, i8* %context, i8* %parentHandle) unnamed_addr #0 {
entry: entry:
%copy.dstPtr = bitcast i32* %dst.data to i8* %copy.dstPtr = bitcast i32* %dst.data to i8*
%copy.srcPtr = bitcast i32* %src.data to i8* %copy.srcPtr = bitcast i32* %src.data to i8*
%copy.n = call i32 @runtime.sliceCopy(i8* %copy.dstPtr, i8* %copy.srcPtr, i32 %dst.len, i32 %src.len, i32 4, i8* undef, i8* null) #0 %copy.n = call i32 @runtime.sliceCopy(i8* %copy.dstPtr, i8* %copy.srcPtr, i32 %dst.len, i32 %src.len, i32 4, i8* undef, i8* null)
ret i32 %copy.n ret i32 %copy.n
} }
declare i32 @runtime.sliceCopy(i8* nocapture writeonly, i8* nocapture readonly, i32, i32, i32, i8*, i8*) declare i32 @runtime.sliceCopy(i8*, i8*, i32, i32, i32, i8*, i8*)
; Function Attrs: nounwind
define hidden { i8*, i32, i32 } @main.makeByteSlice(i32 %len, i8* %context, i8* %parentHandle) unnamed_addr #0 {
entry:
%slice.maxcap = icmp slt i32 %len, 0
br i1 %slice.maxcap, label %slice.throw, label %slice.next
slice.throw: ; preds = %entry
call void @runtime.slicePanic(i8* undef, i8* null) #0
unreachable
slice.next: ; preds = %entry
%makeslice.buf = call i8* @runtime.alloc(i32 %len, i8* nonnull inttoptr (i32 3 to i8*), i8* undef, i8* null) #0
%0 = insertvalue { i8*, i32, i32 } undef, i8* %makeslice.buf, 0
%1 = insertvalue { i8*, i32, i32 } %0, i32 %len, 1
%2 = insertvalue { i8*, i32, i32 } %1, i32 %len, 2
ret { i8*, i32, i32 } %2
}
declare void @runtime.slicePanic(i8*, i8*)
; Function Attrs: nounwind
define hidden { i16*, i32, i32 } @main.makeInt16Slice(i32 %len, i8* %context, i8* %parentHandle) unnamed_addr #0 {
entry:
%slice.maxcap = icmp slt i32 %len, 0
br i1 %slice.maxcap, label %slice.throw, label %slice.next
slice.throw: ; preds = %entry
call void @runtime.slicePanic(i8* undef, i8* null) #0
unreachable
slice.next: ; preds = %entry
%makeslice.cap = shl i32 %len, 1
%makeslice.buf = call i8* @runtime.alloc(i32 %makeslice.cap, i8* nonnull inttoptr (i32 3 to i8*), i8* undef, i8* null) #0
%makeslice.array = bitcast i8* %makeslice.buf to i16*
%0 = insertvalue { i16*, i32, i32 } undef, i16* %makeslice.array, 0
%1 = insertvalue { i16*, i32, i32 } %0, i32 %len, 1
%2 = insertvalue { i16*, i32, i32 } %1, i32 %len, 2
ret { i16*, i32, i32 } %2
}
; Function Attrs: nounwind
define hidden { [3 x i8]*, i32, i32 } @main.makeArraySlice(i32 %len, i8* %context, i8* %parentHandle) unnamed_addr #0 {
entry:
%slice.maxcap = icmp ugt i32 %len, 1431655765
br i1 %slice.maxcap, label %slice.throw, label %slice.next
slice.throw: ; preds = %entry
call void @runtime.slicePanic(i8* undef, i8* null) #0
unreachable
slice.next: ; preds = %entry
%makeslice.cap = mul i32 %len, 3
%makeslice.buf = call i8* @runtime.alloc(i32 %makeslice.cap, i8* nonnull inttoptr (i32 3 to i8*), i8* undef, i8* null) #0
%makeslice.array = bitcast i8* %makeslice.buf to [3 x i8]*
%0 = insertvalue { [3 x i8]*, i32, i32 } undef, [3 x i8]* %makeslice.array, 0
%1 = insertvalue { [3 x i8]*, i32, i32 } %0, i32 %len, 1
%2 = insertvalue { [3 x i8]*, i32, i32 } %1, i32 %len, 2
ret { [3 x i8]*, i32, i32 } %2
}
; Function Attrs: nounwind
define hidden { i32*, i32, i32 } @main.makeInt32Slice(i32 %len, i8* %context, i8* %parentHandle) unnamed_addr #0 {
entry:
%slice.maxcap = icmp ugt i32 %len, 1073741823
br i1 %slice.maxcap, label %slice.throw, label %slice.next
slice.throw: ; preds = %entry
call void @runtime.slicePanic(i8* undef, i8* null) #0
unreachable
slice.next: ; preds = %entry
%makeslice.cap = shl i32 %len, 2
%makeslice.buf = call i8* @runtime.alloc(i32 %makeslice.cap, i8* nonnull inttoptr (i32 3 to i8*), i8* undef, i8* null) #0
%makeslice.array = bitcast i8* %makeslice.buf to i32*
%0 = insertvalue { i32*, i32, i32 } undef, i32* %makeslice.array, 0
%1 = insertvalue { i32*, i32, i32 } %0, i32 %len, 1
%2 = insertvalue { i32*, i32, i32 } %1, i32 %len, 2
ret { i32*, i32, i32 } %2
}
attributes #0 = { nounwind }
-8
View File
@@ -1,13 +1,5 @@
package main package main
func someString() string {
return "foo"
}
func zeroLengthString() string {
return ""
}
func stringLen(s string) int { func stringLen(s string) int {
return len(s) return len(s)
} }
+13 -37
View File
@@ -1,46 +1,27 @@
; ModuleID = 'string.go' ; ModuleID = 'string.go'
source_filename = "string.go" source_filename = "string.go"
target datalayout = "e-m:e-p:32:32-i64:64-n32:64-S128" target datalayout = "e-m:e-p:32:32-p270:32:32-p271:32:32-p272:64:64-f64:32:64-f80:32-n8:16:32-S128"
target triple = "wasm32-unknown-wasi" target triple = "i686--linux"
%runtime._string = type { i8*, i32 } declare noalias nonnull i8* @runtime.alloc(i32, i8*, i8*)
@"main$string" = internal unnamed_addr constant [3 x i8] c"foo", align 1 define hidden void @main.init(i8* %context, i8* %parentHandle) unnamed_addr {
declare noalias nonnull i8* @runtime.alloc(i32, i8*, i8*, i8*)
; Function Attrs: nounwind
define hidden void @main.init(i8* %context, i8* %parentHandle) unnamed_addr #0 {
entry: entry:
ret void ret void
} }
; Function Attrs: nounwind define hidden i32 @main.stringLen(i8* %s.data, i32 %s.len, i8* %context, i8* %parentHandle) unnamed_addr {
define hidden %runtime._string @main.someString(i8* %context, i8* %parentHandle) unnamed_addr #0 {
entry:
ret %runtime._string { i8* getelementptr inbounds ([3 x i8], [3 x i8]* @"main$string", i32 0, i32 0), i32 3 }
}
; Function Attrs: nounwind
define hidden %runtime._string @main.zeroLengthString(i8* %context, i8* %parentHandle) unnamed_addr #0 {
entry:
ret %runtime._string zeroinitializer
}
; Function Attrs: nounwind
define hidden i32 @main.stringLen(i8* %s.data, i32 %s.len, i8* %context, i8* %parentHandle) unnamed_addr #0 {
entry: entry:
ret i32 %s.len ret i32 %s.len
} }
; Function Attrs: nounwind define hidden i8 @main.stringIndex(i8* %s.data, i32 %s.len, i32 %index, i8* %context, i8* %parentHandle) unnamed_addr {
define hidden i8 @main.stringIndex(i8* %s.data, i32 %s.len, i32 %index, i8* %context, i8* %parentHandle) unnamed_addr #0 {
entry: entry:
%.not = icmp ult i32 %index, %s.len %.not = icmp ult i32 %index, %s.len
br i1 %.not, label %lookup.next, label %lookup.throw br i1 %.not, label %lookup.next, label %lookup.throw
lookup.throw: ; preds = %entry lookup.throw: ; preds = %entry
call void @runtime.lookupPanic(i8* undef, i8* null) #0 call void @runtime.lookupPanic(i8* undef, i8* null)
unreachable unreachable
lookup.next: ; preds = %entry lookup.next: ; preds = %entry
@@ -51,31 +32,26 @@ lookup.next: ; preds = %entry
declare void @runtime.lookupPanic(i8*, i8*) declare void @runtime.lookupPanic(i8*, i8*)
; Function Attrs: nounwind define hidden i1 @main.stringCompareEqual(i8* %s1.data, i32 %s1.len, i8* %s2.data, i32 %s2.len, i8* %context, i8* %parentHandle) unnamed_addr {
define hidden i1 @main.stringCompareEqual(i8* %s1.data, i32 %s1.len, i8* %s2.data, i32 %s2.len, i8* %context, i8* %parentHandle) unnamed_addr #0 {
entry: entry:
%0 = call i1 @runtime.stringEqual(i8* %s1.data, i32 %s1.len, i8* %s2.data, i32 %s2.len, i8* undef, i8* null) #0 %0 = call i1 @runtime.stringEqual(i8* %s1.data, i32 %s1.len, i8* %s2.data, i32 %s2.len, i8* undef, i8* null)
ret i1 %0 ret i1 %0
} }
declare i1 @runtime.stringEqual(i8*, i32, i8*, i32, i8*, i8*) declare i1 @runtime.stringEqual(i8*, i32, i8*, i32, i8*, i8*)
; Function Attrs: nounwind define hidden i1 @main.stringCompareUnequal(i8* %s1.data, i32 %s1.len, i8* %s2.data, i32 %s2.len, i8* %context, i8* %parentHandle) unnamed_addr {
define hidden i1 @main.stringCompareUnequal(i8* %s1.data, i32 %s1.len, i8* %s2.data, i32 %s2.len, i8* %context, i8* %parentHandle) unnamed_addr #0 {
entry: entry:
%0 = call i1 @runtime.stringEqual(i8* %s1.data, i32 %s1.len, i8* %s2.data, i32 %s2.len, i8* undef, i8* null) #0 %0 = call i1 @runtime.stringEqual(i8* %s1.data, i32 %s1.len, i8* %s2.data, i32 %s2.len, i8* undef, i8* null)
%1 = xor i1 %0, true %1 = xor i1 %0, true
ret i1 %1 ret i1 %1
} }
; Function Attrs: nounwind define hidden i1 @main.stringCompareLarger(i8* %s1.data, i32 %s1.len, i8* %s2.data, i32 %s2.len, i8* %context, i8* %parentHandle) unnamed_addr {
define hidden i1 @main.stringCompareLarger(i8* %s1.data, i32 %s1.len, i8* %s2.data, i32 %s2.len, i8* %context, i8* %parentHandle) unnamed_addr #0 {
entry: entry:
%0 = call i1 @runtime.stringLess(i8* %s1.data, i32 %s1.len, i8* %s2.data, i32 %s2.len, i8* undef, i8* null) #0 %0 = call i1 @runtime.stringLess(i8* %s1.data, i32 %s1.len, i8* %s2.data, i32 %s2.len, i8* undef, i8* null)
%1 = xor i1 %0, true %1 = xor i1 %0, true
ret i1 %1 ret i1 %1
} }
declare i1 @runtime.stringLess(i8*, i32, i8*, i32, i8*, i8*) declare i1 @runtime.stringLess(i8*, i32, i8*, i32, i8*, i8*)
attributes #0 = { nounwind }
+5 -6
View File
@@ -1,17 +1,16 @@
module github.com/tinygo-org/tinygo module github.com/tinygo-org/tinygo
go 1.15 go 1.13
require ( require (
github.com/aykevl/go-wasm v0.0.2-0.20211030161413-11881cb9032d // indirect
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-20210113043257-dabd2f2e7693 github.com/chromedp/cdproto v0.0.0-20210113043257-dabd2f2e7693
github.com/chromedp/chromedp v0.6.4 github.com/chromedp/chromedp v0.6.4
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-20200531091804-343a4b548892
github.com/mattn/go-colorable v0.1.8 github.com/mattn/go-colorable v0.1.8
go.bug.st/serial v1.1.3 go.bug.st/serial v1.1.2
golang.org/x/sys v0.0.0-20210510120138-977fb7262007 golang.org/x/sys v0.0.0-20210113181707-4bcb84eeeb78
golang.org/x/tools v0.1.6-0.20210813165731-45389f592fe9 golang.org/x/tools v0.0.0-20200216192241-b320d3a0f5a2
tinygo.org/x/go-llvm v0.0.0-20210325115028-e7b85195e81c tinygo.org/x/go-llvm v0.0.0-20210308112806-9ef958b6bed4
) )
+12 -25
View File
@@ -1,5 +1,3 @@
github.com/aykevl/go-wasm v0.0.2-0.20211030161413-11881cb9032d h1:JeuI5/546naK5hpOIX+Lq5xE8rvt7uwiTp6iL+pLQgk=
github.com/aykevl/go-wasm v0.0.2-0.20211030161413-11881cb9032d/go.mod h1:7sXyiaA0WtSogCu67R2252fQpVmJMh9JWJ9ddtGkpWw=
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-20210113043257-dabd2f2e7693 h1:11eq/RkpaotwdF6b1TRMcdgQUPNmyFEJOB7zLvh0O/Y= github.com/chromedp/cdproto v0.0.0-20210113043257-dabd2f2e7693 h1:11eq/RkpaotwdF6b1TRMcdgQUPNmyFEJOB7zLvh0O/Y=
@@ -8,8 +6,8 @@ github.com/chromedp/chromedp v0.6.4 h1:Gx7ZkRyrSVmbbDDja/ieNgNGJIvElroPOyeqYQGVD
github.com/chromedp/chromedp v0.6.4/go.mod h1:vodUdJf5dF/b8n0UBJv6NeM/QK28RjP3j+eM7fq4+84= github.com/chromedp/chromedp v0.6.4/go.mod h1:vodUdJf5dF/b8n0UBJv6NeM/QK28RjP3j+eM7fq4+84=
github.com/chromedp/sysutil v1.0.0 h1:+ZxhTpfpZlmchB58ih/LBHX52ky7w2VhQVKQMucy3Ic= github.com/chromedp/sysutil v1.0.0 h1:+ZxhTpfpZlmchB58ih/LBHX52ky7w2VhQVKQMucy3Ic=
github.com/chromedp/sysutil v1.0.0/go.mod h1:kgWmDdq8fTzXYcKIBqIYvRRTnYb9aNS9moAV0xufSww= github.com/chromedp/sysutil v1.0.0/go.mod h1:kgWmDdq8fTzXYcKIBqIYvRRTnYb9aNS9moAV0xufSww=
github.com/creack/goselect v0.1.2 h1:2DNy14+JPjRBgPzAd1thbQp4BSIihxcBf0IXhQXDRa0= github.com/creack/goselect v0.1.1 h1:tiSSgKE1eJtxs1h/VgGQWuXUP0YS4CDIFMp6vaI1ls0=
github.com/creack/goselect v0.1.2/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.1.0 h1:exrUm0f4YX0L7EBwZHuCF4GDp8aJfVeBrlLQrs6NqWU= github.com/gobwas/httphead v0.1.0 h1:exrUm0f4YX0L7EBwZHuCF4GDp8aJfVeBrlLQrs6NqWU=
github.com/gobwas/httphead v0.1.0/go.mod h1:O/RXo79gxV8G+RqlR/otEwx4Q36zl9rqC5u12GKvMCM= github.com/gobwas/httphead v0.1.0/go.mod h1:O/RXo79gxV8G+RqlR/otEwx4Q36zl9rqC5u12GKvMCM=
@@ -32,43 +30,32 @@ github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Ky
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=
github.com/yuin/goldmark v1.3.5 h1:dPmz1Snjq0kmkz159iL7S6WzdahUTHnHB5M56WFVifs= go.bug.st/serial v1.0.0 h1:ogEPzrllCsnG00EqKRjeYvPRsO7NJW6DqykzkdD6E/k=
github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= go.bug.st/serial v1.0.0/go.mod h1:rpXPISGjuNjPTRTcMlxi9lN6LoIPxd1ixVjBd8aSk/Q=
go.bug.st/serial v1.1.3 h1:YEBxJa9pKS9Wdg46B/jiaKbvvbUrjhZZZITfJHEJhaE= go.bug.st/serial v1.1.2 h1:6xDpbta8KJ+VLRTeM8ghhxXRMLE/Lr8h9iDKwydarAY=
go.bug.st/serial v1.1.3/go.mod h1:8TT7u/SwwNIpJ8QaG4s+HTjFt9ReXs2cdOU7ZEk50Dk= go.bug.st/serial v1.1.2/go.mod h1:VmYBeyJWp5BnJ0tw2NUJHZdJTGl2ecBGABHlzRK1knY=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
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/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
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/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
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/sync v0.0.0-20210220032951-036812b2e83c/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/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200909081042-eff7692f9009/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200909081042-eff7692f9009/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210113181707-4bcb84eeeb78 h1:nVuTkr9L6Bq62qpUqKo/RnZCFfzDBL0bYo6w9OJUqZY=
golang.org/x/sys v0.0.0-20210113181707-4bcb84eeeb78/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210113181707-4bcb84eeeb78/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210510120138-977fb7262007 h1:gG67DSER+11cZvqIMb8S8bt0vZtiN6xWYARwirrOSfE=
golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
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/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/tools v0.0.0-20200216192241-b320d3a0f5a2 h1:0sfSpGSa544Fwnbot3Oxq/U6SXqjty6Jy/3wRhVS7ig=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20200216192241-b320d3a0f5a2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898 h1:/atklqdjdhuosWIl6AIbOeHJjicWYPqR9bpxqxYG2pA=
golang.org/x/tools v0.1.6-0.20210813165731-45389f592fe9 h1:nvvuMxmx1q0gfRki3T0hjG8EwAcVCs91oWAXvyt4zhI=
golang.org/x/tools v0.1.6-0.20210813165731-45389f592fe9/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
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=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
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-20210308112806-9ef958b6bed4 h1:CMUHxVTb+UuUePuMf8vkWjZ3gTp9BBK91KrgOCwoNHs= tinygo.org/x/go-llvm v0.0.0-20210308112806-9ef958b6bed4 h1:CMUHxVTb+UuUePuMf8vkWjZ3gTp9BBK91KrgOCwoNHs=
tinygo.org/x/go-llvm v0.0.0-20210308112806-9ef958b6bed4/go.mod h1:fv1F0BSNpxMfCL0zF3M4OPFbgYHnhtB6ST0HvUtu/LE= tinygo.org/x/go-llvm v0.0.0-20210308112806-9ef958b6bed4/go.mod h1:fv1F0BSNpxMfCL0zF3M4OPFbgYHnhtB6ST0HvUtu/LE=
tinygo.org/x/go-llvm v0.0.0-20210325115028-e7b85195e81c h1:vn9IPshzYmzZis10UEVrsIBRv9FpykADw6M3/tHHROg=
tinygo.org/x/go-llvm v0.0.0-20210325115028-e7b85195e81c/go.mod h1:fv1F0BSNpxMfCL0zF3M4OPFbgYHnhtB6ST0HvUtu/LE=
+2 -3
View File
@@ -171,9 +171,8 @@ func getGoroot() string {
switch runtime.GOOS { switch runtime.GOOS {
case "linux": case "linux":
candidates = []string{ candidates = []string{
"/usr/local/go", // manually installed "/usr/local/go", // manually installed
"/usr/lib/go", // from the distribution "/usr/lib/go", // from the distribution
"/snap/go/current/", // installed using snap
} }
case "darwin": case "darwin":
candidates = []string{ candidates = []string{
+5 -5
View File
@@ -12,7 +12,7 @@ import (
// Version of TinyGo. // Version of TinyGo.
// Update this value before release of new version of software. // Update this value before release of new version of software.
const Version = "0.21.0-dev" const Version = "0.18.0-dev"
// GetGorootVersion returns the major and minor version for a given GOROOT path. // GetGorootVersion returns the major and minor version for a given GOROOT path.
// If the goroot cannot be determined, (0, 0) is returned. // If the goroot cannot be determined, (0, 0) is returned.
@@ -48,10 +48,7 @@ func GetGorootVersion(goroot string) (major, minor int, err error) {
// toolchain for the given GOROOT path. It is usually of the form `go1.x.y` but // 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). // can have some variations (for beta releases, for example).
func GorootVersionString(goroot string) (string, error) { func GorootVersionString(goroot string) (string, error) {
if data, err := ioutil.ReadFile(filepath.Join(goroot, "VERSION")); err == nil { if data, err := ioutil.ReadFile(filepath.Join(
return string(data), nil
} else if data, err := ioutil.ReadFile(filepath.Join(
goroot, "src", "runtime", "internal", "sys", "zversion.go")); err == nil { goroot, "src", "runtime", "internal", "sys", "zversion.go")); err == nil {
r := regexp.MustCompile("const TheVersion = `(.*)`") r := regexp.MustCompile("const TheVersion = `(.*)`")
@@ -62,6 +59,9 @@ func GorootVersionString(goroot string) (string, error) {
return string(matches[1]), nil return string(matches[1]), nil
} else if data, err := ioutil.ReadFile(filepath.Join(goroot, "VERSION")); err == nil {
return string(data), nil
} else { } else {
return "", err return "", err
} }
+1 -17
View File
@@ -25,7 +25,6 @@ type function struct {
// basicBlock represents a LLVM basic block and contains a slice of // basicBlock represents a LLVM basic block and contains a slice of
// instructions. The last instruction must be a terminator instruction. // instructions. The last instruction must be a terminator instruction.
type basicBlock struct { type basicBlock struct {
phiNodes []instruction
instructions []instruction instructions []instruction
} }
@@ -136,15 +135,6 @@ func (r *runner) compileFunction(llvmFn llvm.Value) *function {
default: default:
panic("unknown number of operands") panic("unknown number of operands")
} }
case llvm.Switch:
// A switch is an array of (value, label) pairs, of which the
// first one indicates the to-switch value and the default
// label.
numOperands := llvmInst.OperandsCount()
for i := 0; i < numOperands; i += 2 {
inst.operands = append(inst.operands, r.getValue(llvmInst.Operand(i)))
inst.operands = append(inst.operands, literalValue{uint32(blockIndices[llvmInst.Operand(i+1)])})
}
case llvm.PHI: case llvm.PHI:
inst.name = llvmInst.Name() inst.name = llvmInst.Name()
incomingCount := inst.llvmInst.IncomingCount() incomingCount := inst.llvmInst.IncomingCount()
@@ -347,13 +337,7 @@ func (r *runner) compileFunction(llvmFn llvm.Value) *function {
// This error is handled when actually trying to interpret this // This error is handled when actually trying to interpret this
// instruction (to not trigger on code that won't be executed). // instruction (to not trigger on code that won't be executed).
} }
if inst.opcode == llvm.PHI { bb.instructions = append(bb.instructions, inst)
// PHI nodes need to be treated specially, see the comment in
// interpreter.go for an explanation.
bb.phiNodes = append(bb.phiNodes, inst)
} else {
bb.instructions = append(bb.instructions, inst)
}
} }
} }
return fn return fn
-1
View File
@@ -57,7 +57,6 @@ func (r *runner) errorAt(inst instruction, err error) *Error {
pos := getPosition(inst.llvmInst) pos := getPosition(inst.llvmInst)
return &Error{ return &Error{
ImportPath: r.pkgName, ImportPath: r.pkgName,
Inst: inst.llvmInst,
Pos: pos, Pos: pos,
Err: err, Err: err,
Traceback: []ErrorLine{{pos, inst.llvmInst}}, Traceback: []ErrorLine{{pos, inst.llvmInst}},
+3 -34
View File
@@ -15,7 +15,7 @@ import (
// package is changed in a way that affects the output so that cached package // package is changed in a way that affects the output so that cached package
// builds will be invalidated. // builds will be invalidated.
// This version is independent of the TinyGo version number. // This version is independent of the TinyGo version number.
const Version = 2 // last change: fix GEP on untyped pointers const Version = 1
// Enable extra checks, which should be disabled by default. // Enable extra checks, which should be disabled by default.
// This may help track down bugs by adding a few more sanity checks. // This may help track down bugs by adding a few more sanity checks.
@@ -110,26 +110,17 @@ func Run(mod llvm.Module, debug bool) error {
fmt.Fprintln(os.Stderr, "call:", fn.Name()) fmt.Fprintln(os.Stderr, "call:", fn.Name())
} }
_, mem, callErr := r.run(r.getFunction(fn), nil, nil, " ") _, mem, callErr := r.run(r.getFunction(fn), nil, nil, " ")
call.EraseFromParentAsInstruction()
if callErr != nil { if callErr != nil {
if isRecoverableError(callErr.Err) { if isRecoverableError(callErr.Err) {
if r.debug { if r.debug {
fmt.Fprintln(os.Stderr, "not interpreting", r.pkgName, "because of error:", callErr.Error()) fmt.Fprintln(os.Stderr, "not interpreting", r.pkgName, "because of error:", callErr.Error())
} }
// Remove instructions that were created as part of interpreting
// the package.
mem.revert() mem.revert()
// Create a call to the package initializer (which was
// previously deleted).
i8undef := llvm.Undef(r.i8ptrType)
r.builder.CreateCall(fn, []llvm.Value{i8undef, i8undef}, "")
// Make sure that any globals touched by the package
// initializer, won't be accessed by later package initializers.
r.markExternalLoad(fn)
continue continue
} }
return callErr return callErr
} }
call.EraseFromParentAsInstruction()
for index, obj := range mem.objects { for index, obj := range mem.objects {
r.objects[index] = obj r.objects[index] = obj
} }
@@ -138,7 +129,7 @@ func Run(mod llvm.Module, debug bool) error {
// Update all global variables in the LLVM module. // Update all global variables in the LLVM module.
mem := memoryView{r: r} mem := memoryView{r: r}
for i, obj := range r.objects { for _, obj := range r.objects {
if obj.llvmGlobal.IsNil() { if obj.llvmGlobal.IsNil() {
continue continue
} }
@@ -168,12 +159,6 @@ func Run(mod llvm.Module, debug bool) error {
name := obj.llvmGlobal.Name() name := obj.llvmGlobal.Name()
obj.llvmGlobal.EraseFromParentAsGlobal() obj.llvmGlobal.EraseFromParentAsGlobal()
newGlobal.SetName(name) newGlobal.SetName(name)
// Update interp-internal references.
delete(r.globals, obj.llvmGlobal)
obj.llvmGlobal = newGlobal
r.globals[newGlobal] = i
r.objects[i] = obj
continue continue
} }
if err != nil { if err != nil {
@@ -279,19 +264,3 @@ func (r *runner) getFunction(llvmFn llvm.Value) *function {
r.functionCache[llvmFn] = fn r.functionCache[llvmFn] = fn
return fn return fn
} }
// markExternalLoad marks the given llvmValue as being loaded externally. This
// is primarily used to mark package initializers that could not be run at
// compile time. As an example, a package initialize might store to a global
// variable. Another package initializer might read from the same global
// variable. By marking this function as being run at runtime, that load
// instruction will need to be run at runtime instead of at compile time.
func (r *runner) markExternalLoad(llvmValue llvm.Value) {
mem := memoryView{r: r}
mem.markExternalLoad(llvmValue)
for index, obj := range mem.objects {
if obj.marked > r.objects[index].marked {
r.objects[index].marked = obj.marked
}
}
}
+13 -3
View File
@@ -3,6 +3,7 @@ package interp
import ( import (
"io/ioutil" "io/ioutil"
"os" "os"
"regexp"
"strings" "strings"
"testing" "testing"
@@ -12,12 +13,10 @@ import (
func TestInterp(t *testing.T) { func TestInterp(t *testing.T) {
for _, name := range []string{ for _, name := range []string{
"basic", "basic",
"phi",
"slice-copy", "slice-copy",
"consteval", "consteval",
"map",
"interface", "interface",
"revert",
"alloc",
} { } {
name := name // make tc local to this closure name := name // make tc local to this closure
t.Run(name, func(t *testing.T) { t.Run(name, func(t *testing.T) {
@@ -88,6 +87,8 @@ func runTest(t *testing.T, pathPrefix string) {
} }
} }
var alignRegexp = regexp.MustCompile(", align [0-9]+$")
// fuzzyEqualIR returns true if the two LLVM IR strings passed in are roughly // fuzzyEqualIR returns true if the two LLVM IR strings passed in are roughly
// equal. That means, only relevant lines are compared (excluding comments // equal. That means, only relevant lines are compared (excluding comments
// etc.). // etc.).
@@ -99,6 +100,15 @@ func fuzzyEqualIR(s1, s2 string) bool {
} }
for i, line1 := range lines1 { for i, line1 := range lines1 {
line2 := lines2[i] line2 := lines2[i]
match1 := alignRegexp.MatchString(line1)
match2 := alignRegexp.MatchString(line2)
if match1 != match2 {
// Only one of the lines has the align keyword. Remove it.
// This is a change to make the test work in both LLVM 10 and LLVM
// 11 (LLVM 11 appears to automatically add alignment everywhere).
line1 = alignRegexp.ReplaceAllString(line1, "")
line2 = alignRegexp.ReplaceAllString(line2, "")
}
if line1 != line2 { if line1 != line2 {
return false return false
} }
+117 -113
View File
@@ -5,7 +5,6 @@ import (
"fmt" "fmt"
"math" "math"
"os" "os"
"strconv"
"strings" "strings"
"time" "time"
@@ -34,50 +33,6 @@ func (r *runner) run(fn *function, params []value, parentMem *memoryView, indent
lastBB := -1 // last basic block is undefined, only defined after a branch lastBB := -1 // last basic block is undefined, only defined after a branch
var operands []value var operands []value
for instIndex := 0; instIndex < len(bb.instructions); instIndex++ { for instIndex := 0; instIndex < len(bb.instructions); instIndex++ {
if instIndex == 0 {
// This is the start of a new basic block.
// There may be PHI nodes that need to be resolved. Resolve all PHI
// nodes before continuing with regular instructions.
// PHI nodes need to be treated specially because they can have a
// mutual dependency:
// for.loop:
// %a = phi i8 [ 1, %entry ], [ %b, %for.loop ]
// %b = phi i8 [ 3, %entry ], [ %a, %for.loop ]
// If these PHI nodes are processed like a regular instruction, %a
// and %b are both 3 on the second iteration of the loop because %b
// loads the value of %a from the second iteration, while it should
// load the value from the previous iteration. The correct behavior
// is that these two values swap each others place on each
// iteration.
var phiValues []value
var phiIndices []int
for _, inst := range bb.phiNodes {
var result value
for i := 0; i < len(inst.operands); i += 2 {
if int(inst.operands[i].(literalValue).value.(uint32)) == lastBB {
incoming := inst.operands[i+1]
if local, ok := incoming.(localValue); ok {
result = locals[fn.locals[local.value]]
} else {
result = incoming
}
break
}
}
if r.debug {
fmt.Fprintln(os.Stderr, indent+"phi", inst.operands, "->", result)
}
if result == nil {
panic("could not find PHI input")
}
phiValues = append(phiValues, result)
phiIndices = append(phiIndices, inst.localIndex)
}
for i, value := range phiValues {
locals[phiIndices[i]] = value
}
}
inst := bb.instructions[instIndex] inst := bb.instructions[instIndex]
operands = operands[:0] operands = operands[:0]
isRuntimeInst := false isRuntimeInst := false
@@ -148,24 +103,27 @@ func (r *runner) run(fn *function, params []value, parentMem *memoryView, indent
default: default:
panic("unknown operands length") panic("unknown operands length")
} }
case llvm.Switch: break // continue with next block
// Switch statement: [value, defaultLabel, case0, label0, case1, label1, ...] case llvm.PHI:
value := operands[0].Uint() var result value
targetLabel := operands[1].Uint() // default label for i := 0; i < len(inst.operands); i += 2 {
// Do a lazy switch by iterating over all cases. if int(inst.operands[i].(literalValue).value.(uint32)) == lastBB {
for i := 2; i < len(operands); i += 2 { incoming := inst.operands[i+1]
if value == operands[i].Uint() { if local, ok := incoming.(localValue); ok {
targetLabel = operands[i+1].Uint() result = locals[fn.locals[local.value]]
} else {
result = incoming
}
break break
} }
} }
lastBB = currentBB
currentBB = int(targetLabel)
bb = fn.blocks[currentBB]
instIndex = -1 // start at 0 the next cycle
if r.debug { if r.debug {
fmt.Fprintln(os.Stderr, indent+"switch", operands, "->", currentBB) fmt.Fprintln(os.Stderr, indent+"phi", inst.operands, "->", result)
} }
if result == nil {
panic("could not find PHI input")
}
locals[inst.localIndex] = result
case llvm.Select: case llvm.Select:
// Select is much like a ternary operator: it picks a result from // Select is much like a ternary operator: it picks a result from
// the second and third operand based on the boolean first operand. // the second and third operand based on the boolean first operand.
@@ -197,7 +155,7 @@ func (r *runner) run(fn *function, params []value, parentMem *memoryView, indent
// which case this call won't even get to this point but will // which case this call won't even get to this point but will
// already be emitted in initAll. // already be emitted in initAll.
continue continue
case strings.HasPrefix(callFn.name, "runtime.print") || callFn.name == "runtime._panic" || callFn.name == "runtime.hashmapGet" || callFn.name == "os.runtime_args": case strings.HasPrefix(callFn.name, "runtime.print") || callFn.name == "runtime._panic" || callFn.name == "runtime.hashmapGet":
// These functions should be run at runtime. Specifically: // These functions should be run at runtime. Specifically:
// * Print and panic functions are best emitted directly without // * Print and panic functions are best emitted directly without
// interpreting them, otherwise we get a ton of putchar (etc.) // interpreting them, otherwise we get a ton of putchar (etc.)
@@ -205,9 +163,6 @@ func (r *runner) run(fn *function, params []value, parentMem *memoryView, indent
// * runtime.hashmapGet tries to access the map value directly. // * runtime.hashmapGet tries to access the map value directly.
// This is not possible as the map value is treated as a special // This is not possible as the map value is treated as a special
// kind of object in this package. // kind of object in this package.
// * os.runtime_args reads globals that are initialized outside
// the view of the interp package so it always needs to be run
// at runtime.
err := r.runAtRuntime(fn, inst, locals, &mem, indent) err := r.runAtRuntime(fn, inst, locals, &mem, indent)
if err != nil { if err != nil {
return nil, mem, err return nil, mem, err
@@ -234,15 +189,11 @@ func (r *runner) run(fn *function, params []value, parentMem *memoryView, indent
// Get the requested memory size to be allocated. // Get the requested memory size to be allocated.
size := operands[1].Uint() size := operands[1].Uint()
// Get the object layout, if it is available.
llvmLayoutType := r.getLLVMTypeFromLayout(operands[2])
// Create the object. // Create the object.
alloc := object{ alloc := object{
globalName: r.pkgName + "$alloc", globalName: r.pkgName + "$alloc",
llvmLayoutType: llvmLayoutType, buffer: newRawValue(uint32(size)),
buffer: newRawValue(uint32(size)), size: uint32(size),
size: uint32(size),
} }
index := len(r.objects) index := len(r.objects)
r.objects = append(r.objects, alloc) r.objects = append(r.objects, alloc)
@@ -377,12 +328,12 @@ func (r *runner) run(fn *function, params []value, parentMem *memoryView, indent
return nil, mem, r.errorAt(inst, err) return nil, mem, r.errorAt(inst, err)
} }
actualType := actualTypePtrToInt.Operand(0) actualType := actualTypePtrToInt.Operand(0)
if strings.TrimPrefix(actualType.Name(), "reflect/types.type:") == strings.TrimPrefix(assertedType.Name(), "reflect/types.typeid:") { if actualType.Name()+"$id" == assertedType.Name() {
locals[inst.localIndex] = literalValue{uint8(1)} locals[inst.localIndex] = literalValue{uint8(1)}
} else { } else {
locals[inst.localIndex] = literalValue{uint8(0)} locals[inst.localIndex] = literalValue{uint8(0)}
} }
case strings.HasSuffix(callFn.name, ".$typeassert"): case callFn.name == "runtime.interfaceImplements":
if r.debug { if r.debug {
fmt.Fprintln(os.Stderr, indent+"interface assert:", operands[1:]) fmt.Fprintln(os.Stderr, indent+"interface assert:", operands[1:])
} }
@@ -397,9 +348,11 @@ func (r *runner) run(fn *function, params []value, parentMem *memoryView, indent
return nil, mem, r.errorAt(inst, err) return nil, mem, r.errorAt(inst, err)
} }
methodSet := mem.get(methodSetPtr.index()).llvmGlobal.Initializer() methodSet := mem.get(methodSetPtr.index()).llvmGlobal.Initializer()
llvmFn := inst.llvmInst.CalledValue() interfaceMethodSetPtr, err := operands[2].asPointer(r)
methodSetAttr := llvmFn.GetStringAttributeAtIndex(-1, "tinygo-methods") if err != nil {
methodSetString := methodSetAttr.GetStringValue() return nil, mem, r.errorAt(inst, err)
}
interfaceMethodSet := mem.get(interfaceMethodSetPtr.index()).llvmGlobal.Initializer()
// Make a set of all the methods on the concrete type, for // Make a set of all the methods on the concrete type, for
// easier checking in the next step. // easier checking in the next step.
@@ -414,7 +367,8 @@ func (r *runner) run(fn *function, params []value, parentMem *memoryView, indent
// of defined methods calculated above. This is the interface // of defined methods calculated above. This is the interface
// assert itself. // assert itself.
assertOk := uint8(1) // i1 true assertOk := uint8(1) // i1 true
for _, name := range strings.Split(methodSetString, "; ") { for i := 0; i < interfaceMethodSet.Type().ArrayLength(); i++ {
name := llvm.ConstExtractValue(interfaceMethodSet, []uint32{uint32(i)}).Name()
if _, ok := concreteTypeMethods[name]; !ok { if _, ok := concreteTypeMethods[name]; !ok {
// There is a method on the interface that is not // There is a method on the interface that is not
// implemented by the type. The assertion will fail. // implemented by the type. The assertion will fail.
@@ -424,20 +378,20 @@ func (r *runner) run(fn *function, params []value, parentMem *memoryView, indent
} }
// If assertOk is still 1, the assertion succeeded. // If assertOk is still 1, the assertion succeeded.
locals[inst.localIndex] = literalValue{assertOk} locals[inst.localIndex] = literalValue{assertOk}
case strings.HasSuffix(callFn.name, "$invoke"): case callFn.name == "runtime.interfaceMethod":
// This thunk is the interface method dispatcher: it is called // This builtin returns the function (which may be a thunk) to
// with all regular parameters and a type code. It will then // invoke a method on an interface. It does not call the method.
// call the concrete method for it.
if r.debug { if r.debug {
fmt.Fprintln(os.Stderr, indent+"invoke method:", operands[1:]) fmt.Fprintln(os.Stderr, indent+"interface method:", operands[1:])
} }
// Load the type code of the interface value. // Load the first param, which is the type code (ptrtoint of the
typecodeIDBitCast, err := operands[len(operands)-3].toLLVMValue(inst.llvmInst.Operand(len(operands)-4).Type(), &mem) // type code global).
typecodeIDPtrToInt, err := operands[1].toLLVMValue(inst.llvmInst.Operand(0).Type(), &mem)
if err != nil { if err != nil {
return nil, mem, r.errorAt(inst, err) return nil, mem, r.errorAt(inst, err)
} }
typecodeID := typecodeIDBitCast.Operand(0).Initializer() typecodeID := typecodeIDPtrToInt.Operand(0).Initializer()
// Load the method set, which is part of the typecodeID object. // Load the method set, which is part of the typecodeID object.
methodSet := llvm.ConstExtractValue(typecodeID, []uint32{2}).Operand(0).Initializer() methodSet := llvm.ConstExtractValue(typecodeID, []uint32{2}).Operand(0).Initializer()
@@ -445,10 +399,7 @@ func (r *runner) run(fn *function, params []value, parentMem *memoryView, indent
// We don't need to load the interface method set. // We don't need to load the interface method set.
// Load the signature of the to-be-called function. // Load the signature of the to-be-called function.
llvmFn := inst.llvmInst.CalledValue() signature := inst.llvmInst.Operand(2)
invokeAttr := llvmFn.GetStringAttributeAtIndex(-1, "tinygo-invoke")
invokeName := invokeAttr.GetStringValue()
signature := r.mod.NamedGlobal(invokeName)
// Iterate through all methods, looking for the one method that // Iterate through all methods, looking for the one method that
// should be returned. // should be returned.
@@ -461,13 +412,77 @@ func (r *runner) run(fn *function, params []value, parentMem *memoryView, indent
} }
} }
if method.IsNil() { if method.IsNil() {
return nil, mem, r.errorAt(inst, errors.New("could not find method: "+invokeName)) return nil, mem, r.errorAt(inst, errors.New("could not find method: "+signature.Name()))
} }
locals[inst.localIndex] = r.getValue(method)
case callFn.name == "runtime.hashmapMake":
// Create a new map.
hashmapPointerType := inst.llvmInst.Type()
keySize := uint32(operands[1].Uint())
valueSize := uint32(operands[2].Uint())
m := newMapValue(r, hashmapPointerType, keySize, valueSize)
alloc := object{
llvmType: hashmapPointerType,
globalName: r.pkgName + "$map",
buffer: m,
size: m.len(r),
}
index := len(r.objects)
r.objects = append(r.objects, alloc)
// Change the to-be-called function to the underlying method to // Create a pointer to this map. Maps are reference types, so
// be called and fall through to the default case. // are implemented as pointers.
callFn = r.getFunction(method) ptr := newPointerValue(r, index, 0)
fallthrough if r.debug {
fmt.Fprintln(os.Stderr, indent+"runtime.hashmapMake:", keySize, valueSize, "->", ptr)
}
locals[inst.localIndex] = ptr
case callFn.name == "runtime.hashmapBinarySet":
// Do a mapassign operation with a binary key (that is, without
// a string key).
if r.debug {
fmt.Fprintln(os.Stderr, indent+"runtime.hashmapBinarySet:", operands[1:])
}
mapPtr, err := operands[1].asPointer(r)
if err != nil {
return nil, mem, r.errorAt(inst, err)
}
m := mem.getWritable(mapPtr.index()).buffer.(*mapValue)
keyPtr, err := operands[2].asPointer(r)
if err != nil {
return nil, mem, r.errorAt(inst, err)
}
valuePtr, err := operands[3].asPointer(r)
if err != nil {
return nil, mem, r.errorAt(inst, err)
}
err = m.putBinary(&mem, keyPtr, valuePtr)
if err != nil {
return nil, mem, r.errorAt(inst, err)
}
case callFn.name == "runtime.hashmapStringSet":
// Do a mapassign operation with a string key.
if r.debug {
fmt.Fprintln(os.Stderr, indent+"runtime.hashmapBinarySet:", operands[1:])
}
mapPtr, err := operands[1].asPointer(r)
if err != nil {
return nil, mem, r.errorAt(inst, err)
}
m := mem.getWritable(mapPtr.index()).buffer.(*mapValue)
stringPtr, err := operands[2].asPointer(r)
if err != nil {
return nil, mem, r.errorAt(inst, err)
}
stringLen := operands[3].Uint()
valuePtr, err := operands[4].asPointer(r)
if err != nil {
return nil, mem, r.errorAt(inst, err)
}
err = m.putString(&mem, stringPtr, stringLen, valuePtr)
if err != nil {
return nil, mem, r.errorAt(inst, err)
}
default: default:
if len(callFn.blocks) == 0 { if len(callFn.blocks) == 0 {
// Call to a function declaration without a definition // Call to a function declaration without a definition
@@ -598,14 +613,18 @@ func (r *runner) run(fn *function, params []value, parentMem *memoryView, indent
// GetElementPtr does pointer arithmetic, changing the offset of the // GetElementPtr does pointer arithmetic, changing the offset of the
// pointer into the underlying object. // pointer into the underlying object.
var offset uint64 var offset uint64
var gepOperands []uint64
for i := 2; i < len(operands); i += 2 { for i := 2; i < len(operands); i += 2 {
index := operands[i].Uint() index := operands[i].Uint()
elementSize := operands[i+1].Uint() elementSize := operands[i+1].Uint()
if int64(elementSize) < 0 { if int64(elementSize) < 0 {
// This is a struct field. // This is a struct field.
// The field number is encoded by flipping all the bits.
gepOperands = append(gepOperands, ^elementSize)
offset += index offset += index
} else { } else {
// This is a normal GEP, probably an array index. // This is a normal GEP, probably an array index.
gepOperands = append(gepOperands, index)
offset += elementSize * index offset += elementSize * index
} }
} }
@@ -935,31 +954,16 @@ func (r *runner) runAtRuntime(fn *function, inst instruction, locals []value, me
result = r.builder.CreateBitCast(operands[0], inst.llvmInst.Type(), inst.name) result = r.builder.CreateBitCast(operands[0], inst.llvmInst.Type(), inst.name)
case llvm.ExtractValue: case llvm.ExtractValue:
indices := inst.llvmInst.Indices() indices := inst.llvmInst.Indices()
// Note: the Go LLVM API doesn't support multiple indices, so simulate if len(indices) != 1 {
// this operation with some extra extractvalue instructions. Hopefully panic("expected exactly one index")
// this is optimized to a single instruction.
agg := operands[0]
for i := 0; i < len(indices)-1; i++ {
agg = r.builder.CreateExtractValue(agg, int(indices[i]), inst.name+".agg")
} }
result = r.builder.CreateExtractValue(agg, int(indices[len(indices)-1]), inst.name) result = r.builder.CreateExtractValue(operands[0], int(indices[0]), inst.name)
case llvm.InsertValue: case llvm.InsertValue:
indices := inst.llvmInst.Indices() indices := inst.llvmInst.Indices()
// Similar to extractvalue, we're working around a limitation in the Go if len(indices) != 1 {
// LLVM API here by splitting the insertvalue into multiple instructions panic("expected exactly one index")
// if there is more than one operand.
agg := operands[0]
aggregates := []llvm.Value{agg}
for i := 0; i < len(indices)-1; i++ {
agg = r.builder.CreateExtractValue(agg, int(indices[i]), inst.name+".agg"+strconv.Itoa(i))
aggregates = append(aggregates, agg)
} }
result = operands[1] result = r.builder.CreateInsertValue(operands[0], operands[1], int(indices[0]), inst.name)
for i := len(indices) - 1; i >= 0; i-- {
agg := aggregates[i]
result = r.builder.CreateInsertValue(agg, result, int(indices[i]), inst.name+".insertvalue"+strconv.Itoa(i))
}
case llvm.Add: case llvm.Add:
result = r.builder.CreateAdd(operands[0], operands[1], inst.name) result = r.builder.CreateAdd(operands[0], operands[1], inst.name)
case llvm.Sub: case llvm.Sub:

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