Compare commits

..

3 Commits

Author SHA1 Message Date
Ayke van Laethem 6272973d3f optimize check function 2019-09-15 19:58:23 +02:00
Jaden Weiss fee496ae79 fix bugs found by LLVM assertions 2019-09-15 12:51:54 -04:00
Jaden Weiss d975ed9373 fix incorrect typing of globals bitmap 2019-09-12 16:42:37 -04:00
456 changed files with 9941 additions and 30028 deletions
+52 -160
View File
@@ -14,16 +14,19 @@ commands:
- run:
name: "Install apt dependencies"
command: |
echo 'deb https://apt.llvm.org/buster/ llvm-toolchain-buster-<<parameters.llvm>> main' | sudo tee /etc/apt/sources.list.d/llvm.list
echo 'deb http://apt.llvm.org/stretch/ llvm-toolchain-stretch<<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 -
sudo apt-get update
sudo apt-get install \
llvm-<<parameters.llvm>>-dev \
clang-<<parameters.llvm>> \
libclang-<<parameters.llvm>>-dev \
lld-<<parameters.llvm>> \
python3 \
llvm<<parameters.llvm>>-dev \
clang<<parameters.llvm>> \
libclang<<parameters.llvm>>-dev \
lld<<parameters.llvm>> \
gcc-arm-linux-gnueabihf \
libc6-dev-armel-cross \
gcc-aarch64-linux-gnu \
libc6-dev-arm64-cross \
qemu-system-arm \
qemu-user \
gcc-avr \
@@ -41,119 +44,36 @@ commands:
steps:
- restore_cache:
keys:
- llvm-source-10-v0
- llvm-source-8-v5
- run:
name: "Fetch LLVM source"
command: make llvm-source
- save_cache:
key: llvm-source-10-v0
key: llvm-source-8-v5
paths:
- llvm-project
build-wasi-libc:
steps:
- restore_cache:
keys:
- wasi-libc-sysroot-v2
- run:
name: "Build wasi-libc"
command: make wasi-libc
- save_cache:
key: wasi-libc-sysroot-v2
paths:
- lib/wasi-libc/sysroot
test-linux:
parameters:
llvm:
type: string
steps:
- checkout
- submodules
- apt-dependencies:
llvm: "<<parameters.llvm>>"
llvm: "-8"
- install-node
- restore_cache:
keys:
- go-cache-v2-{{ checksum "go.mod" }}-{{ .Environment.CIRCLE_PREVIOUS_BUILD_NUM }}
- go-cache-v2-{{ checksum "go.mod" }}
- go-cache-{{ checksum "Gopkg.lock" }}-{{ .Environment.CIRCLE_PREVIOUS_BUILD_NUM }}
- go-cache-{{ checksum "Gopkg.lock" }}
- llvm-source-linux
- run: go install -tags=llvm<<parameters.llvm>> .
- restore_cache:
keys:
- wasi-libc-sysroot-systemclang-v1
- run: make wasi-libc
- save_cache:
key: wasi-libc-sysroot-systemclang-v1
paths:
- lib/wasi-libc/sysroot
- run: go test -v -tags=llvm<<parameters.llvm>> ./cgo ./compileopts ./interp ./transform .
- run: go install .
- run: go test -v
- run: make gen-device -j4
- run: make smoketest
- run: make smoketest RISCV=0
- save_cache:
key: go-cache-v2-{{ checksum "go.mod" }}-{{ .Environment.CIRCLE_BUILD_NUM }}
key: go-cache-{{ checksum "Gopkg.lock" }}-{{ .Environment.CIRCLE_BUILD_NUM }}
paths:
- ~/.cache/go-build
- ~/.cache/tinygo
- /go/pkg/mod
- run: make fmt-check
assert-test-linux:
steps:
- checkout
- submodules
- run:
name: "Install apt dependencies"
command: |
sudo apt-get install \
gcc-arm-linux-gnueabihf \
binutils-arm-none-eabi \
libc6-dev-armel-cross \
gcc-aarch64-linux-gnu \
libc6-dev-arm64-cross \
qemu-system-arm \
qemu-user \
gcc-avr \
avr-libc
- install-node
- restore_cache:
keys:
- go-cache-v2-{{ checksum "go.mod" }}-{{ .Environment.CIRCLE_PREVIOUS_BUILD_NUM }}
- go-cache-v2-{{ checksum "go.mod" }}
- llvm-source-linux
- restore_cache:
keys:
- llvm-build-10-linux-v0-assert
- run:
name: "Build LLVM"
command: |
if [ ! -f llvm-build/lib/liblldELF.a ]
then
# install dependencies
sudo apt-get install cmake clang ninja-build
# make build faster
export CC=clang
export CXX=clang++
# hack ninja to use less jobs
echo -e '#!/bin/sh\n/usr/bin/ninja -j3 "$@"' > /go/bin/ninja
chmod +x /go/bin/ninja
# build!
make ASSERT=1 llvm-build
fi
- save_cache:
key: llvm-build-10-linux-v0-assert
paths:
llvm-build
- run: make ASSERT=1
- build-wasi-libc
- run:
name: "Test TinyGo"
command: make ASSERT=1 test
- save_cache:
key: go-cache-v2-{{ checksum "go.mod" }}-{{ .Environment.CIRCLE_BUILD_NUM }}
paths:
- ~/.cache/go-build
- ~/.cache/tinygo
- /go/pkg/mod
- run: make gen-device -j4
- run: make smoketest TINYGO=build/tinygo
build-linux:
steps:
- checkout
@@ -162,6 +82,7 @@ commands:
name: "Install apt dependencies"
command: |
sudo apt-get install \
python3 \
gcc-arm-linux-gnueabihf \
binutils-arm-none-eabi \
libc6-dev-armel-cross \
@@ -174,12 +95,12 @@ commands:
- install-node
- restore_cache:
keys:
- go-cache-v2-{{ checksum "go.mod" }}-{{ .Environment.CIRCLE_PREVIOUS_BUILD_NUM }}
- go-cache-v2-{{ checksum "go.mod" }}
- go-cache-{{ checksum "Gopkg.lock" }}-{{ .Environment.CIRCLE_PREVIOUS_BUILD_NUM }}
- go-cache-{{ checksum "Gopkg.lock" }}
- llvm-source-linux
- restore_cache:
keys:
- llvm-build-10-linux-v0
- llvm-build-8-linux-v7
- run:
name: "Build LLVM"
command: |
@@ -197,10 +118,15 @@ commands:
make llvm-build
fi
- save_cache:
key: llvm-build-10-linux-v0
key: llvm-build-8-linux-v7
paths:
llvm-build
- build-wasi-libc
- run:
name: "Create LLVM symlinks"
command: |
ln -s $PWD/llvm-build/bin/clang-8 /go/bin/clang-8
ln -s $PWD/llvm-build/bin/ld.lld /go/bin/ld.lld-8
ln -s $PWD/llvm-build/bin/wasm-ld /go/bin/wasm-ld-8
- run:
name: "Test TinyGo"
command: make test
@@ -212,11 +138,10 @@ commands:
- store_artifacts:
path: /tmp/tinygo.linux-amd64.tar.gz
- save_cache:
key: go-cache-v2-{{ checksum "go.mod" }}-{{ .Environment.CIRCLE_BUILD_NUM }}
key: go-cache-{{ checksum "Gopkg.lock" }}-{{ .Environment.CIRCLE_BUILD_NUM }}
paths:
- ~/.cache/go-build
- ~/.cache/tinygo
- /go/pkg/mod
- run:
name: "Extract release tarball"
command: |
@@ -224,6 +149,11 @@ commands:
tar -C ~/lib -xf /tmp/tinygo.linux-amd64.tar.gz
ln -s ~/lib/tinygo/bin/tinygo /go/bin/tinygo
tinygo version
- run:
name: "Download SiFive GNU toolchain"
command: |
curl -O https://static.dev.sifive.com/dev-tools/riscv64-unknown-elf-gcc-8.2.0-2019.05.3-x86_64-linux-ubuntu14.tar.gz
sudo tar -C /usr/local --strip-components=1 -xf riscv64-unknown-elf-gcc-8.2.0-2019.05.3-x86_64-linux-ubuntu14.tar.gz
- run: make smoketest
build-macos:
steps:
@@ -232,27 +162,23 @@ commands:
- run:
name: "Install dependencies"
command: |
curl https://dl.google.com/go/go1.14.darwin-amd64.tar.gz -o go1.14.darwin-amd64.tar.gz
sudo tar -C /usr/local -xzf go1.14.darwin-amd64.tar.gz
curl https://dl.google.com/go/go1.12.5.darwin-amd64.tar.gz -o go1.12.5.darwin-amd64.tar.gz
sudo tar -C /usr/local -xzf go1.12.5.darwin-amd64.tar.gz
ln -s /usr/local/go/bin/go /usr/local/bin/go
HOMEBREW_NO_AUTO_UPDATE=1 brew install qemu
- restore_cache:
keys:
- go-cache-macos-v2-{{ checksum "go.mod" }}-{{ .Environment.CIRCLE_PREVIOUS_BUILD_NUM }}
- go-cache-macos-v2-{{ checksum "go.mod" }}
- restore_cache:
keys:
- llvm-source-10-macos-v0
- llvm-source-8-macos-v5
- run:
name: "Fetch LLVM source"
command: make llvm-source
- save_cache:
key: llvm-source-10-macos-v0
key: llvm-source-8-macos-v5
paths:
- llvm-project
- restore_cache:
keys:
- llvm-build-10-macos-v0
- llvm-build-8-macos-v6
- run:
name: "Build LLVM"
command: |
@@ -264,19 +190,13 @@ commands:
make llvm-build
fi
- save_cache:
key: llvm-build-10-macos-v0
key: llvm-build-8-macos-v6
paths:
llvm-build
- restore_cache:
keys:
- wasi-libc-sysroot-macos-v1
- run:
name: "Build wasi-libc"
command: make wasi-libc
- save_cache:
key: wasi-libc-sysroot-macos-v1
paths:
- lib/wasi-libc/sysroot
name: "Create LLVM symlinks"
command: |
ln -s $PWD/llvm-build/bin/clang-8 /usr/local/bin/clang-8
- run:
name: "Test TinyGo"
command: make test
@@ -300,47 +220,22 @@ commands:
curl -O https://static.dev.sifive.com/dev-tools/riscv64-unknown-elf-gcc-8.2.0-2019.05.3-x86_64-apple-darwin.tar.gz
sudo tar -C /usr/local --strip-components=1 -xf riscv64-unknown-elf-gcc-8.2.0-2019.05.3-x86_64-apple-darwin.tar.gz
- run: make smoketest AVR=0
- save_cache:
key: go-cache-macos-v2-{{ checksum "go.mod" }}-{{ .Environment.CIRCLE_BUILD_NUM }}
paths:
- ~/.cache/go-build
- ~/.cache/tinygo
- /go/pkg/mod
jobs:
test-llvm9-go111:
test-llvm8-go111:
docker:
- image: circleci/golang:1.11-buster
- image: circleci/golang:1.11-stretch
steps:
- test-linux:
llvm: "9"
test-llvm10-go112:
- test-linux
test-llvm8-go112:
docker:
- image: circleci/golang:1.12-buster
- image: circleci/golang:1.12-stretch
steps:
- test-linux:
llvm: "10"
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"
assert-test-linux:
docker:
- image: circleci/golang:1.14-stretch
steps:
- assert-test-linux
- test-linux
build-linux:
docker:
- image: circleci/golang:1.14-stretch
- image: circleci/golang:1.12-stretch
steps:
- build-linux
build-macos:
@@ -355,10 +250,7 @@ jobs:
workflows:
test-all:
jobs:
- test-llvm9-go111
- test-llvm10-go112
- test-llvm10-go113
- test-llvm10-go114
- test-llvm8-go111
- test-llvm8-go112
- build-linux
- build-macos
- assert-test-linux
+1 -1
View File
@@ -12,5 +12,5 @@ src/device/sifive/*.s
src/device/stm32/*.go
src/device/stm32/*.s
vendor
llvm
llvm-build
llvm-project
+1 -7
View File
@@ -9,14 +9,8 @@
url = https://github.com/avr-rust/avr-mcu.git
[submodule "lib/cmsis-svd"]
path = lib/cmsis-svd
url = https://github.com/posborne/cmsis-svd
url = https://github.com/tinygo-org/cmsis-svd
[submodule "lib/compiler-rt"]
path = lib/compiler-rt
url = https://github.com/llvm-mirror/compiler-rt.git
branch = release_80
[submodule "lib/wasi-libc"]
path = lib/wasi-libc
url = https://github.com/CraneStation/wasi-libc
[submodule "lib/picolibc"]
path = lib/picolibc
url = https://github.com/keith-packard/picolibc.git
+4 -7
View File
@@ -16,6 +16,7 @@ LLVM, Clang and LLD are quite light on dependencies, requiring only standard
build tools to be built. Go is of course necessary to build TinyGo itself.
* Go (1.11+)
* [dep](https://golang.github.io/dep/)
* Standard build tools (gcc/clang)
* git
* CMake
@@ -27,19 +28,15 @@ on a different system like Mac.
## Download the source
The first step is to download the TinyGo sources (use `--recursive` if you clone
the git repository). Then, inside the directory, download the LLVM source:
the git repository). Then, inside the directory, perform these steps:
make llvm-source
dep ensure -vendor-only # download Go dependencies
make llvm-source # download LLVM
You can also store LLVM outside of the TinyGo root directory by setting the
`LLVM_BUILDDIR`, `CLANG_SRC` and `LLD_SRC` make variables, but that is not
covered by this guide.
TinyGo uses Go modules, so if you clone TinyGo inside your GOPATH (and are using
Go below 1.13), make sure that Go modules are enabled:
export GO111MODULE=on
## Build LLVM, Clang, LLD
Before starting the build, you may want to set the following environment
-329
View File
@@ -1,332 +1,3 @@
0.13.1
---
* **standard library**
- `runtime`: do not put scheduler and GC code in the same section
- `runtime`: copy stack scan assembly for GBA
* **boards**
- `gameboy-advance`: always use ARM mode instead of Thumb mode
0.13.0
---
* **command line**
- use `gdb-multiarch` for debugging Cortex-M chips
- support `tinygo run` with simavr
- support LLVM 10
- support Go 1.14
- retry 3 times when attempting to do a 1200-baud reset
* **compiler**
- mark the `abort` function as noreturn
- fix deferred calls to exported functions
- add debug info for local variables
- check for channel size limit
- refactor coroutine lowering
- add `dereferenceable_or_null` attribute to pointer parameters
- do not perform nil checking when indexing slices and on `unsafe.Pointer`
- remove `runtime.isnil` hack
- use LLVM builtins for runtime `memcpy`/`memmove`/`memzero` functions
- implement spec-compliant shifts on negative/overflow
- support anonymous type asserts
- track pointer result of string concatenation for GC
- track PHI nodes for GC
- add debug info to goroutine start wrappers
- optimize comparing interface values against nil
- fix miscompilation when deferring an interface call
- builder: include picolibc for most baremetal targets
- builder: run tools (clang, lld) as separate processes
- builder: use `-fshort-enums` consistently
- interp: add support for constant type asserts
- interp: better support for interface operations
- interp: include backtrace with error
- transform: do not track const globals for GC
- transform: replace panics with source locations
- transform: fix error in interface lowering pass
- transform: make coroutine lowering deterministic
- transform: fix miscompilation in func lowering
* **cgo**
- make `-I` and `-L` paths absolute
* **standard library**
- `machine`: set the USB VID and PID to the manufacturer values
- `machine`: correct USB CDC composite descriptors
- `machine`: move `errors.New` calls to globals
- `runtime`: support operations on nil maps
- `runtime`: fix copy builtin return value on AVR
- `runtime`: refactor goroutines
- `runtime`: support `-scheduler=none` on most platforms
- `runtime`: run package initialization in the main goroutine
- `runtime`: export `malloc` / `free` for use from C
- `runtime`: add garbage collector that uses an external allocator
- `runtime`: scan callee-saved registers while marking the stack
- `runtime`: remove recursion from conservative GC
- `runtime`: fix blocking select on nil channel
- `runtime/volatile`: include `ReplaceBits` method
- `sync`: implement trivial `sync.Map`
* **targets**
- `arm`: use `-fomit-frame-pointer`
- `atmega1284`: support this chip for testing purposes
- `atsamd51`: make QSPI available on all boards
- `atsamd51`: add support for ADC1
- `atsamd51`: use new interrupt registration in UART code
- `attiny`: clean up pin definitions
- `avr`: use the correct RAM start address
- `avr`: pass the correct `-mmcu` flag to the linker
- `avr`: add support for tasks scheduler (disabled by default)
- `avr`: fix linker problem with overlapping program/data areas
- `nrf`: fix typo in pin configuration options
- `nrf`: add lib/nrfx/mdk to include dirs
- `nrf52840`: implement USB-CDC
- `riscv`: implement VirtIO target and add RISC-V integration test
- `riscv`: add I2C support for the HiFive1 rev B board
- `stm32`: refactor GPIO pin handling
- `stm32`: refactor UART code
- `stm32f4`: add SPI
- `wasm`: support Go 1.14 (breaking previous versions)
- `wasm`: support `syscall/js.CopyBytesToJS`
- `wasm`: sync polyfills from Go 1.14.
* **boards**
- `arduino-mega2560`: add the Arduino Mega 2560
- `clue-alpha`: add the Adafruit CLUE Alpha
- `gameboy-advance`: enable debugging with GDB
- `particle-argon`: add the Particle Argon board
- `particle-boron`: add the Particle Boron board
- `particle-xenon`: add the Particle Xenon board
- `reelboard`: add `reelboard-s140v7` SoftDevice target
0.12.0
---
* **command line**
- add initial FreeBSD support
- remove getting a serial port in gdb subcommand
- add support for debugging through JLinkGDBServer
- fix CGo when cross compiling
- remove default port check for Digispark as micronucleus communicates directly using HID
- differentiate between various serial/USB error messages
* **builder**
- improve detection of Clang headers
* **compiler**
- fix assertion on empty interface
- don't crash when encountering `types.Invalid`
- revise defer to use heap allocations when running a variable number of times
- improve error messages for failed imports
- improve "function redeclared" error
- add globaldce pass to start of optimization pipeline
- add support for debugging globals
- implement RISC-V CSR operations as intrinsics
- add support for CGO_ENABLED environment variable
- do not emit debug info for extern globals (bugfix)
- add support for interrupts
- implement maps for arbitrary keys
- interp: error location for "unknown GEP" error
- wasm-abi: create temporary allocas in the entry block
* **cgo**
- add support for symbols in `#define`
- fix a bug in number tokenization
* **standard library**
- `machine`: avoid bytes package in USB logic
- `runtime`: fix external address declarations
- `runtime`: provide implementation for `internal/bytealg.IndexByte`
* **targets**
- `atsamd51`: fix volatile usage
- `atsamd51`: fix ADC, updating to 12-bits precision
- `atsamd51`: refactor SPI pin configuration to only look at pin numbers
- `atsamd51`: switch UART to use new pin configuration
- `atsamd51`: fix obvious bug in I2C code
- `atsamd51`: use only the necessary UART interrupts
- `atsamd51`: refactor I2C pin handling to auto-detect pin mode
- `avr`: use a garbage collector
- `fe310`: use CLINT peripheral for timekeeping
- `fe310`: add support for PLIC interrupts
- `fe310`: implement UART receive interrupts
- `riscv`: support sleeping in QEMU
- `riscv`: add bare-bones interrupt support
- `riscv`: print exception PC and code
- `wasm`: implement memcpy and memset
- `wasm`: include wasi-libc
- `wasm`: use wasi ABI for basic startup/stdout
* **boards**
- `arduino`: make avrdude command line compatible with Windows
- `arduino-nano`: add this board
- `arduino-nano33`: fix UART1 and UART2
- `circuitplay-bluefruit`: add this board
- `digispark`: add clock speed and pin mappings
- `gameboy-advance`: include compiler-rt in build
- `gameboy-advance`: implement interrupt handler
- `hifive1b`: add support for gdb subcommand
- `pyportal`: add this board
- `pyportal`: remove manual SPI pin mapping as now handled by default
0.11.0
---
* **command line**
- add support for QEMU in `gdb` subcommand
- use builtin Clang when building statically, dropping the clang-9 dependency
- search for default serial port on both macOS and Linux
- windows: support `tinygo flash` directly by using win32 wmi
* **compiler**
- add location information to the IR checker
- make reflection sidetables constant globals
- improve error locations in goroutine lowering
- interp: improve support for maps with string keys
- interp: add runtime fallback for mapassign operations
* **standard library**
- `machine`: add support for `SPI.Tx()` on play.tinygo.org
- `machine`: rename `CPU_FREQUENCY` to `CPUFrequency()`
* **targets**
- `adafruit-pybadge`: add Adafruit Pybadge
- `arduino-nano33`: allow simulation on play.tinygo.org
- `arduino-nano33`: fix default SPI pin numbers to be D13/D11/D12
- `circuitplay-express`: allow simulation on play.tinygo.org
- `hifive1-qemu`: add target for testing RISC-V bare metal in QEMU
- `riscv`: fix heap corruption due to changes in LLVM 9
- `riscv`: add support for compiler-rt
- `qemu`: rename to `cortex-m-qemu`
0.10.0
---
* **command line**
- halt GDB after flashing with `gdb` subcommand
- fix a crash when using `-ocd-output`
- add `info` subcommand
- add `-programmer` flag
* **builder**
- macos: use llvm@8 instead of just llvm in paths
- add `linkerscript` key to target JSON files
- write a symbol table when writing out the compiler-rt lib
- make Clang header detection more robust
- switch to LLVM 9
* **compiler**
- fix interface miscompilation with reflect
- fix miscompile of static goroutine calls to closures
- fix `todo: store` panic
- fix incorrect starting value for optimized allocations in a loop
- optimize coroutines on non-Cortex-M targets
- fix crash for programs which have heap allocations but never hit the GC
- add support for async interface calls
- fix inserting non-const values in a const global
- interp: improve error reporting
- interp: implement comparing ptrtoint to 0
* **cgo**
- improve diagnostics
- implement the constant parser (for `#define`) as a real parser
- rename reserved field names such as `type`
- avoid `"unsafe" imported but not used` error
- include all enums in the CGo Go AST
- add support for nested structs and unions
- implement `#cgo CFLAGS`
* **standard library**
- `reflect`: add implementation of array alignment
- `runtime`: improve scheduler performance when no goroutines are queued
- `runtime`: add blocking select
- `runtime`: implement interface equality in non-trivial cases
- `runtime`: add AdjustTimeOffset to update current time
- `runtime`: only implement CountString for required platforms
- `runtime`: use MSP/PSP registers for scheduling on Cortex-M
* **targets**
- `arm`: add system timer registers
- `atmega`: add port C GPIO support
- `atsamd21`: correct handling of pins >= 32
- `atsamd21`: i2s initialization fixes
- `atsamd51`: fix clock init code
- `atsamd51`: correct initialization for RTC
- `atsamd51`: fix pin function selection
- `atsamd51`: pin method cleanup
- `atsamd51`: allow setting pin mode for each of the SPI pins
- `atsamd51`: correct channel init and pin map for ADC based on ItsyBitsy-M4
- `feather-m4`: add Adafruit Feather M4 board
- `hifive1b`: add support for SPI1
- `hifive1b`: fix compiling in simulation
- `linux`: fix time on arm32
- `metro-m4`: add support for Adafruit Metro M4 Express Airlift board
- `metro-m4`: fixes for UART2
- `pinetime-devkit0`: add support for the PineTime dev kit
- `x9pro`: add support for this smartwatch
- `pca10040-s132v6`: add support for SoftDevice
- `pca10056-s140v7`: add support for SoftDevice
- `arduino-nano33`: added SPI1 connected to NINA-W102 chip on Arduino Nano 33 IOT
0.9.0
---
* **command line**
- implement 1200-baud UART bootloader reset when flashing boards that support
it
- flash using mass-storage device for boards that support it
- implement `tinygo env`
- add support for Windows (but not yet producing Windows binaries)
- add Go version to `tinygo env`
- update SVD files for up-to-date peripheral interfaces
* **compiler**
- add `//go:align` pragma
- fix bug related to type aliases
- add support for buffered channels
- remove incorrect reflect optimization
- implement copying slices in init interpretation
- add support for constant indices with a named type
- add support for recursive types like linked lists
- fix miscompile of function nil panics
- fix bug related to goroutines
* **standard library**
- `machine`: do not check for nil slices in `SPI.Tx`
- `reflectlite`: add support for Go 1.13
- `runtime`: implement `internal/bytealg.CountString`
- `sync`: properly handle nil `New` func in `sync.Pool`
* **targets**
- `arduino`: fix .bss section initialization
- `fe310`: implement `Pin.Get`
- `gameboy-advance`: support directly outputting .gba files
- `samd`: reduce code size by avoiding reflection
- `samd21`: do not hardcode pin numbers for peripherals
- `stm32f103`: avoid issue with `time.Sleep` less than 200µs
0.8.0
---
* **command line**
- fix parsing of beta Go versions
- check the major/minor installed version of Go before compiling
- validate `-target` flag better to not panic on an invalid target
* **compiler**
- implement full slice expression: `s[:2:4]`
- fix a crash when storing a linked list in an interface
- fix comparing struct types by making type IDs more unique
- fix some bugs in IR generation
- add support for linked lists in reflect data
- implement `[]rune` to string conversion
- implement support for `go` on func values
* **standard library**
- `reflect`: add support for named types
- `reflect`: add support for `t.Bits()`
- `reflect`: add basic support for `t.AssignableTo()`
- `reflect`: implement `t.Align()`
- `reflect`: add support for struct types
- `reflect`: fix bug in `v.IsNil` and `v.Pointer` for addressable values
- `reflect`: implement support for array types
- `reflect`: implement `t.Comparable()`
- `runtime`: implement stack-based scheduler
- `runtime`: fix bug in the sleep queue of the scheduler
- `runtime`: implement `memcpy` for Cortex-M
- `testing`: implement stub `testing.B` struct
- `testing`: add common test logging methods such as Errorf/Fatalf/Printf
* **targets**
- `386`: add support for linux/386 syscalls
- `atsamd21`: make SPI pins configurable so that multiple SPI ports can be
used
- `atsamd21`: correct issue with invalid first reading coming from ADC
- `atsamd21`: add support for reset-to-bootloader using 1200baud over USB-CDC
- `atsamd21`: make pin selection more flexible for peripherals
- `atsamd21`: fix minimum delay in `time.Sleep`
- `atsamd51`: fix minimum delay in `time.Sleep`
- `nrf`: improve SPI write-only speed, by making use of double buffering
- `stm32f103`: fix SPI frequency selection
- `stm32f103`: add machine.Pin.Get method for reading GPIO values
- `stm32f103`: allow board specific UART usage
- `nucleo-f103rb`: add support for NUCLEO-F103RB board
- `itsybitsy-m4`: add support for this board with a SAMD51 family chip
- `cortex-m`: add support for `arm.SystemReset()`
- `gameboy-advance`: add initial support for the GameBoy Advance
- `wasm`: add `//go:wasm-module` magic comment to set the wasm module name
- `wasm`: add syscall/js.valueSetIndex support
- `wasm`: add syscall/js.valueInvoke support
0.7.1
---
* **targets**
-76
View File
@@ -1,76 +0,0 @@
# Contributor Covenant Code of Conduct
## Our Pledge
In the interest of fostering an open and welcoming environment, we as
contributors and maintainers pledge to make participation in our project and
our community a harassment-free experience for everyone, regardless of age, body
size, disability, ethnicity, sex characteristics, gender identity and expression,
level of experience, education, socio-economic status, nationality, personal
appearance, race, religion, or sexual identity and orientation.
## Our Standards
Examples of behavior that contributes to creating a positive environment
include:
* Using welcoming and inclusive language
* Being respectful of differing viewpoints and experiences
* Gracefully accepting constructive criticism
* Focusing on what is best for the community
* Showing empathy towards other community members
Examples of unacceptable behavior by participants include:
* The use of sexualized language or imagery and unwelcome sexual attention or
advances
* Trolling, insulting/derogatory comments, and personal or political attacks
* Public or private harassment
* Publishing others' private information, such as a physical or electronic
address, without explicit permission
* Other conduct which could reasonably be considered inappropriate in a
professional setting
## Our Responsibilities
Project maintainers are responsible for clarifying the standards of acceptable
behavior and are expected to take appropriate and fair corrective action in
response to any instances of unacceptable behavior.
Project maintainers have the right and responsibility to remove, edit, or
reject comments, commits, code, wiki edits, issues, and other contributions
that are not aligned to this Code of Conduct, or to ban temporarily or
permanently any contributor for other behaviors that they deem inappropriate,
threatening, offensive, or harmful.
## Scope
This Code of Conduct applies within all project spaces, and it also applies when
an individual is representing the project or its community in public spaces.
Examples of representing a project or community include using an official
project e-mail address, posting via an official social media account, or acting
as an appointed representative at an online or offline event. Representation of
a project may be further defined and clarified by project maintainers.
## Enforcement
Instances of abusive, harassing, or otherwise unacceptable behavior may be
reported by contacting the project team at [conduct@tinygo.org](mailto:conduct@tinygo.org). All
complaints will be reviewed and investigated and will result in a response that
is deemed necessary and appropriate to the circumstances. The project team is
obligated to maintain confidentiality with regard to the reporter of an incident.
Further details of specific enforcement policies may be posted separately.
Project maintainers who do not follow or enforce the Code of Conduct in good
faith may face temporary or permanent repercussions as determined by other
members of the project's leadership.
## Attribution
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html
[homepage]: https://www.contributor-covenant.org
For answers to common questions about this code of conduct, see
https://www.contributor-covenant.org/faq
-1
View File
@@ -15,4 +15,3 @@ Ayke van Laethem <aykevanlaethem@gmail.com>
Daniel Esteban <conejo@conejo.me>
Loon, LLC.
Ron Evans <ron@hybridgroup.com>
Jaden Weiss <jaden@jadendw.dev>
+43 -36
View File
@@ -1,51 +1,52 @@
# TinyGo base stage installs Go 1.14, LLVM 10 and the TinyGo compiler itself.
FROM golang:1.14 AS tinygo-base
# TinyGo base stage just installs LLVM 8 and the TinyGo compiler itself.
FROM golang:latest AS tinygo-base
RUN wget -O- https://apt.llvm.org/llvm-snapshot.gpg.key| apt-key add - && \
echo "deb http://apt.llvm.org/buster/ llvm-toolchain-buster-10 main" >> /etc/apt/sources.list && \
echo "deb http://apt.llvm.org/buster/ llvm-toolchain-buster-8 main" >> /etc/apt/sources.list && \
apt-get update && \
apt-get install -y llvm-10-dev libclang-10-dev git
apt-get install -y llvm-8-dev libclang-8-dev git
COPY . /tinygo
RUN wget -O- https://raw.githubusercontent.com/golang/dep/master/install.sh | sh
COPY . /go/src/github.com/tinygo-org/tinygo
# remove submodules directories and re-init them to fix any hard-coded paths
# after copying the tinygo directory in the previous step.
RUN cd /tinygo/ && \
RUN cd /go/src/github.com/tinygo-org/tinygo/ && \
rm -rf ./lib/* && \
git submodule sync && \
git submodule update --init --recursive --force
COPY ./lib/picolibc-include/* /tinygo/lib/picolibc-include/
RUN cd /tinygo/ && \
go install /tinygo/
RUN cd /go/src/github.com/tinygo-org/tinygo/ && \
dep ensure --vendor-only && \
go install /go/src/github.com/tinygo-org/tinygo/
# tinygo-wasm stage installs the needed dependencies to compile TinyGo programs for WASM.
FROM tinygo-base AS tinygo-wasm
COPY --from=tinygo-base /go/bin/tinygo /go/bin/tinygo
COPY --from=tinygo-base /tinygo/src /tinygo/src
COPY --from=tinygo-base /tinygo/targets /tinygo/targets
COPY --from=tinygo-base /go/src/github.com/tinygo-org/tinygo/src /go/src/github.com/tinygo-org/tinygo/src
COPY --from=tinygo-base /go/src/github.com/tinygo-org/tinygo/targets /go/src/github.com/tinygo-org/tinygo/targets
RUN wget -O- https://apt.llvm.org/llvm-snapshot.gpg.key| apt-key add - && \
echo "deb http://apt.llvm.org/buster/ llvm-toolchain-buster-10 main" >> /etc/apt/sources.list && \
echo "deb http://apt.llvm.org/buster/ llvm-toolchain-buster-8 main" >> /etc/apt/sources.list && \
apt-get update && \
apt-get install -y libllvm10 lld-10
apt-get install -y libllvm8 lld-8
# tinygo-avr stage installs the needed dependencies to compile TinyGo programs for AVR microcontrollers.
FROM tinygo-base AS tinygo-avr
COPY --from=tinygo-base /go/bin/tinygo /go/bin/tinygo
COPY --from=tinygo-base /tinygo/src /tinygo/src
COPY --from=tinygo-base /tinygo/targets /tinygo/targets
COPY --from=tinygo-base /tinygo/Makefile /tinygo/
COPY --from=tinygo-base /tinygo/tools /tinygo/tools
COPY --from=tinygo-base /tinygo/lib /tinygo/lib
COPY --from=tinygo-base /go/src/github.com/tinygo-org/tinygo/src /go/src/github.com/tinygo-org/tinygo/src
COPY --from=tinygo-base /go/src/github.com/tinygo-org/tinygo/targets /go/src/github.com/tinygo-org/tinygo/targets
COPY --from=tinygo-base /go/src/github.com/tinygo-org/tinygo/Makefile /go/src/github.com/tinygo-org/tinygo/
COPY --from=tinygo-base /go/src/github.com/tinygo-org/tinygo/tools /go/src/github.com/tinygo-org/tinygo/tools
COPY --from=tinygo-base /go/src/github.com/tinygo-org/tinygo/lib /go/src/github.com/tinygo-org/tinygo/lib
RUN cd /tinygo/ && \
RUN cd /go/src/github.com/tinygo-org/tinygo/ && \
apt-get update && \
apt-get install -y apt-utils make binutils-avr gcc-avr avr-libc && \
apt-get install -y apt-utils python3 make binutils-avr gcc-avr avr-libc && \
make gen-device-avr && \
apt-get remove -y python3 && \
apt-get autoremove -y && \
apt-get clean
@@ -53,27 +54,33 @@ RUN cd /tinygo/ && \
FROM tinygo-base AS tinygo-arm
COPY --from=tinygo-base /go/bin/tinygo /go/bin/tinygo
COPY --from=tinygo-base /tinygo/src /tinygo/src
COPY --from=tinygo-base /tinygo/targets /tinygo/targets
COPY --from=tinygo-base /tinygo/Makefile /tinygo/
COPY --from=tinygo-base /tinygo/tools /tinygo/tools
COPY --from=tinygo-base /tinygo/lib /tinygo/lib
COPY --from=tinygo-base /go/src/github.com/tinygo-org/tinygo/src /go/src/github.com/tinygo-org/tinygo/src
COPY --from=tinygo-base /go/src/github.com/tinygo-org/tinygo/targets /go/src/github.com/tinygo-org/tinygo/targets
COPY --from=tinygo-base /go/src/github.com/tinygo-org/tinygo/Makefile /go/src/github.com/tinygo-org/tinygo/
COPY --from=tinygo-base /go/src/github.com/tinygo-org/tinygo/tools /go/src/github.com/tinygo-org/tinygo/tools
COPY --from=tinygo-base /go/src/github.com/tinygo-org/tinygo/lib /go/src/github.com/tinygo-org/tinygo/lib
RUN cd /tinygo/ && \
RUN cd /go/src/github.com/tinygo-org/tinygo/ && \
apt-get update && \
apt-get install -y apt-utils make clang-10 && \
make gen-device-nrf && make gen-device-stm32
apt-get install -y apt-utils python3 make clang-8 && \
make gen-device-nrf && make gen-device-stm32 && \
apt-get remove -y python3 && \
apt-get autoremove -y && \
apt-get clean
# tinygo-all stage installs the needed dependencies to compile TinyGo programs for all platforms.
FROM tinygo-wasm AS tinygo-all
COPY --from=tinygo-base /tinygo/Makefile /tinygo/
COPY --from=tinygo-base /tinygo/tools /tinygo/tools
COPY --from=tinygo-base /tinygo/lib /tinygo/lib
COPY --from=tinygo-base /go/src/github.com/tinygo-org/tinygo/Makefile /go/src/github.com/tinygo-org/tinygo/
COPY --from=tinygo-base /go/src/github.com/tinygo-org/tinygo/tools /go/src/github.com/tinygo-org/tinygo/tools
COPY --from=tinygo-base /go/src/github.com/tinygo-org/tinygo/lib /go/src/github.com/tinygo-org/tinygo/lib
RUN cd /tinygo/ && \
RUN cd /go/src/github.com/tinygo-org/tinygo/ && \
apt-get update && \
apt-get install -y apt-utils make clang-10 binutils-avr gcc-avr avr-libc && \
make gen-device
apt-get install -y apt-utils python3 make clang-8 binutils-avr gcc-avr avr-libc && \
make gen-device && \
apt-get remove -y python3 && \
apt-get autoremove -y && \
apt-get clean
CMD ["tinygo"]
Generated
+51
View File
@@ -0,0 +1,51 @@
# This file is autogenerated, do not edit; changes may be undone by the next 'dep ensure'.
[[projects]]
branch = "master"
digest = "1:06519a2ec1d59040eaccec40206f9d0b59dc662db2a032f974d6d6b9a2bcb839"
name = "github.com/blakesmith/ar"
packages = ["."]
pruneopts = "UT"
revision = "8bd4349a67f2533b078dbc524689d15dba0f4659"
[[projects]]
branch = "master"
digest = "1:00b45e06c7843541372fc17d982242bd6adfc2fc382b6f2e9ef9ce53d87a50b9"
name = "github.com/marcinbor85/gohex"
packages = ["."]
pruneopts = "UT"
revision = "7a43cd876e46e0f6ddc553f10f91731a78e6e949"
[[projects]]
branch = "master"
digest = "1:ba70784a3deee74c0ca3c87bcac3c2f93d3b2d27d8f237b768c358b45ba47da8"
name = "golang.org/x/tools"
packages = [
"go/ast/astutil",
"go/ssa",
"go/types/typeutil",
]
pruneopts = "UT"
revision = "8dcc6e70cdefe9a82236b6e195e4f4e2108fcb9f"
[[projects]]
branch = "llvm8"
digest = "1:bf5539bdf6b3cc3ec1e45926db05d81180da11ce722fa1edcce3f0b4e1967da5"
name = "tinygo.org/x/go-llvm"
packages = ["."]
pruneopts = "UT"
revision = "7707ae5d1261a8929edea7336c8087ca8b520d8d"
[solve-meta]
analyzer-name = "dep"
analyzer-version = 1
input-imports = [
"github.com/blakesmith/ar",
"github.com/marcinbor85/gohex",
"golang.org/x/tools/go/ast/astutil",
"golang.org/x/tools/go/ssa",
"tinygo.org/x/go-llvm",
]
solver-name = "gps-cdcl"
solver-version = 1
+11
View File
@@ -0,0 +1,11 @@
[[constraint]]
branch = "llvm8"
name = "tinygo.org/x/go-llvm"
[[constraint]]
branch = "master"
name = "golang.org/x/tools"
[prune]
go-tests = true
unused-packages = true
+2 -5
View File
@@ -1,10 +1,7 @@
Copyright (c) 2018-2020 TinyGo Authors. All rights reserved.
Copyright (c) 2018-2019 TinyGo Authors. All rights reserved.
TinyGo includes portions of the Go standard library.
Copyright (c) 2009-2020 The Go Authors. All rights reserved.
TinyGo includes portions of LLVM, which is under the Apache License v2.0 with
LLVM Exceptions. See https://llvm.org/LICENSE.txt for license information.
Copyright (c) 2009-2019 The Go Authors. All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
+78 -265
View File
@@ -1,117 +1,40 @@
# aliases
all: tinygo
tinygo: build/tinygo
# Default build and source directories, as created by `make llvm-build`.
LLVM_BUILDDIR ?= llvm-build
LLVM_PROJECTDIR ?= llvm-project
CLANG_SRC ?= $(LLVM_PROJECTDIR)/clang
LLD_SRC ?= $(LLVM_PROJECTDIR)/lld
CLANG_SRC ?= llvm-project/clang
LLD_SRC ?= llvm-project/lld
# Try to autodetect LLVM build tools.
ifneq (, $(shell command -v llvm-build/bin/clang 2> /dev/null))
CLANG ?= $(abspath llvm-build/bin/clang)
else
CLANG ?= clang-10
endif
ifneq (, $(shell command -v llvm-build/bin/llvm-ar 2> /dev/null))
LLVM_AR ?= $(abspath llvm-build/bin/llvm-ar)
else ifneq (, $(shell command -v llvm-ar-10 2> /dev/null))
LLVM_AR ?= llvm-ar-10
else
LLVM_AR ?= llvm-ar
endif
ifneq (, $(shell command -v llvm-build/bin/llvm-nm 2> /dev/null))
LLVM_NM ?= $(abspath llvm-build/bin/llvm-nm)
else ifneq (, $(shell command -v llvm-nm-10 2> /dev/null))
LLVM_NM ?= llvm-nm-10
else
LLVM_NM ?= llvm-nm
endif
.PHONY: all tinygo build/tinygo test $(LLVM_BUILDDIR) llvm-source clean fmt gen-device gen-device-nrf gen-device-avr
# Go binary and GOROOT to select
GO ?= go
export GOROOT = $(shell $(GO) env GOROOT)
LLVM_COMPONENTS = all-targets analysis asmparser asmprinter bitreader bitwriter codegen core coroutines debuginfodwarf executionengine instrumentation interpreter ipo irreader linker lto mc mcjit objcarcopts option profiledata scalaropts support target
# md5sum binary
MD5SUM = md5sum
# tinygo binary for tests
TINYGO ?= tinygo
# Use CCACHE for LLVM if possible
ifneq (, $(shell command -v ccache 2> /dev/null))
LLVM_OPTION += '-DLLVM_CCACHE_BUILD=ON'
endif
# Allow enabling LLVM assertions
ifeq (1, $(ASSERT))
LLVM_OPTION += '-DLLVM_ENABLE_ASSERTIONS=ON'
else
LLVM_OPTION += '-DLLVM_ENABLE_ASSERTIONS=OFF'
endif
.PHONY: all tinygo test $(LLVM_BUILDDIR) llvm-source clean fmt gen-device gen-device-nrf gen-device-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
ifeq ($(OS),Windows_NT)
EXE = .exe
START_GROUP = -Wl,--start-group
END_GROUP = -Wl,--end-group
# LLVM compiled using MinGW on Windows appears to have problems with threads.
# Without this flag, linking results in errors like these:
# libLLVMSupport.a(Threading.cpp.obj):Threading.cpp:(.text+0x55): undefined reference to `std::thread::hardware_concurrency()'
LLVM_OPTION += -DLLVM_ENABLE_THREADS=OFF
CGO_LDFLAGS += -static -static-libgcc -static-libstdc++
CGO_LDFLAGS_EXTRA += -lversion
# Build libclang manually because the CMake-based build system on Windows
# doesn't allow building libclang as a static library.
LIBCLANG_PATH = $(abspath build/libclang-custom.a)
LIBCLANG_FILES = $(abspath $(wildcard $(LLVM_BUILDDIR)/tools/clang/tools/libclang/CMakeFiles/libclang.dir/*.cpp.obj))
# Add the libclang dependency to the tinygo binary target.
tinygo: $(LIBCLANG_PATH)
test: $(LIBCLANG_PATH)
# Build libclang.
$(LIBCLANG_PATH): $(LIBCLANG_FILES)
@mkdir -p build
ar rcs $(LIBCLANG_PATH) $^
else ifeq ($(shell uname -s),Darwin)
MD5SUM = md5
LIBCLANG_PATH = $(abspath $(LLVM_BUILDDIR))/lib/libclang.a
else ifeq ($(shell uname -s),FreeBSD)
MD5SUM = md5
LIBCLANG_PATH = $(abspath $(LLVM_BUILDDIR))/lib/libclang.a
START_GROUP = -Wl,--start-group
END_GROUP = -Wl,--end-group
else
LIBCLANG_PATH = $(abspath $(LLVM_BUILDDIR))/lib/libclang.a
UNAME_S := $(shell uname -s)
ifeq ($(UNAME_S),Linux)
START_GROUP = -Wl,--start-group
END_GROUP = -Wl,--end-group
endif
CLANG_LIBS = $(START_GROUP) -lclangAnalysis -lclangARCMigrate -lclangAST -lclangASTMatchers -lclangBasic -lclangCodeGen -lclangCrossTU -lclangDriver -lclangDynamicASTMatchers -lclangEdit -lclangFormat -lclangFrontend -lclangFrontendTool -lclangHandleCXX -lclangHandleLLVM -lclangIndex -lclangLex -lclangParse -lclangRewrite -lclangRewriteFrontend -lclangSema -lclangSerialization -lclangStaticAnalyzerCheckers -lclangStaticAnalyzerCore -lclangStaticAnalyzerFrontend -lclangTooling -lclangToolingASTDiff -lclangToolingCore -lclangToolingInclusions $(END_GROUP) -lstdc++
CLANG_LIBS = $(START_GROUP) $(abspath $(LLVM_BUILDDIR))/lib/libclang.a -lclangAnalysis -lclangARCMigrate -lclangAST -lclangASTMatchers -lclangBasic -lclangCodeGen -lclangCrossTU -lclangDriver -lclangDynamicASTMatchers -lclangEdit -lclangFormat -lclangFrontend -lclangFrontendTool -lclangHandleCXX -lclangHandleLLVM -lclangIndex -lclangLex -lclangParse -lclangRewrite -lclangRewriteFrontend -lclangSema -lclangSerialization -lclangStaticAnalyzerCheckers -lclangStaticAnalyzerCore -lclangStaticAnalyzerFrontend -lclangTooling -lclangToolingASTDiff -lclangToolingCore -lclangToolingInclusions $(END_GROUP) -lstdc++
LLD_LIBS = $(START_GROUP) -llldCOFF -llldCommon -llldCore -llldDriver -llldELF -llldMachO -llldMinGW -llldReaderWriter -llldWasm -llldYAML $(END_GROUP)
# For static linking.
ifneq ("$(wildcard $(LLVM_BUILDDIR)/bin/llvm-config*)","")
CGO_CPPFLAGS=$(shell $(LLVM_BUILDDIR)/bin/llvm-config --cppflags) -I$(abspath $(LLVM_BUILDDIR))/tools/clang/include -I$(abspath $(CLANG_SRC))/include -I$(abspath $(LLD_SRC))/include
CGO_CXXFLAGS=-std=c++14
CGO_LDFLAGS+=$(LIBCLANG_PATH) -std=c++14 -L$(abspath $(LLVM_BUILDDIR)/lib) $(CLANG_LIBS) $(LLD_LIBS) $(shell $(LLVM_BUILDDIR)/bin/llvm-config --ldflags --libs --system-libs $(LLVM_COMPONENTS)) -lstdc++ $(CGO_LDFLAGS_EXTRA)
ifneq ("$(wildcard $(LLVM_BUILDDIR)/bin/llvm-config)","")
CGO_CPPFLAGS=$(shell $(LLVM_BUILDDIR)/bin/llvm-config --cppflags) -I$(abspath $(CLANG_SRC))/include -I$(abspath $(LLD_SRC))/include
CGO_CXXFLAGS=-std=c++11
CGO_LDFLAGS=-L$(LLVM_BUILDDIR)/lib $(CLANG_LIBS) $(LLD_LIBS) $(shell $(LLVM_BUILDDIR)/bin/llvm-config --ldflags --libs --system-libs $(LLVM_COMPONENTS))
endif
clean:
@rm -rf build
FMT_PATHS = ./*.go builder cgo compiler interp ir loader src/device/arm src/examples src/machine src/os src/reflect src/runtime src/sync src/syscall src/internal/reflectlite transform
FMT_PATHS = ./*.go cgo compiler interp ir loader src/device/arm src/examples src/machine src/os src/reflect src/runtime src/sync src/syscall
fmt:
@gofmt -l -w $(FMT_PATHS)
fmt-check:
@@ -121,208 +44,108 @@ fmt-check:
gen-device: gen-device-avr gen-device-nrf gen-device-sam gen-device-sifive gen-device-stm32
gen-device-avr:
$(GO) build -o ./build/gen-device-avr ./tools/gen-device-avr/
./build/gen-device-avr lib/avr/packs/atmega src/device/avr/
./build/gen-device-avr lib/avr/packs/tiny src/device/avr/
@GO111MODULE=off $(GO) fmt ./src/device/avr
./tools/gen-device-avr.py lib/avr/packs/atmega src/device/avr/
./tools/gen-device-avr.py lib/avr/packs/tiny src/device/avr/
go fmt ./src/device/avr
build/gen-device-svd: ./tools/gen-device-svd/*.go
$(GO) build -o $@ ./tools/gen-device-svd/
gen-device-nrf:
./tools/gen-device-svd.py lib/nrfx/mdk/ src/device/nrf/ --source=https://github.com/NordicSemiconductor/nrfx/tree/master/mdk
go fmt ./src/device/nrf
gen-device-nrf: build/gen-device-svd
./build/gen-device-svd -source=https://github.com/NordicSemiconductor/nrfx/tree/master/mdk lib/nrfx/mdk/ src/device/nrf/
GO111MODULE=off $(GO) fmt ./src/device/nrf
gen-device-sam:
./tools/gen-device-svd.py lib/cmsis-svd/data/Atmel/ src/device/sam/ --source=https://github.com/posborne/cmsis-svd/tree/master/data/Atmel
go fmt ./src/device/sam
gen-device-sam: build/gen-device-svd
./build/gen-device-svd -source=https://github.com/posborne/cmsis-svd/tree/master/data/Atmel lib/cmsis-svd/data/Atmel/ src/device/sam/
GO111MODULE=off $(GO) fmt ./src/device/sam
gen-device-sifive:
./tools/gen-device-svd.py lib/cmsis-svd/data/SiFive-Community/ src/device/sifive/ --source=https://github.com/AdaCore/svd2ada/tree/master/CMSIS-SVD/SiFive-Community
go fmt ./src/device/sifive
gen-device-sifive: build/gen-device-svd
./build/gen-device-svd -source=https://github.com/posborne/cmsis-svd/tree/master/data/SiFive-Community -interrupts=software lib/cmsis-svd/data/SiFive-Community/ src/device/sifive/
GO111MODULE=off $(GO) fmt ./src/device/sifive
gen-device-stm32: build/gen-device-svd
./build/gen-device-svd -source=https://github.com/posborne/cmsis-svd/tree/master/data/STMicro lib/cmsis-svd/data/STMicro/ src/device/stm32/
GO111MODULE=off $(GO) fmt ./src/device/stm32
gen-device-stm32:
./tools/gen-device-svd.py lib/cmsis-svd/data/STMicro/ src/device/stm32/ --source=https://github.com/posborne/cmsis-svd/tree/master/data/STMicro
go fmt ./src/device/stm32
# Get LLVM sources.
$(LLVM_PROJECTDIR)/README.md:
git clone -b release/10.x https://github.com/llvm/llvm-project $(LLVM_PROJECTDIR)
llvm-source: $(LLVM_PROJECTDIR)/README.md
llvm-project/README.md:
git clone -b release/8.x https://github.com/llvm/llvm-project
llvm-source: llvm-project/README.md
# Configure LLVM.
TINYGO_SOURCE_DIR=$(shell pwd)
$(LLVM_BUILDDIR)/build.ninja: llvm-source
mkdir -p $(LLVM_BUILDDIR); cd $(LLVM_BUILDDIR); cmake -G Ninja $(TINYGO_SOURCE_DIR)/$(LLVM_PROJECTDIR)/llvm "-DLLVM_TARGETS_TO_BUILD=X86;ARM;AArch64;RISCV;WebAssembly" "-DLLVM_EXPERIMENTAL_TARGETS_TO_BUILD=AVR" -DCMAKE_BUILD_TYPE=Release -DLIBCLANG_BUILD_STATIC=ON -DLLVM_ENABLE_TERMINFO=OFF -DLLVM_ENABLE_ZLIB=OFF -DLLVM_ENABLE_PROJECTS="clang;lld" -DLLVM_TOOL_CLANG_TOOLS_EXTRA_BUILD=OFF $(LLVM_OPTION)
mkdir -p $(LLVM_BUILDDIR); cd $(LLVM_BUILDDIR); cmake -G Ninja $(TINYGO_SOURCE_DIR)/llvm-project/llvm "-DLLVM_TARGETS_TO_BUILD=X86;ARM;AArch64;WebAssembly" "-DLLVM_EXPERIMENTAL_TARGETS_TO_BUILD=AVR;RISCV" -DCMAKE_BUILD_TYPE=Release -DLLVM_ENABLE_ASSERTIONS=OFF -DLIBCLANG_BUILD_STATIC=ON -DLLVM_ENABLE_TERMINFO=OFF -DLLVM_ENABLE_ZLIB=OFF -DLLVM_ENABLE_PROJECTS="clang;lld" -DLLVM_TOOL_CLANG_TOOLS_EXTRA_BUILD=OFF
# Build LLVM.
$(LLVM_BUILDDIR): $(LLVM_BUILDDIR)/build.ninja
cd $(LLVM_BUILDDIR); ninja
# Build wasi-libc sysroot
.PHONY: wasi-libc
wasi-libc: lib/wasi-libc/sysroot/lib/wasm32-wasi/libc.a
lib/wasi-libc/sysroot/lib/wasm32-wasi/libc.a:
cd lib/wasi-libc && make -j4 WASM_CC=$(CLANG) WASM_AR=$(LLVM_AR) WASM_NM=$(LLVM_NM)
# Build the Go compiler.
tinygo:
build/tinygo:
@if [ ! -f "$(LLVM_BUILDDIR)/bin/llvm-config" ]; then echo "Fetch and build LLVM first by running:"; echo " make llvm-source"; echo " make $(LLVM_BUILDDIR)"; exit 1; fi
CGO_CPPFLAGS="$(CGO_CPPFLAGS)" CGO_CXXFLAGS="$(CGO_CXXFLAGS)" CGO_LDFLAGS="$(CGO_LDFLAGS)" $(GO) build -o build/tinygo$(EXE) -tags byollvm .
CGO_CPPFLAGS="$(CGO_CPPFLAGS)" CGO_CXXFLAGS="$(CGO_CXXFLAGS)" CGO_LDFLAGS="$(CGO_LDFLAGS)" go build -o build/tinygo -tags byollvm .
test: wasi-libc
CGO_CPPFLAGS="$(CGO_CPPFLAGS)" CGO_CXXFLAGS="$(CGO_CXXFLAGS)" CGO_LDFLAGS="$(CGO_LDFLAGS)" $(GO) test -v -tags byollvm ./cgo ./compileopts ./interp ./transform .
test:
CGO_CPPFLAGS="$(CGO_CPPFLAGS)" CGO_CXXFLAGS="$(CGO_CXXFLAGS)" CGO_LDFLAGS="$(CGO_LDFLAGS)" go test -v -tags byollvm .
tinygo-test:
cd tests/tinygotest && tinygo test
.PHONY: smoketest
smoketest:
$(TINYGO) version
# test all examples
$(TINYGO) build -size short -o test.hex -target=pca10040 examples/blinky1
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=pca10040 examples/adc
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=pca10040 examples/blinkm
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=pca10040 examples/blinky2
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=pca10040 examples/button
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=pca10040 examples/button2
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=pca10040 examples/echo
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=circuitplay-express examples/i2s
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=pca10040 examples/mcp3008
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=microbit examples/microbit-blink
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=pca10040 examples/pwm
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=pca10040 examples/serial
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=pca10040 examples/systick
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=pca10040 examples/test
@$(MD5SUM) test.hex
# test simulated boards on play.tinygo.org
$(TINYGO) build -o test.wasm -tags=arduino examples/blinky1
@$(MD5SUM) test.wasm
$(TINYGO) build -o test.wasm -tags=hifive1-qemu examples/serial
@$(MD5SUM) test.wasm
$(TINYGO) build -o test.wasm -tags=hifive1b examples/blinky1
@$(MD5SUM) test.wasm
$(TINYGO) build -o test.wasm -tags=reelboard examples/blinky1
@$(MD5SUM) test.wasm
$(TINYGO) build -o test.wasm -tags=pca10040 examples/blinky2
@$(MD5SUM) test.wasm
$(TINYGO) build -o test.wasm -tags=pca10056 examples/blinky2
@$(MD5SUM) test.wasm
$(TINYGO) build -o test.wasm -tags=circuitplay_express examples/blinky1
@$(MD5SUM) test.wasm
tinygo build -size short -o test.elf -target=pca10040 examples/blinky1
tinygo build -size short -o test.elf -target=pca10040 examples/adc
tinygo build -size short -o test.elf -target=pca10040 examples/blinkm
tinygo build -size short -o test.elf -target=pca10040 examples/blinky2
tinygo build -size short -o test.elf -target=pca10040 examples/button
tinygo build -size short -o test.elf -target=pca10040 examples/button2
tinygo build -size short -o test.elf -target=pca10040 examples/echo
tinygo build -size short -o test.elf -target=circuitplay-express examples/i2s
tinygo build -size short -o test.elf -target=pca10040 examples/mcp3008
tinygo build -size short -o test.elf -target=microbit examples/microbit-blink
tinygo build -size short -o test.elf -target=pca10040 examples/pwm
tinygo build -size short -o test.elf -target=pca10040 examples/serial
tinygo build -size short -o test.elf -target=pca10040 examples/test
# test all targets/boards
$(TINYGO) build -size short -o test.hex -target=pca10040-s132v6 examples/blinky1
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=microbit examples/echo
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=nrf52840-mdk examples/blinky1
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=pca10031 examples/blinky1
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=bluepill examples/blinky1
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=reelboard examples/blinky1
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=reelboard examples/blinky2
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=pca10056 examples/blinky1
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=pca10056 examples/blinky2
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=itsybitsy-m0 examples/blinky1
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=feather-m0 examples/blinky1
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=trinket-m0 examples/blinky1
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=circuitplay-express examples/blinky1
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=stm32f4disco examples/blinky1
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=stm32f4disco examples/blinky2
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=circuitplay-bluefruit examples/blinky1
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=circuitplay-express examples/i2s
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=clue_alpha examples/blinky1
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.gba -target=gameboy-advance examples/gba-display
@$(MD5SUM) test.gba
$(TINYGO) build -size short -o test.hex -target=itsybitsy-m4 examples/blinky1
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=feather-m4 examples/blinky1
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=pybadge examples/blinky1
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=metro-m4-airlift examples/blinky1
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=pyportal examples/blinky1
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=particle-argon examples/blinky1
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=particle-boron examples/blinky1
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=particle-xenon examples/blinky1
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=nucleo-f103rb examples/blinky1
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=pinetime-devkit0 examples/blinky1
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=x9pro examples/blinky1
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=pca10056-s140v7 examples/blinky1
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=reelboard-s140v7 examples/blinky1
@$(MD5SUM) test.hex
tinygo build -o test.wasm -tags=pca10040 examples/blinky2
tinygo build -size short -o test.elf -target=microbit examples/echo
tinygo build -size short -o test.elf -target=nrf52840-mdk examples/blinky1
tinygo build -size short -o test.elf -target=pca10031 examples/blinky1
tinygo build -size short -o test.elf -target=bluepill examples/blinky1
tinygo build -size short -o test.elf -target=reelboard examples/blinky1
tinygo build -size short -o test.elf -target=reelboard examples/blinky2
tinygo build -size short -o test.elf -target=pca10056 examples/blinky1
tinygo build -size short -o test.elf -target=pca10056 examples/blinky2
tinygo build -size short -o test.elf -target=itsybitsy-m0 examples/blinky1
tinygo build -size short -o test.elf -target=feather-m0 examples/blinky1
tinygo build -size short -o test.elf -target=trinket-m0 examples/blinky1
tinygo build -size short -o test.elf -target=circuitplay-express examples/blinky1
tinygo build -size short -o test.elf -target=stm32f4disco examples/blinky1
tinygo build -size short -o test.elf -target=stm32f4disco examples/blinky2
tinygo build -size short -o test.elf -target=circuitplay-express examples/i2s
tinygo build -size short -o test.elf -target=gameboy-advance examples/gba-display
ifneq ($(AVR), 0)
$(TINYGO) build -size short -o test.hex -target=atmega1284p examples/serial
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=arduino examples/blinky1
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=arduino -scheduler=tasks examples/blinky1
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=arduino-nano examples/blinky1
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=digispark examples/blinky1
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=digispark -gc=leaking examples/blinky1
@$(MD5SUM) test.hex
tinygo build -size short -o test.elf -target=arduino examples/blinky1
tinygo build -size short -o test.elf -target=digispark examples/blinky1
endif
$(TINYGO) build -size short -o test.hex -target=hifive1b examples/blinky1
@$(MD5SUM) test.hex
$(TINYGO) build -o wasm.wasm -target=wasm examples/wasm/export
$(TINYGO) build -o wasm.wasm -target=wasm examples/wasm/main
ifneq ($(RISCV), 0)
tinygo build -size short -o test.elf -target=hifive1b examples/blinky1
endif
tinygo build -o wasm.wasm -target=wasm examples/wasm/export
tinygo build -o wasm.wasm -target=wasm examples/wasm/main
release: tinygo gen-device wasi-libc
release: build/tinygo gen-device
@mkdir -p build/release/tinygo/bin
@mkdir -p build/release/tinygo/lib/clang/include
@mkdir -p build/release/tinygo/lib/CMSIS/CMSIS
@mkdir -p build/release/tinygo/lib/compiler-rt/lib
@mkdir -p build/release/tinygo/lib/nrfx
@mkdir -p build/release/tinygo/lib/picolibc/newlib/libc
@mkdir -p build/release/tinygo/lib/wasi-libc
@mkdir -p build/release/tinygo/pkg/armv6m-none-eabi
@mkdir -p build/release/tinygo/pkg/armv7m-none-eabi
@mkdir -p build/release/tinygo/pkg/armv7em-none-eabi
@echo copying source files
@cp -p build/tinygo$(EXE) build/release/tinygo/bin
@cp -p build/tinygo build/release/tinygo/bin
@cp -p $(abspath $(CLANG_SRC))/lib/Headers/*.h build/release/tinygo/lib/clang/include
@cp -rp lib/CMSIS/CMSIS/Include build/release/tinygo/lib/CMSIS/CMSIS
@cp -rp lib/CMSIS/README.md build/release/tinygo/lib/CMSIS
@@ -330,19 +153,9 @@ release: tinygo gen-device wasi-libc
@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/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/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/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-include build/release/tinygo/lib
@cp -rp lib/wasi-libc/sysroot build/release/tinygo/lib/wasi-libc/sysroot
@cp -rp src build/release/tinygo/src
@cp -rp targets build/release/tinygo/targets
./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-none-eabi -o build/release/tinygo/pkg/armv7m-none-eabi/compiler-rt.a 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-none-eabi -o build/release/tinygo/pkg/armv6m-none-eabi/picolibc.a picolibc
./build/tinygo build-library -target=armv7m-none-eabi -o build/release/tinygo/pkg/armv7m-none-eabi/picolibc.a picolibc
./build/tinygo build-library -target=armv7em-none-eabi -o build/release/tinygo/pkg/armv7em-none-eabi/picolibc.a picolibc
./build/tinygo build-builtins -target=armv6m-none-eabi -o build/release/tinygo/pkg/armv6m-none-eabi/compiler-rt.a
./build/tinygo build-builtins -target=armv7m-none-eabi -o build/release/tinygo/pkg/armv7m-none-eabi/compiler-rt.a
./build/tinygo build-builtins -target=armv7em-none-eabi -o build/release/tinygo/pkg/armv7em-none-eabi/compiler-rt.a
tar -czf build/release.tar.gz -C build/release tinygo
+5 -24
View File
@@ -1,6 +1,6 @@
# TinyGo - Go compiler for small places
[![CircleCI](https://circleci.com/gh/tinygo-org/tinygo/tree/dev.svg?style=svg)](https://circleci.com/gh/tinygo-org/tinygo/tree/dev) [![Build Status](https://dev.azure.com/tinygo/tinygo/_apis/build/status/tinygo-CI?branchName=dev)](https://dev.azure.com/tinygo/tinygo/_build/latest?definitionId=1&branchName=dev)
[![CircleCI](https://circleci.com/gh/tinygo-org/tinygo/tree/dev.svg?style=svg)](https://circleci.com/gh/tinygo-org/tinygo/tree/dev)
TinyGo is a Go compiler intended for use in small places such as microcontrollers, WebAssembly (Wasm), and command-line tools.
@@ -43,40 +43,23 @@ See the [getting started instructions](https://tinygo.org/getting-started/) for
You can compile TinyGo programs for microcontrollers, WebAssembly and Linux.
The following 32 microcontroller boards are currently supported:
The following 15 microcontroller boards are currently supported:
* [Adafruit Circuit Playground Bluefruit](https://www.adafruit.com/product/4333)
* [Adafruit Circuit Playground Express](https://www.adafruit.com/product/3333)
* [Adafruit CLUE Alpha](https://www.adafruit.com/product/4500)
* [Adafruit Feather M0](https://www.adafruit.com/product/2772)
* [Adafruit Feather M4](https://www.adafruit.com/product/3857)
* [Adafruit ItsyBitsy M0](https://www.adafruit.com/product/3727)
* [Adafruit ItsyBitsy M4](https://www.adafruit.com/product/3800)
* [Adafruit Metro M4 Express Airlift](https://www.adafruit.com/product/4000)
* [Adafruit PyBadge](https://www.adafruit.com/product/4200)
* [Adafruit PyPortal](https://www.adafruit.com/product/4116)
* [Adafruit Trinket M0](https://www.adafruit.com/product/3500)
* [Arduino Mega 2560](https://store.arduino.cc/arduino-mega-2560-rev3)
* [Arduino Nano](https://store.arduino.cc/arduino-nano)
* [Arduino Nano33 IoT](https://store.arduino.cc/nano-33-iot)
* [Arduino Uno](https://store.arduino.cc/arduino-uno-rev3)
* [BBC micro:bit](https://microbit.org/)
* [ST Micro STM32F103XX "Bluepill"](http://wiki.stm32duino.com/index.php?title=Blue_Pill)
* [ST Micro STM32F407 "Discovery"](https://www.st.com/en/evaluation-tools/stm32f4discovery.html)
* [Digispark](http://digistump.com/products/1)
* [Game Boy Advance](https://en.wikipedia.org/wiki/Game_Boy_Advance)
* [Makerdiary nRF52840-MDK](https://wiki.makerdiary.com/nrf52840-mdk/)
* [Nordic Semiconductor PCA10031](https://www.nordicsemi.com/eng/Products/nRF51-Dongle)
* [Nordic Semiconductor PCA10040](https://www.nordicsemi.com/eng/Products/Bluetooth-low-energy/nRF52-DK)
* [Nordic Semiconductor PCA10056](https://www.nordicsemi.com/Software-and-Tools/Development-Kits/nRF52840-DK)
* [Particle Argon](https://docs.particle.io/datasheets/wi-fi/argon-datasheet/)
* [Particle Boron](https://docs.particle.io/datasheets/cellular/boron-datasheet/)
* [Particle Xenon](https://docs.particle.io/datasheets/discontinued/xenon-datasheet/)
* [Makerdiary nRF52840-MDK](https://wiki.makerdiary.com/nrf52840-mdk/)
* [Phytec reel board](https://www.phytec.eu/product-eu/internet-of-things/reelboard/)
* [PineTime DevKit](https://www.pine64.org/pinetime/)
* [SiFIve HiFive1](https://www.sifive.com/boards/hifive1)
* [ST Micro "Nucleo F103RB"](https://www.st.com/en/evaluation-tools/nucleo-f103rb.html)
* [ST Micro STM32F103XX "Bluepill"](http://wiki.stm32duino.com/index.php?title=Blue_Pill)
* [ST Micro STM32F407 "Discovery"](https://www.st.com/en/evaluation-tools/stm32f4discovery.html)
* [X9 Pro smartwatch](https://github.com/curtpw/nRF5x-device-reverse-engineering/tree/master/X9-nrf52832-activity-tracker/)
For more information, see [this list of boards](https://tinygo.org/microcontrollers/). Pull requests for additional support are welcome!
@@ -135,5 +118,3 @@ The original reasoning was: if [Python](https://micropython.org/) can run on mic
## License
This project is licensed under the BSD 3-clause license, just like the [Go project](https://golang.org/LICENSE) itself.
Some code has been copied from the LLVM project and is therefore licensed under [a variant of the Apache 2.0 license](http://releases.llvm.org/10.0.0/LICENSE.TXT). This has been clearly indicated in the header of these files.
-83
View File
@@ -1,83 +0,0 @@
# Avoid lengthy LLVM rebuilds on each newly pushed branch. Pull requests will
# be built anyway.
trigger:
- master
- dev
jobs:
- job: Build
timeoutInMinutes: 240 # 4h
pool:
vmImage: 'VS2017-Win2016'
steps:
- task: GoTool@0
inputs:
version: '1.14.1'
- checkout: self
- task: CacheBeta@0
displayName: Cache LLVM source
inputs:
key: llvm-source-10-windows-v0
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-10-windows-v0
path: llvm-build
- task: Bash@3
displayName: Build LLVM
inputs:
targetType: inline
script: |
if [ ! -f llvm-build/lib/liblldELF.a ]
then
choco install ninja
make llvm-build
fi
- task: Bash@3
displayName: Install QEMU
inputs:
targetType: inline
script: choco install qemu
- task: CacheBeta@0
displayName: Cache wasi-libc sysroot
inputs:
key: wasi-libc-sysroot-v2
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 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
+19 -32
View File
@@ -1,4 +1,4 @@
package builder
package main
import (
"debug/elf"
@@ -6,18 +6,18 @@ import (
"strings"
)
// programSize contains size statistics per package of a compiled program.
type programSize struct {
Packages map[string]*packageSize
Sum *packageSize
// Statistics about code size in a program.
type ProgramSize struct {
Packages map[string]*PackageSize
Sum *PackageSize
Code uint64
Data uint64
BSS uint64
}
// sortedPackageNames returns the list of package names (ProgramSize.Packages)
// sorted alphabetically.
func (ps *programSize) sortedPackageNames() []string {
// Return the list of package names (ProgramSize.Packages) sorted
// alphabetically.
func (ps *ProgramSize) SortedPackageNames() []string {
names := make([]string, 0, len(ps.Packages))
for name := range ps.Packages {
names = append(names, name)
@@ -26,9 +26,8 @@ func (ps *programSize) sortedPackageNames() []string {
return names
}
// packageSize contains the size of a package, calculated from the linked object
// file.
type packageSize struct {
// The size of a package, calculated from the linked object file.
type PackageSize struct {
Code uint64
ROData uint64
Data uint64
@@ -36,12 +35,12 @@ type packageSize struct {
}
// Flash usage in regular microcontrollers.
func (ps *packageSize) Flash() uint64 {
func (ps *PackageSize) Flash() uint64 {
return ps.Code + ps.ROData + ps.Data
}
// Static RAM usage in regular microcontrollers.
func (ps *packageSize) RAM() uint64 {
func (ps *PackageSize) RAM() uint64 {
return ps.Data + ps.BSS
}
@@ -65,9 +64,8 @@ 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
// given ELF file.
func loadProgramSize(path string) (*programSize, error) {
// Calculate program/data size breakdown of each package for a given ELF file.
func Sizes(path string) (*ProgramSize, error) {
file, err := elf.Open(path)
if err != nil {
return nil, err
@@ -84,18 +82,7 @@ func loadProgramSize(path string) (*programSize, error) {
if section.Type != elf.SHT_PROGBITS && section.Type != elf.SHT_NOBITS {
continue
}
if section.Name == ".stack" {
// HACK: this works around a bug in ld.lld from LLVM 10. The linker
// marks sections with no input symbols (such as is the case for the
// .stack section) as SHT_PROGBITS instead of SHT_NOBITS. While it
// doesn't affect the generated binaries (.hex and .bin), it does
// affect the reported size.
// https://bugs.llvm.org/show_bug.cgi?id=45336
// https://reviews.llvm.org/D76981
// It has been merged in master, but it has not (yet) been
// backported to the LLVM 10 release branch.
sumBSS += section.Size
} else if section.Type == elf.SHT_NOBITS {
if section.Type == elf.SHT_NOBITS {
sumBSS += section.Size
} else if section.Flags&elf.SHF_EXECINSTR != 0 {
sumCode += section.Size
@@ -128,7 +115,7 @@ func loadProgramSize(path string) (*programSize, error) {
}
sort.Sort(symbolList(symbols))
sizes := map[string]*packageSize{}
sizes := map[string]*PackageSize{}
var lastSymbolValue uint64
for _, symbol := range symbols {
symType := elf.ST_TYPE(symbol.Info)
@@ -142,7 +129,7 @@ func loadProgramSize(path string) (*programSize, error) {
}
pkgSize := sizes[pkgName]
if pkgSize == nil {
pkgSize = &packageSize{}
pkgSize = &PackageSize{}
sizes[pkgName] = pkgSize
}
if lastSymbolValue != symbol.Value || lastSymbolValue == 0 {
@@ -161,7 +148,7 @@ func loadProgramSize(path string) (*programSize, error) {
lastSymbolValue = symbol.Value
}
sum := &packageSize{}
sum := &PackageSize{}
for _, pkg := range sizes {
sum.Code += pkg.Code
sum.ROData += pkg.ROData
@@ -169,5 +156,5 @@ func loadProgramSize(path string) (*programSize, error) {
sum.BSS += pkg.BSS
}
return &programSize{Packages: sizes, Code: sumCode, Data: sumData, BSS: sumBSS, Sum: sum}, nil
return &ProgramSize{Packages: sizes, Code: sumCode, Data: sumData, BSS: sumBSS, Sum: sum}, nil
}
+15 -7
View File
@@ -1,14 +1,21 @@
package builder
package main
import (
"io"
"os"
"path/filepath"
"time"
"github.com/tinygo-org/tinygo/goenv"
)
// Get the cache directory, usually ~/.cache/tinygo
func cacheDir() string {
dir, err := os.UserCacheDir()
if err != nil {
panic("could not find cache dir: " + err.Error())
}
return filepath.Join(dir, "tinygo")
}
// Return the newest timestamp of all the file paths passed in. Used to check
// for stale caches.
func cacheTimestamp(paths []string) (time.Time, error) {
@@ -34,7 +41,8 @@ func cacheTimestamp(paths []string) (time.Time, error) {
// TODO: the configKey is currently ignored. It is supposed to be used as extra
// data for the cache key, like the compiler version and arguments.
func cacheLoad(name, configKey string, sourceFiles []string) (string, error) {
cachepath := filepath.Join(goenv.Get("GOCACHE"), name)
dir := cacheDir()
cachepath := filepath.Join(dir, name)
cacheStat, err := os.Stat(cachepath)
if os.IsNotExist(err) {
return "", nil // does not exist
@@ -68,7 +76,7 @@ func cacheStore(tmppath, name, configKey string, sourceFiles []string) (string,
// TODO: check the config key
dir := goenv.Get("GOCACHE")
dir := cacheDir()
err := os.MkdirAll(dir, 0777)
if err != nil {
return "", err
@@ -109,10 +117,10 @@ func moveFile(src, dst string) error {
return err
}
err = outf.Close()
err = os.Rename(dst+".tmp", dst)
if err != nil {
return err
}
return os.Rename(dst+".tmp", dst)
return outf.Close()
}
-182
View File
@@ -1,182 +0,0 @@
package builder
import (
"bytes"
"debug/elf"
"encoding/binary"
"errors"
"io"
"os"
"path/filepath"
"time"
"github.com/blakesmith/ar"
)
// makeArchive creates an arcive for static linking from a list of object files
// given as a parameter. It is equivalent to the following command:
//
// ar -rcs <archivePath> <objs...>
func makeArchive(archivePath string, objs []string) error {
// Open the archive file.
arfile, err := os.Create(archivePath)
if err != nil {
return err
}
defer arfile.Close()
arwriter := ar.NewWriter(arfile)
err = arwriter.WriteGlobalHeader()
if err != nil {
return &os.PathError{"write ar header", archivePath, err}
}
// Open all object files and read the symbols for the symbol table.
symbolTable := []struct {
name string // symbol name
fileIndex int // index into objfiles
}{}
objfiles := make([]struct {
file *os.File
archiveOffset int32
}, len(objs))
for i, objpath := range objs {
objfile, err := os.Open(objpath)
if err != nil {
return err
}
objfiles[i].file = objfile
// Read the symbols and add them to the symbol table.
dbg, err := elf.NewFile(objfile)
if err != nil {
return err
}
symbols, err := dbg.Symbols()
if err != nil {
return err
}
for _, symbol := range symbols {
bind := elf.ST_BIND(symbol.Info)
if bind != elf.STB_GLOBAL && bind != elf.STB_WEAK {
// Don't include local symbols (STB_LOCAL).
continue
}
if elf.ST_TYPE(symbol.Info) != elf.STT_FUNC {
// Not a function.
// TODO: perhaps globals variables should also be included?
continue
}
// Include in archive.
symbolTable = append(symbolTable, struct {
name string
fileIndex int
}{symbol.Name, i})
}
}
// Create the symbol table buffer.
// For some (sparse) details on the file format:
// https://en.wikipedia.org/wiki/Ar_(Unix)#System_V_(or_GNU)_variant
buf := &bytes.Buffer{}
binary.Write(buf, binary.BigEndian, int32(len(symbolTable)))
for range symbolTable {
// This is a placeholder index, it will be updated after all files have
// been written to the archive (see the end of this function).
err = binary.Write(buf, binary.BigEndian, int32(0))
if err != nil {
return err
}
}
for _, sym := range symbolTable {
_, err := buf.Write([]byte(sym.name + "\x00"))
if err != nil {
return err
}
}
for buf.Len()%2 != 0 {
// The symbol table must be aligned.
// This appears to be required by lld.
buf.WriteByte(0)
}
// Write the symbol table.
err = arwriter.WriteHeader(&ar.Header{
Name: "/",
ModTime: time.Unix(0, 0),
Uid: 0,
Gid: 0,
Mode: 0,
Size: int64(buf.Len()),
})
if err != nil {
return err
}
// Keep track of the start of the symbol table.
symbolTableStart, err := arfile.Seek(0, os.SEEK_CUR)
if err != nil {
return err
}
// Write symbol table contents.
_, err = arfile.Write(buf.Bytes())
if err != nil {
return err
}
// Add all object files to the archive.
for i, objfile := range objfiles {
// Store the start index, for when we'll update the symbol table with
// the correct file start indices.
offset, err := arfile.Seek(0, os.SEEK_CUR)
if err != nil {
return err
}
if int64(int32(offset)) != offset {
return errors.New("large archives (4GB+) not supported: " + archivePath)
}
objfiles[i].archiveOffset = int32(offset)
// Write the file header.
st, err := objfile.file.Stat()
if err != nil {
return err
}
err = arwriter.WriteHeader(&ar.Header{
Name: filepath.Base(objfile.file.Name()),
ModTime: time.Unix(0, 0),
Uid: 0,
Gid: 0,
Mode: 0644,
Size: st.Size(),
})
if err != nil {
return err
}
// Copy the file contents into the archive.
n, err := io.Copy(arwriter, objfile.file)
if err != nil {
return err
}
if n != st.Size() {
return errors.New("file modified during ar creation: " + archivePath)
}
// File is not needed anymore.
objfile.file.Close()
}
// Create symbol indices.
indicesBuf := &bytes.Buffer{}
for _, sym := range symbolTable {
err = binary.Write(indicesBuf, binary.BigEndian, objfiles[sym.fileIndex].archiveOffset)
if err != nil {
return err
}
}
// Overwrite placeholder indices.
_, err = arfile.WriteAt(indicesBuf.Bytes(), symbolTableStart+4)
return err
}
-232
View File
@@ -1,232 +0,0 @@
// Package builder is the compiler driver of TinyGo. It takes in a package name
// and an output path, and outputs an executable. It manages the entire
// compilation pipeline in between.
package builder
import (
"errors"
"fmt"
"io/ioutil"
"os"
"path/filepath"
"strconv"
"strings"
"github.com/tinygo-org/tinygo/compileopts"
"github.com/tinygo-org/tinygo/compiler"
"github.com/tinygo-org/tinygo/goenv"
"github.com/tinygo-org/tinygo/interp"
"github.com/tinygo-org/tinygo/transform"
"tinygo.org/x/go-llvm"
)
// Build performs a single package to executable Go build. It takes in a package
// name, an output path, and set of compile options and from that it manages the
// whole compilation process.
//
// The error value may be of type *MultiError. Callers will likely want to check
// for this case and print such errors individually.
func Build(pkgName, outpath string, config *compileopts.Config, action func(string) error) error {
// Compile Go code to IR.
machine, err := compiler.NewTargetMachine(config)
if err != nil {
return err
}
mod, extraFiles, errs := compiler.Compile(pkgName, machine, config)
if errs != nil {
return newMultiError(errs)
}
if config.Options.PrintIR {
fmt.Println("; Generated LLVM IR:")
fmt.Println(mod.String())
}
if err := llvm.VerifyModule(mod, llvm.PrintMessageAction); err != nil {
return errors.New("verification error after IR construction")
}
err = interp.Run(mod, config.DumpSSA())
if err != nil {
return err
}
if err := llvm.VerifyModule(mod, llvm.PrintMessageAction); err != nil {
return errors.New("verification error after interpreting runtime.initAll")
}
if config.GOOS() != "darwin" {
transform.ApplyFunctionSections(mod) // -ffunction-sections
}
// Browsers cannot handle external functions that have type i64 because it
// cannot be represented exactly in JavaScript (JS only has doubles). To
// keep functions interoperable, pass int64 types as pointers to
// stack-allocated values.
// Use -wasm-abi=generic to disable this behaviour.
if config.Options.WasmAbi == "js" && strings.HasPrefix(config.Triple(), "wasm") {
err := transform.ExternalInt64AsPtr(mod)
if err != nil {
return err
}
}
// Optimization levels here are roughly the same as Clang, but probably not
// exactly.
errs = nil
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:
errs = []error{errors.New("unknown optimization level: -opt=" + config.Options.Opt)}
}
if len(errs) > 0 {
return newMultiError(errs)
}
if err := llvm.VerifyModule(mod, llvm.PrintMessageAction); err != nil {
return errors.New("verification failure after LLVM optimization passes")
}
// On the AVR, pointers can point either to flash or to RAM, but we don't
// know. As a temporary fix, load all global variables in RAM.
// In the future, there should be a compiler pass that determines which
// pointers are flash and which are in RAM so that pointers can have a
// correct address space parameter (address space 1 is for flash).
if strings.HasPrefix(config.Triple(), "avr") {
transform.NonConstGlobals(mod)
if err := llvm.VerifyModule(mod, llvm.PrintMessageAction); err != nil {
return errors.New("verification error after making all globals non-constant on AVR")
}
}
// Generate output.
outext := filepath.Ext(outpath)
switch outext {
case ".o":
llvmBuf, err := machine.EmitToMemoryBuffer(mod, llvm.ObjectFile)
if err != nil {
return err
}
return ioutil.WriteFile(outpath, llvmBuf.Bytes(), 0666)
case ".bc":
data := llvm.WriteBitcodeToMemoryBuffer(mod).Bytes()
return ioutil.WriteFile(outpath, data, 0666)
case ".ll":
data := []byte(mod.String())
return ioutil.WriteFile(outpath, data, 0666)
default:
// Act as a compiler driver.
// Create a temporary directory for intermediary files.
dir, err := ioutil.TempDir("", "tinygo")
if err != nil {
return err
}
defer os.RemoveAll(dir)
// Write the object file.
objfile := filepath.Join(dir, "main.o")
llvmBuf, err := machine.EmitToMemoryBuffer(mod, llvm.ObjectFile)
if err != nil {
return err
}
err = ioutil.WriteFile(objfile, llvmBuf.Bytes(), 0666)
if err != nil {
return err
}
// Prepare link command.
executable := filepath.Join(dir, "main")
tmppath := executable // final file
ldflags := append(config.LDFlags(), "-o", executable, objfile)
// Load builtins library from the cache, possibly compiling it on the
// fly.
if config.Target.RTLib == "compiler-rt" {
librt, err := CompilerRT.Load(config.Triple())
if err != nil {
return err
}
ldflags = append(ldflags, librt)
}
// Add libc.
if config.Target.Libc == "picolibc" {
libc, err := Picolibc.Load(config.Triple())
if err != nil {
return err
}
ldflags = append(ldflags, libc)
}
// Compile extra files.
root := goenv.Get("TINYGOROOT")
for i, path := range config.ExtraFiles() {
abspath := filepath.Join(root, path)
outpath := filepath.Join(dir, "extra-"+strconv.Itoa(i)+"-"+filepath.Base(path)+".o")
err := runCCompiler(config.Target.Compiler, append(config.CFlags(), "-c", "-o", outpath, abspath)...)
if err != nil {
return &commandError{"failed to build", path, err}
}
ldflags = append(ldflags, outpath)
}
// Compile C files in packages.
for i, file := range extraFiles {
outpath := filepath.Join(dir, "pkg"+strconv.Itoa(i)+"-"+filepath.Base(file)+".o")
err := runCCompiler(config.Target.Compiler, append(config.CFlags(), "-c", "-o", outpath, file)...)
if err != nil {
return &commandError{"failed to build", file, err}
}
ldflags = append(ldflags, outpath)
}
// Link the object files together.
err = link(config.Target.Linker, ldflags...)
if err != nil {
return &commandError{"failed to link", executable, err}
}
if config.Options.PrintSizes == "short" || config.Options.PrintSizes == "full" {
sizes, err := loadProgramSize(executable)
if err != nil {
return err
}
if config.Options.PrintSizes == "short" {
fmt.Printf(" code data bss | flash ram\n")
fmt.Printf("%7d %7d %7d | %7d %7d\n", sizes.Code, sizes.Data, sizes.BSS, sizes.Code+sizes.Data, sizes.Data+sizes.BSS)
} else {
fmt.Printf(" code rodata data bss | flash ram | package\n")
for _, name := range sizes.sortedPackageNames() {
pkgSize := sizes.Packages[name]
fmt.Printf("%7d %7d %7d %7d | %7d %7d | %s\n", pkgSize.Code, pkgSize.ROData, pkgSize.Data, pkgSize.BSS, pkgSize.Flash(), pkgSize.RAM(), name)
}
fmt.Printf("%7d %7d %7d %7d | %7d %7d | (sum)\n", sizes.Sum.Code, sizes.Sum.ROData, sizes.Sum.Data, sizes.Sum.BSS, sizes.Sum.Flash(), sizes.Sum.RAM())
fmt.Printf("%7d - %7d %7d | %7d %7d | (all)\n", sizes.Code, sizes.Data, sizes.BSS, sizes.Code+sizes.Data, sizes.Data+sizes.BSS)
}
}
// Get an Intel .hex file or .bin file from the .elf file.
if outext == ".hex" || outext == ".bin" || outext == ".gba" {
tmppath = filepath.Join(dir, "main"+outext)
err := objcopy(executable, tmppath)
if err != nil {
return err
}
} else if outext == ".uf2" {
// Get UF2 from the .elf file.
tmppath = filepath.Join(dir, "main"+outext)
err := convertELFFileToUF2File(executable, tmppath, config.Target.UF2FamilyID)
if err != nil {
return err
}
}
return action(tmppath)
}
}
-170
View File
@@ -1,170 +0,0 @@
package builder
import (
"strings"
)
// These are the GENERIC_SOURCES according to CMakeList.txt.
var genericBuiltins = []string{
"absvdi2.c",
"absvsi2.c",
"absvti2.c",
"adddf3.c",
"addsf3.c",
"addtf3.c",
"addvdi3.c",
"addvsi3.c",
"addvti3.c",
"apple_versioning.c",
"ashldi3.c",
"ashlti3.c",
"ashrdi3.c",
"ashrti3.c",
"bswapdi2.c",
"bswapsi2.c",
"clzdi2.c",
"clzsi2.c",
"clzti2.c",
"cmpdi2.c",
"cmpti2.c",
"comparedf2.c",
"comparesf2.c",
"ctzdi2.c",
"ctzsi2.c",
"ctzti2.c",
"divdc3.c",
"divdf3.c",
"divdi3.c",
"divmoddi4.c",
"divmodsi4.c",
"divsc3.c",
"divsf3.c",
"divsi3.c",
"divtc3.c",
"divti3.c",
"divtf3.c",
"extendsfdf2.c",
"extendhfsf2.c",
"ffsdi2.c",
"ffssi2.c",
"ffsti2.c",
"fixdfdi.c",
"fixdfsi.c",
"fixdfti.c",
"fixsfdi.c",
"fixsfsi.c",
"fixsfti.c",
"fixunsdfdi.c",
"fixunsdfsi.c",
"fixunsdfti.c",
"fixunssfdi.c",
"fixunssfsi.c",
"fixunssfti.c",
"floatdidf.c",
"floatdisf.c",
"floatsidf.c",
"floatsisf.c",
"floattidf.c",
"floattisf.c",
"floatundidf.c",
"floatundisf.c",
"floatunsidf.c",
"floatunsisf.c",
"floatuntidf.c",
"floatuntisf.c",
//"int_util.c",
"lshrdi3.c",
"lshrti3.c",
"moddi3.c",
"modsi3.c",
"modti3.c",
"muldc3.c",
"muldf3.c",
"muldi3.c",
"mulodi4.c",
"mulosi4.c",
"muloti4.c",
"mulsc3.c",
"mulsf3.c",
"multi3.c",
"multf3.c",
"mulvdi3.c",
"mulvsi3.c",
"mulvti3.c",
"negdf2.c",
"negdi2.c",
"negsf2.c",
"negti2.c",
"negvdi2.c",
"negvsi2.c",
"negvti2.c",
"os_version_check.c",
"paritydi2.c",
"paritysi2.c",
"parityti2.c",
"popcountdi2.c",
"popcountsi2.c",
"popcountti2.c",
"powidf2.c",
"powisf2.c",
"powitf2.c",
"subdf3.c",
"subsf3.c",
"subvdi3.c",
"subvsi3.c",
"subvti3.c",
"subtf3.c",
"trampoline_setup.c",
"truncdfhf2.c",
"truncdfsf2.c",
"truncsfhf2.c",
"ucmpdi2.c",
"ucmpti2.c",
"udivdi3.c",
"udivmoddi4.c",
"udivmodsi4.c",
"udivmodti4.c",
"udivsi3.c",
"udivti3.c",
"umoddi3.c",
"umodsi3.c",
"umodti3.c",
}
var aeabiBuiltins = []string{
"arm/aeabi_cdcmp.S",
"arm/aeabi_cdcmpeq_check_nan.c",
"arm/aeabi_cfcmp.S",
"arm/aeabi_cfcmpeq_check_nan.c",
"arm/aeabi_dcmp.S",
"arm/aeabi_div0.c",
"arm/aeabi_drsub.c",
"arm/aeabi_fcmp.S",
"arm/aeabi_frsub.c",
"arm/aeabi_idivmod.S",
"arm/aeabi_ldivmod.S",
"arm/aeabi_memcmp.S",
"arm/aeabi_memcpy.S",
"arm/aeabi_memmove.S",
"arm/aeabi_memset.S",
"arm/aeabi_uidivmod.S",
"arm/aeabi_uldivmod.S",
}
// CompilerRT is a library with symbols required by programs compiled with LLVM.
// These symbols are for operations that cannot be emitted with a single
// instruction or a short sequence of instructions for that target.
//
// For more information, see: https://compiler-rt.llvm.org/
var CompilerRT = Library{
name: "compiler-rt",
cflags: func() []string { return []string{"-Werror", "-Wall", "-std=c11", "-nostdlibinc"} },
sourceDir: "lib/compiler-rt/lib/builtins",
sources: func(target string) []string {
builtins := append([]string{}, genericBuiltins...) // copy genericBuiltins
if strings.HasPrefix(target, "arm") || strings.HasPrefix(target, "thumb") {
builtins = append(builtins, aeabiBuiltins...)
}
return builtins
},
}
-510
View File
@@ -1,510 +0,0 @@
// +build byollvm
//===-- cc1as.cpp - Clang Assembler --------------------------------------===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
//
// This is the entry point to the clang -cc1as functionality, which implements
// the direct interface to the LLVM MC based assembler.
//
//===----------------------------------------------------------------------===//
#include "clang/Basic/Diagnostic.h"
#include "clang/Basic/DiagnosticOptions.h"
#include "clang/Driver/DriverDiagnostic.h"
#include "clang/Driver/Options.h"
#include "clang/Frontend/FrontendDiagnostic.h"
#include "clang/Frontend/TextDiagnosticPrinter.h"
#include "clang/Frontend/Utils.h"
#include "llvm/ADT/STLExtras.h"
#include "llvm/ADT/StringSwitch.h"
#include "llvm/ADT/Triple.h"
#include "llvm/IR/DataLayout.h"
#include "llvm/MC/MCAsmBackend.h"
#include "llvm/MC/MCAsmInfo.h"
#include "llvm/MC/MCCodeEmitter.h"
#include "llvm/MC/MCContext.h"
#include "llvm/MC/MCInstrInfo.h"
#include "llvm/MC/MCObjectFileInfo.h"
#include "llvm/MC/MCObjectWriter.h"
#include "llvm/MC/MCParser/MCAsmParser.h"
#include "llvm/MC/MCParser/MCTargetAsmParser.h"
#include "llvm/MC/MCRegisterInfo.h"
#include "llvm/MC/MCSectionMachO.h"
#include "llvm/MC/MCStreamer.h"
#include "llvm/MC/MCSubtargetInfo.h"
#include "llvm/MC/MCTargetOptions.h"
#include "llvm/Option/Arg.h"
#include "llvm/Option/ArgList.h"
#include "llvm/Option/OptTable.h"
#include "llvm/Support/CommandLine.h"
#include "llvm/Support/ErrorHandling.h"
#include "llvm/Support/FileSystem.h"
#include "llvm/Support/FormattedStream.h"
#include "llvm/Support/Host.h"
#include "llvm/Support/MemoryBuffer.h"
#include "llvm/Support/Path.h"
#include "llvm/Support/Process.h"
#include "llvm/Support/Signals.h"
#include "llvm/Support/SourceMgr.h"
#include "llvm/Support/TargetRegistry.h"
#include "llvm/Support/TargetSelect.h"
#include "llvm/Support/Timer.h"
#include "llvm/Support/raw_ostream.h"
#include <memory>
#include <system_error>
using namespace clang;
using namespace clang::driver;
using namespace clang::driver::options;
using namespace llvm;
using namespace llvm::opt;
#include "cc1as.h"
bool AssemblerInvocation::CreateFromArgs(AssemblerInvocation &Opts,
ArrayRef<const char *> Argv,
DiagnosticsEngine &Diags) {
bool Success = true;
// Parse the arguments.
const OptTable &OptTbl = getDriverOptTable();
const unsigned IncludedFlagsBitmask = options::CC1AsOption;
unsigned MissingArgIndex, MissingArgCount;
InputArgList Args = OptTbl.ParseArgs(Argv, MissingArgIndex, MissingArgCount,
IncludedFlagsBitmask);
// Check for missing argument error.
if (MissingArgCount) {
Diags.Report(diag::err_drv_missing_argument)
<< Args.getArgString(MissingArgIndex) << MissingArgCount;
Success = false;
}
// Issue errors on unknown arguments.
for (const Arg *A : Args.filtered(OPT_UNKNOWN)) {
auto ArgString = A->getAsString(Args);
std::string Nearest;
if (OptTbl.findNearest(ArgString, Nearest, IncludedFlagsBitmask) > 1)
Diags.Report(diag::err_drv_unknown_argument) << ArgString;
else
Diags.Report(diag::err_drv_unknown_argument_with_suggestion)
<< ArgString << Nearest;
Success = false;
}
// Construct the invocation.
// Target Options
Opts.Triple = llvm::Triple::normalize(Args.getLastArgValue(OPT_triple));
Opts.CPU = Args.getLastArgValue(OPT_target_cpu);
Opts.Features = Args.getAllArgValues(OPT_target_feature);
// Use the default target triple if unspecified.
if (Opts.Triple.empty())
Opts.Triple = llvm::sys::getDefaultTargetTriple();
// Language Options
Opts.IncludePaths = Args.getAllArgValues(OPT_I);
Opts.NoInitialTextSection = Args.hasArg(OPT_n);
Opts.SaveTemporaryLabels = Args.hasArg(OPT_msave_temp_labels);
// Any DebugInfoKind implies GenDwarfForAssembly.
Opts.GenDwarfForAssembly = Args.hasArg(OPT_debug_info_kind_EQ);
if (const Arg *A = Args.getLastArg(OPT_compress_debug_sections,
OPT_compress_debug_sections_EQ)) {
if (A->getOption().getID() == OPT_compress_debug_sections) {
// TODO: be more clever about the compression type auto-detection
Opts.CompressDebugSections = llvm::DebugCompressionType::GNU;
} else {
Opts.CompressDebugSections =
llvm::StringSwitch<llvm::DebugCompressionType>(A->getValue())
.Case("none", llvm::DebugCompressionType::None)
.Case("zlib", llvm::DebugCompressionType::Z)
.Case("zlib-gnu", llvm::DebugCompressionType::GNU)
.Default(llvm::DebugCompressionType::None);
}
}
Opts.RelaxELFRelocations = Args.hasArg(OPT_mrelax_relocations);
Opts.DwarfVersion = getLastArgIntValue(Args, OPT_dwarf_version_EQ, 2, Diags);
Opts.DwarfDebugFlags = Args.getLastArgValue(OPT_dwarf_debug_flags);
Opts.DwarfDebugProducer = Args.getLastArgValue(OPT_dwarf_debug_producer);
Opts.DebugCompilationDir = Args.getLastArgValue(OPT_fdebug_compilation_dir);
Opts.MainFileName = Args.getLastArgValue(OPT_main_file_name);
for (const auto &Arg : Args.getAllArgValues(OPT_fdebug_prefix_map_EQ))
Opts.DebugPrefixMap.insert(StringRef(Arg).split('='));
// Frontend Options
if (Args.hasArg(OPT_INPUT)) {
bool First = true;
for (const Arg *A : Args.filtered(OPT_INPUT)) {
if (First) {
Opts.InputFile = A->getValue();
First = false;
} else {
Diags.Report(diag::err_drv_unknown_argument) << A->getAsString(Args);
Success = false;
}
}
}
Opts.LLVMArgs = Args.getAllArgValues(OPT_mllvm);
Opts.OutputPath = Args.getLastArgValue(OPT_o);
Opts.SplitDwarfOutput = Args.getLastArgValue(OPT_split_dwarf_output);
if (Arg *A = Args.getLastArg(OPT_filetype)) {
StringRef Name = A->getValue();
unsigned OutputType = StringSwitch<unsigned>(Name)
.Case("asm", FT_Asm)
.Case("null", FT_Null)
.Case("obj", FT_Obj)
.Default(~0U);
if (OutputType == ~0U) {
Diags.Report(diag::err_drv_invalid_value) << A->getAsString(Args) << Name;
Success = false;
} else
Opts.OutputType = FileType(OutputType);
}
Opts.ShowHelp = Args.hasArg(OPT_help);
Opts.ShowVersion = Args.hasArg(OPT_version);
// Transliterate Options
Opts.OutputAsmVariant =
getLastArgIntValue(Args, OPT_output_asm_variant, 0, Diags);
Opts.ShowEncoding = Args.hasArg(OPT_show_encoding);
Opts.ShowInst = Args.hasArg(OPT_show_inst);
// Assemble Options
Opts.RelaxAll = Args.hasArg(OPT_mrelax_all);
Opts.NoExecStack = Args.hasArg(OPT_mno_exec_stack);
Opts.FatalWarnings = Args.hasArg(OPT_massembler_fatal_warnings);
Opts.NoWarn = Args.hasArg(OPT_massembler_no_warn);
Opts.RelocationModel = Args.getLastArgValue(OPT_mrelocation_model, "pic");
Opts.TargetABI = Args.getLastArgValue(OPT_target_abi);
Opts.IncrementalLinkerCompatible =
Args.hasArg(OPT_mincremental_linker_compatible);
Opts.SymbolDefs = Args.getAllArgValues(OPT_defsym);
// EmbedBitcode Option. If -fembed-bitcode is enabled, set the flag.
// EmbedBitcode behaves the same for all embed options for assembly files.
if (auto *A = Args.getLastArg(OPT_fembed_bitcode_EQ)) {
Opts.EmbedBitcode = llvm::StringSwitch<unsigned>(A->getValue())
.Case("all", 1)
.Case("bitcode", 1)
.Case("marker", 1)
.Default(0);
}
return Success;
}
static std::unique_ptr<raw_fd_ostream>
getOutputStream(StringRef Path, DiagnosticsEngine &Diags, bool Binary) {
// Make sure that the Out file gets unlinked from the disk if we get a
// SIGINT.
if (Path != "-")
sys::RemoveFileOnSignal(Path);
std::error_code EC;
auto Out = std::make_unique<raw_fd_ostream>(
Path, EC, (Binary ? sys::fs::OF_None : sys::fs::OF_Text));
if (EC) {
Diags.Report(diag::err_fe_unable_to_open_output) << Path << EC.message();
return nullptr;
}
return Out;
}
bool ExecuteAssembler(AssemblerInvocation &Opts, DiagnosticsEngine &Diags) {
// Get the target specific parser.
std::string Error;
const Target *TheTarget = TargetRegistry::lookupTarget(Opts.Triple, Error);
if (!TheTarget)
return Diags.Report(diag::err_target_unknown_triple) << Opts.Triple;
ErrorOr<std::unique_ptr<MemoryBuffer>> Buffer =
MemoryBuffer::getFileOrSTDIN(Opts.InputFile);
if (std::error_code EC = Buffer.getError()) {
Error = EC.message();
return Diags.Report(diag::err_fe_error_reading) << Opts.InputFile;
}
SourceMgr SrcMgr;
// Tell SrcMgr about this buffer, which is what the parser will pick up.
unsigned BufferIndex = SrcMgr.AddNewSourceBuffer(std::move(*Buffer), SMLoc());
// Record the location of the include directories so that the lexer can find
// it later.
SrcMgr.setIncludeDirs(Opts.IncludePaths);
std::unique_ptr<MCRegisterInfo> MRI(TheTarget->createMCRegInfo(Opts.Triple));
assert(MRI && "Unable to create target register info!");
MCTargetOptions MCOptions;
std::unique_ptr<MCAsmInfo> MAI(
TheTarget->createMCAsmInfo(*MRI, Opts.Triple, MCOptions));
assert(MAI && "Unable to create target asm info!");
// Ensure MCAsmInfo initialization occurs before any use, otherwise sections
// may be created with a combination of default and explicit settings.
MAI->setCompressDebugSections(Opts.CompressDebugSections);
MAI->setRelaxELFRelocations(Opts.RelaxELFRelocations);
bool IsBinary = Opts.OutputType == AssemblerInvocation::FT_Obj;
if (Opts.OutputPath.empty())
Opts.OutputPath = "-";
std::unique_ptr<raw_fd_ostream> FDOS =
getOutputStream(Opts.OutputPath, Diags, IsBinary);
if (!FDOS)
return true;
std::unique_ptr<raw_fd_ostream> DwoOS;
if (!Opts.SplitDwarfOutput.empty())
DwoOS = getOutputStream(Opts.SplitDwarfOutput, Diags, IsBinary);
// FIXME: This is not pretty. MCContext has a ptr to MCObjectFileInfo and
// MCObjectFileInfo needs a MCContext reference in order to initialize itself.
std::unique_ptr<MCObjectFileInfo> MOFI(new MCObjectFileInfo());
MCContext Ctx(MAI.get(), MRI.get(), MOFI.get(), &SrcMgr, &MCOptions);
bool PIC = false;
if (Opts.RelocationModel == "static") {
PIC = false;
} else if (Opts.RelocationModel == "pic") {
PIC = true;
} else {
assert(Opts.RelocationModel == "dynamic-no-pic" &&
"Invalid PIC model!");
PIC = false;
}
MOFI->InitMCObjectFileInfo(Triple(Opts.Triple), PIC, Ctx);
if (Opts.SaveTemporaryLabels)
Ctx.setAllowTemporaryLabels(false);
if (Opts.GenDwarfForAssembly)
Ctx.setGenDwarfForAssembly(true);
if (!Opts.DwarfDebugFlags.empty())
Ctx.setDwarfDebugFlags(StringRef(Opts.DwarfDebugFlags));
if (!Opts.DwarfDebugProducer.empty())
Ctx.setDwarfDebugProducer(StringRef(Opts.DwarfDebugProducer));
if (!Opts.DebugCompilationDir.empty())
Ctx.setCompilationDir(Opts.DebugCompilationDir);
else {
// If no compilation dir is set, try to use the current directory.
SmallString<128> CWD;
if (!sys::fs::current_path(CWD))
Ctx.setCompilationDir(CWD);
}
if (!Opts.DebugPrefixMap.empty())
for (const auto &KV : Opts.DebugPrefixMap)
Ctx.addDebugPrefixMapEntry(KV.first, KV.second);
if (!Opts.MainFileName.empty())
Ctx.setMainFileName(StringRef(Opts.MainFileName));
Ctx.setDwarfVersion(Opts.DwarfVersion);
if (Opts.GenDwarfForAssembly)
Ctx.setGenDwarfRootFile(Opts.InputFile,
SrcMgr.getMemoryBuffer(BufferIndex)->getBuffer());
// Build up the feature string from the target feature list.
std::string FS;
if (!Opts.Features.empty()) {
FS = Opts.Features[0];
for (unsigned i = 1, e = Opts.Features.size(); i != e; ++i)
FS += "," + Opts.Features[i];
}
std::unique_ptr<MCStreamer> Str;
std::unique_ptr<MCInstrInfo> MCII(TheTarget->createMCInstrInfo());
std::unique_ptr<MCSubtargetInfo> STI(
TheTarget->createMCSubtargetInfo(Opts.Triple, Opts.CPU, FS));
raw_pwrite_stream *Out = FDOS.get();
std::unique_ptr<buffer_ostream> BOS;
MCOptions.MCNoWarn = Opts.NoWarn;
MCOptions.MCFatalWarnings = Opts.FatalWarnings;
MCOptions.ABIName = Opts.TargetABI;
// FIXME: There is a bit of code duplication with addPassesToEmitFile.
if (Opts.OutputType == AssemblerInvocation::FT_Asm) {
MCInstPrinter *IP = TheTarget->createMCInstPrinter(
llvm::Triple(Opts.Triple), Opts.OutputAsmVariant, *MAI, *MCII, *MRI);
std::unique_ptr<MCCodeEmitter> CE;
if (Opts.ShowEncoding)
CE.reset(TheTarget->createMCCodeEmitter(*MCII, *MRI, Ctx));
std::unique_ptr<MCAsmBackend> MAB(
TheTarget->createMCAsmBackend(*STI, *MRI, MCOptions));
auto FOut = std::make_unique<formatted_raw_ostream>(*Out);
Str.reset(TheTarget->createAsmStreamer(
Ctx, std::move(FOut), /*asmverbose*/ true,
/*useDwarfDirectory*/ true, IP, std::move(CE), std::move(MAB),
Opts.ShowInst));
} else if (Opts.OutputType == AssemblerInvocation::FT_Null) {
Str.reset(createNullStreamer(Ctx));
} else {
assert(Opts.OutputType == AssemblerInvocation::FT_Obj &&
"Invalid file type!");
if (!FDOS->supportsSeeking()) {
BOS = std::make_unique<buffer_ostream>(*FDOS);
Out = BOS.get();
}
std::unique_ptr<MCCodeEmitter> CE(
TheTarget->createMCCodeEmitter(*MCII, *MRI, Ctx));
std::unique_ptr<MCAsmBackend> MAB(
TheTarget->createMCAsmBackend(*STI, *MRI, MCOptions));
std::unique_ptr<MCObjectWriter> OW =
DwoOS ? MAB->createDwoObjectWriter(*Out, *DwoOS)
: MAB->createObjectWriter(*Out);
Triple T(Opts.Triple);
Str.reset(TheTarget->createMCObjectStreamer(
T, Ctx, std::move(MAB), std::move(OW), std::move(CE), *STI,
Opts.RelaxAll, Opts.IncrementalLinkerCompatible,
/*DWARFMustBeAtTheEnd*/ true));
Str.get()->InitSections(Opts.NoExecStack);
}
// When -fembed-bitcode is passed to clang_as, a 1-byte marker
// is emitted in __LLVM,__asm section if the object file is MachO format.
if (Opts.EmbedBitcode && Ctx.getObjectFileInfo()->getObjectFileType() ==
MCObjectFileInfo::IsMachO) {
MCSection *AsmLabel = Ctx.getMachOSection(
"__LLVM", "__asm", MachO::S_REGULAR, 4, SectionKind::getReadOnly());
Str.get()->SwitchSection(AsmLabel);
Str.get()->EmitZeros(1);
}
// Assembly to object compilation should leverage assembly info.
Str->setUseAssemblerInfoForParsing(true);
bool Failed = false;
std::unique_ptr<MCAsmParser> Parser(
createMCAsmParser(SrcMgr, Ctx, *Str.get(), *MAI));
// FIXME: init MCTargetOptions from sanitizer flags here.
std::unique_ptr<MCTargetAsmParser> TAP(
TheTarget->createMCAsmParser(*STI, *Parser, *MCII, MCOptions));
if (!TAP)
Failed = Diags.Report(diag::err_target_unknown_triple) << Opts.Triple;
// Set values for symbols, if any.
for (auto &S : Opts.SymbolDefs) {
auto Pair = StringRef(S).split('=');
auto Sym = Pair.first;
auto Val = Pair.second;
int64_t Value;
// We have already error checked this in the driver.
Val.getAsInteger(0, Value);
Ctx.setSymbolValue(Parser->getStreamer(), Sym, Value);
}
if (!Failed) {
Parser->setTargetParser(*TAP.get());
Failed = Parser->Run(Opts.NoInitialTextSection);
}
// Close Streamer first.
// It might have a reference to the output stream.
Str.reset();
// Close the output stream early.
BOS.reset();
FDOS.reset();
// Delete output file if there were errors.
if (Failed) {
if (Opts.OutputPath != "-")
sys::fs::remove(Opts.OutputPath);
if (!Opts.SplitDwarfOutput.empty() && Opts.SplitDwarfOutput != "-")
sys::fs::remove(Opts.SplitDwarfOutput);
}
return Failed;
}
static void LLVMErrorHandler(void *UserData, const std::string &Message,
bool GenCrashDiag) {
DiagnosticsEngine &Diags = *static_cast<DiagnosticsEngine*>(UserData);
Diags.Report(diag::err_fe_error_backend) << Message;
// We cannot recover from llvm errors.
sys::Process::Exit(1);
}
int cc1as_main(ArrayRef<const char *> Argv, const char *Argv0, void *MainAddr) {
// Initialize targets and assembly printers/parsers.
InitializeAllTargetInfos();
InitializeAllTargetMCs();
InitializeAllAsmParsers();
// Construct our diagnostic client.
IntrusiveRefCntPtr<DiagnosticOptions> DiagOpts = new DiagnosticOptions();
TextDiagnosticPrinter *DiagClient
= new TextDiagnosticPrinter(errs(), &*DiagOpts);
DiagClient->setPrefix("clang -cc1as");
IntrusiveRefCntPtr<DiagnosticIDs> DiagID(new DiagnosticIDs());
DiagnosticsEngine Diags(DiagID, &*DiagOpts, DiagClient);
// Set an error handler, so that any LLVM backend diagnostics go through our
// error handler.
ScopedFatalErrorHandler FatalErrorHandler
(LLVMErrorHandler, static_cast<void*>(&Diags));
// Parse the arguments.
AssemblerInvocation Asm;
if (!AssemblerInvocation::CreateFromArgs(Asm, Argv, Diags))
return 1;
if (Asm.ShowHelp) {
getDriverOptTable().PrintHelp(
llvm::outs(), "clang -cc1as [options] file...",
"Clang Integrated Assembler",
/*Include=*/driver::options::CC1AsOption, /*Exclude=*/0,
/*ShowAllAliases=*/false);
return 0;
}
// Honor -version.
//
// FIXME: Use a better -version message?
if (Asm.ShowVersion) {
llvm::cl::PrintVersionMessage();
return 0;
}
// Honor -mllvm.
//
// FIXME: Remove this, one day.
if (!Asm.LLVMArgs.empty()) {
unsigned NumArgs = Asm.LLVMArgs.size();
auto Args = std::make_unique<const char*[]>(NumArgs + 2);
Args[0] = "clang (LLVM option parsing)";
for (unsigned i = 0; i != NumArgs; ++i)
Args[i + 1] = Asm.LLVMArgs[i].c_str();
Args[NumArgs + 1] = nullptr;
llvm::cl::ParseCommandLineOptions(NumArgs + 1, Args.get());
}
// Execute the invocation, unless there were parsing errors.
bool Failed = Diags.hasErrorOccurred() || ExecuteAssembler(Asm, Diags);
// If any timers were active but haven't been destroyed yet, print their
// results now.
TimerGroup::printAll(errs());
TimerGroup::clearAll();
return !!Failed;
}
-120
View File
@@ -1,120 +0,0 @@
//===-- cc1as.h - Clang Assembler ----------------------------------------===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
//
// This is the entry point to the clang -cc1as functionality, which implements
// the direct interface to the LLVM MC based assembler.
//
//===----------------------------------------------------------------------===//
/// Helper class for representing a single invocation of the assembler.
struct AssemblerInvocation {
/// @name Target Options
/// @{
/// The name of the target triple to assemble for.
std::string Triple;
/// If given, the name of the target CPU to determine which instructions
/// are legal.
std::string CPU;
/// The list of target specific features to enable or disable -- this should
/// be a list of strings starting with '+' or '-'.
std::vector<std::string> Features;
/// The list of symbol definitions.
std::vector<std::string> SymbolDefs;
/// @}
/// @name Language Options
/// @{
std::vector<std::string> IncludePaths;
unsigned NoInitialTextSection : 1;
unsigned SaveTemporaryLabels : 1;
unsigned GenDwarfForAssembly : 1;
unsigned RelaxELFRelocations : 1;
unsigned DwarfVersion;
std::string DwarfDebugFlags;
std::string DwarfDebugProducer;
std::string DebugCompilationDir;
std::map<const std::string, const std::string> DebugPrefixMap;
llvm::DebugCompressionType CompressDebugSections =
llvm::DebugCompressionType::None;
std::string MainFileName;
std::string SplitDwarfOutput;
/// @}
/// @name Frontend Options
/// @{
std::string InputFile;
std::vector<std::string> LLVMArgs;
std::string OutputPath;
enum FileType {
FT_Asm, ///< Assembly (.s) output, transliterate mode.
FT_Null, ///< No output, for timing purposes.
FT_Obj ///< Object file output.
};
FileType OutputType;
unsigned ShowHelp : 1;
unsigned ShowVersion : 1;
/// @}
/// @name Transliterate Options
/// @{
unsigned OutputAsmVariant;
unsigned ShowEncoding : 1;
unsigned ShowInst : 1;
/// @}
/// @name Assembler Options
/// @{
unsigned RelaxAll : 1;
unsigned NoExecStack : 1;
unsigned FatalWarnings : 1;
unsigned NoWarn : 1;
unsigned IncrementalLinkerCompatible : 1;
unsigned EmbedBitcode : 1;
/// The name of the relocation model to use.
std::string RelocationModel;
/// The ABI targeted by the backend. Specified using -target-abi. Empty
/// otherwise.
std::string TargetABI;
/// @}
public:
AssemblerInvocation() {
Triple = "";
NoInitialTextSection = 0;
InputFile = "-";
OutputPath = "-";
OutputType = FT_Asm;
OutputAsmVariant = 0;
ShowInst = 0;
ShowEncoding = 0;
RelaxAll = 0;
NoExecStack = 0;
FatalWarnings = 0;
NoWarn = 0;
IncrementalLinkerCompatible = 0;
DwarfVersion = 0;
EmbedBitcode = 0;
}
static bool CreateFromArgs(AssemblerInvocation &Res,
ArrayRef<const char *> Argv,
DiagnosticsEngine &Diags);
};
bool ExecuteAssembler(AssemblerInvocation &Opts, DiagnosticsEngine &Diags);
-97
View File
@@ -1,97 +0,0 @@
// +build byollvm
#include <clang/Basic/DiagnosticOptions.h>
#include <clang/CodeGen/CodeGenAction.h>
#include <clang/Driver/Compilation.h>
#include <clang/Driver/Driver.h>
#include <clang/Frontend/CompilerInstance.h>
#include <clang/Frontend/CompilerInvocation.h>
#include <clang/Frontend/FrontendDiagnostic.h>
#include <clang/Frontend/TextDiagnosticPrinter.h>
#include <clang/FrontendTool/Utils.h>
#include <llvm/ADT/IntrusiveRefCntPtr.h>
#include <llvm/Option/Option.h>
using namespace llvm;
using namespace clang;
#include "cc1as.h"
// This file provides C wrappers for the builtin tools cc1 and cc1as
// provided by Clang, and calls them as the driver would call them.
extern "C" {
bool tinygo_clang_driver(int argc, char **argv) {
std::vector<const char*> args(argv, argv + argc);
// The compiler invocation needs a DiagnosticsEngine so it can report problems
llvm::IntrusiveRefCntPtr<clang::DiagnosticOptions> DiagOpts = new clang::DiagnosticOptions();
clang::TextDiagnosticPrinter DiagnosticPrinter(llvm::errs(), &*DiagOpts);
clang::DiagnosticsEngine Diags(llvm::IntrusiveRefCntPtr<clang::DiagnosticIDs>(new clang::DiagnosticIDs()), &*DiagOpts, &DiagnosticPrinter, false);
// Create the clang driver
clang::driver::Driver TheDriver(args[0], llvm::sys::getDefaultTargetTriple(), Diags);
// Create the set of actions to perform
std::unique_ptr<clang::driver::Compilation> C(TheDriver.BuildCompilation(args));
if (!C) {
return false;
}
const clang::driver::JobList &Jobs = C->getJobs();
// There may be more than one job, for example for .S files
// (preprocessor + assembler).
for (auto Cmd : Jobs) {
// Select the tool: cc1 or cc1as.
const llvm::opt::ArgStringList &CCArgs = Cmd.getArguments();
if (strcmp(*CCArgs.data(), "-cc1") == 0) {
// This is the C frontend.
// Initialize a compiler invocation object from the clang (-cc1) arguments.
std::unique_ptr<clang::CompilerInstance> Clang(new clang::CompilerInstance());
bool success = clang::CompilerInvocation::CreateFromArgs(
Clang->getInvocation(),
CCArgs,
Diags);
if (!success) {
return false;
}
// Create the actual diagnostics engine.
Clang->createDiagnostics();
if (!Clang->hasDiagnostics()) {
return false;
}
// Execute the frontend actions.
success = ExecuteCompilerInvocation(Clang.get());
if (!success) {
return false;
}
} else if (strcmp(*CCArgs.data(), "-cc1as") == 0) {
// This is the assembler frontend. Parse the arguments.
AssemblerInvocation Asm;
ArrayRef<const char *> Argv = llvm::ArrayRef<const char*>(CCArgs);
if (!AssemblerInvocation::CreateFromArgs(Asm, Argv.slice(1), Diags))
return false;
// Execute the invocation, unless there were parsing errors.
bool failed = Diags.hasErrorOccurred() || ExecuteAssembler(Asm, Diags);
if (failed) {
return false;
}
} else {
// Unknown tool, print the tool and exit.
fprintf(stderr, "unknown tool: %s\n", *CCArgs.data());
return false;
}
}
// Commands executed successfully.
return true;
}
} // extern "C"
-65
View File
@@ -1,65 +0,0 @@
package builder
import (
"errors"
"os"
"os/exec"
"runtime"
"strings"
"tinygo.org/x/go-llvm"
)
// Commands lists command alternatives for various operating systems. These
// commands may have a slightly different name across operating systems and
// distributions or may not even exist in $PATH, in which case absolute paths
// may be used.
var commands = map[string][]string{}
func init() {
llvmMajor := strings.Split(llvm.Version, ".")[0]
commands["clang"] = []string{"clang-" + llvmMajor}
commands["ld.lld"] = []string{"ld.lld-" + llvmMajor, "ld.lld"}
commands["wasm-ld"] = []string{"wasm-ld-" + llvmMajor, "wasm-ld"}
// Add the path to a Homebrew-installed LLVM for ease of use (no need to
// manually set $PATH).
if runtime.GOOS == "darwin" {
prefix := "/usr/local/opt/llvm@" + llvmMajor + "/bin/"
commands["clang"] = append(commands["clang"], prefix+"clang-"+llvmMajor)
commands["ld.lld"] = append(commands["ld.lld"], prefix+"ld.lld")
commands["wasm-ld"] = append(commands["wasm-ld"], prefix+"wasm-ld")
}
// 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
// variable.
if runtime.GOOS == "windows" {
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["wasm-ld"] = append(commands["wasm-ld"], "C:\\Program Files\\LLVM\\bin\\wasm-ld.exe")
}
// Add the path to LLVM installed from ports.
if runtime.GOOS == "freebsd" {
prefix := "/usr/local/llvm" + llvmMajor + "/bin/"
commands["clang"] = append(commands["clang"], prefix+"clang-"+llvmMajor)
commands["ld.lld"] = append(commands["ld.lld"], prefix+"ld.lld")
commands["wasm-ld"] = append(commands["wasm-ld"], prefix+"wasm-ld")
}
}
func execCommand(cmdNames []string, args ...string) error {
for _, cmdName := range cmdNames {
cmd := exec.Command(cmdName, args...)
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
err := cmd.Run()
if err != nil {
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
}
return err
}
return nil
}
return errors.New("none of these commands were found in your $PATH: " + strings.Join(cmdNames, " "))
}
-39
View File
@@ -1,39 +0,0 @@
package builder
import (
"errors"
"fmt"
"github.com/tinygo-org/tinygo/compileopts"
"github.com/tinygo-org/tinygo/goenv"
)
// NewConfig builds a new Config object from a set of compiler options. It also
// loads some information from the environment while doing that. For example, it
// uses the currently active GOPATH (from the goenv package) to determine the Go
// version to use.
func NewConfig(options *compileopts.Options) (*compileopts.Config, error) {
spec, err := compileopts.LoadTarget(options.Target)
if err != nil {
return nil, err
}
goroot := goenv.Get("GOROOT")
if goroot == "" {
return nil, errors.New("cannot locate $GOROOT, please set it manually")
}
major, minor, err := goenv.GetGorootVersion(goroot)
if err != nil {
return nil, fmt.Errorf("could not read version from GOROOT (%v): %v", goroot, err)
}
if major != 1 || minor < 11 || minor > 14 {
return nil, fmt.Errorf("requires go version 1.11, 1.12, 1.13, or 1.14, got go%d.%d", major, minor)
}
clangHeaderPath := getClangHeaderPath(goenv.Get("TINYGOROOT"))
return &compileopts.Config{
Options: options,
Target: spec,
GoMinorVersion: minor,
ClangHeaders: clangHeaderPath,
TestConfig: options.TestConfig,
}, nil
}
-69
View File
@@ -1,69 +0,0 @@
package builder
import (
"io/ioutil"
"os"
"os/exec"
"path/filepath"
"sort"
)
// getClangHeaderPath returns the path to the built-in Clang headers. It tries
// multiple locations, which should make it find the directory when installed in
// various ways.
func getClangHeaderPath(TINYGOROOT string) string {
// Check whether we're running from the source directory.
path := filepath.Join(TINYGOROOT, "llvm-project", "clang", "lib", "Headers")
if _, err := os.Stat(path); !os.IsNotExist(err) {
return path
}
// Check whether we're running from the installation directory.
path = filepath.Join(TINYGOROOT, "lib", "clang", "include")
if _, err := os.Stat(path); !os.IsNotExist(err) {
return path
}
// It looks like we are built with a system-installed LLVM. Do a last
// attempt: try to use Clang headers relative to the clang binary.
for _, cmdName := range commands["clang"] {
binpath, err := exec.LookPath(cmdName)
if err == nil {
// This should be the command that will also be used by
// execCommand. To avoid inconsistencies, make sure we use the
// headers relative to this command.
binpath, err = filepath.EvalSymlinks(binpath)
if err != nil {
// Unexpected.
return ""
}
// Example executable:
// /usr/lib/llvm-9/bin/clang
// Example include path:
// /usr/lib/llvm-9/lib/clang/9.0.1/include/
llvmRoot := filepath.Dir(filepath.Dir(binpath))
clangVersionRoot := filepath.Join(llvmRoot, "lib", "clang")
dirs, err := ioutil.ReadDir(clangVersionRoot)
if err != nil {
// Unexpected.
continue
}
dirnames := make([]string, len(dirs))
for i, d := range dirs {
dirnames[i] = d.Name()
}
sort.Strings(dirnames)
// Check for the highest version first.
for i := len(dirnames) - 1; i >= 0; i-- {
path := filepath.Join(clangVersionRoot, dirnames[i], "include")
_, err := os.Stat(filepath.Join(path, "stdint.h"))
if err == nil {
return path
}
}
}
}
// Could not find it.
return ""
}
-39
View File
@@ -1,39 +0,0 @@
package builder
// MultiError is a list of multiple errors (actually: diagnostics) returned
// during LLVM IR generation.
type MultiError struct {
Errs []error
}
func (e *MultiError) Error() string {
// Return the first error, to conform to the error interface. Clients should
// really do a type-assertion on *MultiError.
return e.Errs[0].Error()
}
// newMultiError returns a *MultiError if there is more than one error, or
// returns that error directly when there is only one. Passing an empty slice
// will lead to a panic.
func newMultiError(errs []error) error {
switch len(errs) {
case 0:
panic("attempted to create empty MultiError")
case 1:
return errs[0]
default:
return &MultiError{errs}
}
}
// commandError is an error type to wrap os/exec.Command errors. This provides
// some more information regarding what went wrong while running a command.
type commandError struct {
Msg string
File string
Err error
}
func (e *commandError) Error() string {
return e.Msg + " " + e.File + ": " + e.Err.Error()
}
-102
View File
@@ -1,102 +0,0 @@
package builder
import (
"io/ioutil"
"os"
"path/filepath"
"strings"
"github.com/tinygo-org/tinygo/goenv"
)
// Library is a container for information about a single C library, such as a
// compiler runtime or libc.
type Library struct {
// The library name, such as compiler-rt or picolibc.
name string
cflags func() []string
// The source directory, relative to TINYGOROOT.
sourceDir string
// The source files, relative to sourceDir.
sources func(target string) []string
}
// fullPath returns the full path to the source directory.
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.
func (l *Library) Load(target string) (path string, err error) {
// Try to load a precompiled library.
precompiledPath := filepath.Join(goenv.Get("TINYGOROOT"), "pkg", target, l.name+".a")
if _, err := os.Stat(precompiledPath); err == nil {
// Found a precompiled library for this OS/architecture. Return the path
// directly.
return precompiledPath, nil
}
outfile := l.name + "-" + target + ".a"
// Try to fetch this library from the cache.
if path, err := cacheLoad(outfile, commands["clang"][0], l.sourcePaths(target)); path != "" || err != nil {
// Cache hit.
return path, err
}
// Cache miss, build it now.
dirPrefix := "tinygo-" + l.name
remapDir := filepath.Join(os.TempDir(), dirPrefix)
dir, err := ioutil.TempDir(os.TempDir(), dirPrefix)
if err != nil {
return "", err
}
defer os.RemoveAll(dir)
// Precalculate the flags to the compiler invocation.
args := append(l.cflags(), "-c", "-Oz", "-g", "-ffunction-sections", "-fdata-sections", "-Wno-macro-redefined", "--target="+target, "-fdebug-prefix-map="+dir+"="+remapDir)
if strings.HasPrefix(target, "arm") || strings.HasPrefix(target, "thumb") {
args = append(args, "-fshort-enums", "-fomit-frame-pointer")
}
if strings.HasPrefix(target, "riscv32-") {
args = append(args, "-march=rv32imac", "-mabi=ilp32", "-fforce-enable-int128")
}
// Compile all sources.
var objs []string
for _, srcpath := range l.sourcePaths(target) {
objpath := filepath.Join(dir, filepath.Base(srcpath)+".o")
objs = append(objs, objpath)
// Note: -fdebug-prefix-map is necessary to make the output archive
// reproducible. Otherwise the temporary directory is stored in the
// archive itself, which varies each run.
err := runCCompiler("clang", append(args, "-o", objpath, srcpath)...)
if err != nil {
return "", &commandError{"failed to build", srcpath, err}
}
}
// Put all the object files in a single archive. This archive file will be
// used to statically link this library.
arpath := filepath.Join(dir, l.name+".a")
err = makeArchive(arpath, objs)
if err != nil {
return "", err
}
// Store this archive in the cache.
return cacheStore(arpath, outfile, commands["clang"][0], l.sourcePaths(target))
}
-127
View File
@@ -1,127 +0,0 @@
package builder
import (
"path/filepath"
"github.com/tinygo-org/tinygo/goenv"
)
// Picolibc is a C library for bare metal embedded devices. It was originally
// based on newlib.
var Picolibc = Library{
name: "picolibc",
cflags: func() []string {
picolibcDir := filepath.Join(goenv.Get("TINYGOROOT"), "lib/picolibc/newlib/libc")
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"}
},
sourceDir: "lib/picolibc/newlib/libc",
sources: func(target string) []string {
return picolibcSources
},
}
var picolibcSources = []string{
"string/bcmp.c",
"string/bcopy.c",
"string/bzero.c",
"string/explicit_bzero.c",
"string/ffsl.c",
"string/ffsll.c",
"string/fls.c",
"string/flsl.c",
"string/flsll.c",
"string/gnu_basename.c",
"string/index.c",
"string/memccpy.c",
"string/memchr.c",
"string/memcmp.c",
"string/memcpy.c",
"string/memmem.c",
"string/memmove.c",
"string/mempcpy.c",
"string/memrchr.c",
"string/memset.c",
"string/rawmemchr.c",
"string/rindex.c",
"string/stpcpy.c",
"string/stpncpy.c",
"string/strcasecmp.c",
"string/strcasecmp_l.c",
"string/strcasestr.c",
"string/strcat.c",
"string/strchr.c",
"string/strchrnul.c",
"string/strcmp.c",
"string/strcoll.c",
"string/strcoll_l.c",
"string/strcpy.c",
"string/strcspn.c",
"string/strdup.c",
"string/strerror.c",
"string/strerror_r.c",
"string/strlcat.c",
"string/strlcpy.c",
"string/strlen.c",
"string/strlwr.c",
"string/strncasecmp.c",
"string/strncasecmp_l.c",
"string/strncat.c",
"string/strncmp.c",
"string/strncpy.c",
"string/strndup.c",
"string/strnlen.c",
"string/strnstr.c",
"string/strpbrk.c",
"string/strrchr.c",
"string/strsep.c",
"string/strsignal.c",
"string/strspn.c",
"string/strstr.c",
"string/strtok.c",
"string/strtok_r.c",
"string/strupr.c",
"string/strverscmp.c",
"string/strxfrm.c",
"string/strxfrm_l.c",
"string/swab.c",
"string/timingsafe_bcmp.c",
"string/timingsafe_memcmp.c",
"string/u_strerr.c",
"string/wcpcpy.c",
"string/wcpncpy.c",
"string/wcscasecmp.c",
"string/wcscasecmp_l.c",
"string/wcscat.c",
"string/wcschr.c",
"string/wcscmp.c",
"string/wcscoll.c",
"string/wcscoll_l.c",
"string/wcscpy.c",
"string/wcscspn.c",
"string/wcsdup.c",
"string/wcslcat.c",
"string/wcslcpy.c",
"string/wcslen.c",
"string/wcsncasecmp.c",
"string/wcsncasecmp_l.c",
"string/wcsncat.c",
"string/wcsncmp.c",
"string/wcsncpy.c",
"string/wcsnlen.c",
"string/wcspbrk.c",
"string/wcsrchr.c",
"string/wcsspn.c",
"string/wcsstr.c",
"string/wcstok.c",
"string/wcswidth.c",
"string/wcsxfrm.c",
"string/wcsxfrm_l.c",
"string/wcwidth.c",
"string/wmemchr.c",
"string/wmemcmp.c",
"string/wmemcpy.c",
"string/wmemmove.c",
"string/wmempcpy.c",
"string/wmemset.c",
"string/xpg_strerror_r.c",
}
-54
View File
@@ -1,54 +0,0 @@
// +build byollvm
package builder
import (
"errors"
"unsafe"
)
/*
#cgo CXXFLAGS: -fno-rtti
#include <stdbool.h>
#include <stdlib.h>
bool tinygo_clang_driver(int argc, char **argv);
bool tinygo_link_elf(int argc, char **argv);
bool tinygo_link_wasm(int argc, char **argv);
*/
import "C"
const hasBuiltinTools = true
// RunTool runs the given tool (such as clang).
//
// This version actually runs the tools because TinyGo was compiled while
// linking statically with LLVM (with the byollvm build tag).
func RunTool(tool string, args ...string) error {
args = append([]string{"tinygo:" + tool}, args...)
var cflag *C.char
buf := C.calloc(C.size_t(len(args)), C.size_t(unsafe.Sizeof(cflag)))
defer C.free(buf)
cflags := (*[1 << 10]*C.char)(unsafe.Pointer(buf))[:len(args):len(args)]
for i, flag := range args {
cflag := C.CString(flag)
cflags[i] = cflag
defer C.free(unsafe.Pointer(cflag))
}
var ok C.bool
switch tool {
case "clang":
ok = C.tinygo_clang_driver(C.int(len(args)), (**C.char)(buf))
case "ld.lld":
ok = C.tinygo_link_elf(C.int(len(args)), (**C.char)(buf))
case "wasm-ld":
ok = C.tinygo_link_wasm(C.int(len(args)), (**C.char)(buf))
default:
return errors.New("unknown tool: " + tool)
}
if !ok {
return errors.New("failed to run tool: " + tool)
}
return nil
}
-15
View File
@@ -1,15 +0,0 @@
// +build !byollvm
package builder
import "errors"
const hasBuiltinTools = false
// RunTool runs the given tool (such as clang).
//
// This version doesn't actually run the tool: TinyGo has not been compiled by
// statically linking to LLVM.
func RunTool(tool string, args ...string) error {
return errors.New("cannot run tool: " + tool)
}
-59
View File
@@ -1,59 +0,0 @@
package builder
import (
"errors"
"os"
"os/exec"
"github.com/tinygo-org/tinygo/goenv"
)
// runCCompiler invokes a C compiler with the given arguments.
func runCCompiler(command string, flags ...string) error {
if hasBuiltinTools && command == "clang" {
// Compile this with the internal Clang compiler.
headerPath := getClangHeaderPath(goenv.Get("TINYGOROOT"))
if headerPath == "" {
return errors.New("could not locate Clang headers")
}
flags = append(flags, "-I"+headerPath)
cmd := exec.Command(os.Args[0], append([]string{"clang"}, flags...)...)
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
return cmd.Run()
}
// Running some other compiler. Maybe it has been defined in the
// 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.
func link(linker string, flags ...string) error {
if hasBuiltinTools && (linker == "ld.lld" || linker == "wasm-ld") {
// Run command with internal linker.
cmd := exec.Command(os.Args[0], append([]string{linker}, flags...)...)
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
return cmd.Run()
}
// Fall back to external command.
if cmdNames, ok := commands[linker]; ok {
return execCommand(cmdNames, flags...)
}
cmd := exec.Command(linker, flags...)
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
cmd.Dir = goenv.Get("TINYGOROOT")
return cmd.Run()
}
+294
View File
@@ -0,0 +1,294 @@
package main
import (
"errors"
"io"
"io/ioutil"
"os"
"path/filepath"
"strings"
"time"
"github.com/blakesmith/ar"
)
// These are the GENERIC_SOURCES according to CMakeList.txt.
var genericBuiltins = []string{
"absvdi2.c",
"absvsi2.c",
"absvti2.c",
"adddf3.c",
"addsf3.c",
"addtf3.c",
"addvdi3.c",
"addvsi3.c",
"addvti3.c",
"apple_versioning.c",
"ashldi3.c",
"ashlti3.c",
"ashrdi3.c",
"ashrti3.c",
"bswapdi2.c",
"bswapsi2.c",
"clzdi2.c",
"clzsi2.c",
"clzti2.c",
"cmpdi2.c",
"cmpti2.c",
"comparedf2.c",
"comparesf2.c",
"ctzdi2.c",
"ctzsi2.c",
"ctzti2.c",
"divdc3.c",
"divdf3.c",
"divdi3.c",
"divmoddi4.c",
"divmodsi4.c",
"divsc3.c",
"divsf3.c",
"divsi3.c",
"divtc3.c",
"divti3.c",
"divtf3.c",
"extendsfdf2.c",
"extendhfsf2.c",
"ffsdi2.c",
"ffssi2.c",
"ffsti2.c",
"fixdfdi.c",
"fixdfsi.c",
"fixdfti.c",
"fixsfdi.c",
"fixsfsi.c",
"fixsfti.c",
"fixunsdfdi.c",
"fixunsdfsi.c",
"fixunsdfti.c",
"fixunssfdi.c",
"fixunssfsi.c",
"fixunssfti.c",
"floatdidf.c",
"floatdisf.c",
"floatsidf.c",
"floatsisf.c",
"floattidf.c",
"floattisf.c",
"floatundidf.c",
"floatundisf.c",
"floatunsidf.c",
"floatunsisf.c",
"floatuntidf.c",
"floatuntisf.c",
//"int_util.c",
"lshrdi3.c",
"lshrti3.c",
"moddi3.c",
"modsi3.c",
"modti3.c",
"muldc3.c",
"muldf3.c",
"muldi3.c",
"mulodi4.c",
"mulosi4.c",
"muloti4.c",
"mulsc3.c",
"mulsf3.c",
"multi3.c",
"multf3.c",
"mulvdi3.c",
"mulvsi3.c",
"mulvti3.c",
"negdf2.c",
"negdi2.c",
"negsf2.c",
"negti2.c",
"negvdi2.c",
"negvsi2.c",
"negvti2.c",
"os_version_check.c",
"paritydi2.c",
"paritysi2.c",
"parityti2.c",
"popcountdi2.c",
"popcountsi2.c",
"popcountti2.c",
"powidf2.c",
"powisf2.c",
"powitf2.c",
"subdf3.c",
"subsf3.c",
"subvdi3.c",
"subvsi3.c",
"subvti3.c",
"subtf3.c",
"trampoline_setup.c",
"truncdfhf2.c",
"truncdfsf2.c",
"truncsfhf2.c",
"ucmpdi2.c",
"ucmpti2.c",
"udivdi3.c",
"udivmoddi4.c",
"udivmodsi4.c",
"udivmodti4.c",
"udivsi3.c",
"udivti3.c",
"umoddi3.c",
"umodsi3.c",
"umodti3.c",
}
var aeabiBuiltins = []string{
"arm/aeabi_cdcmp.S",
"arm/aeabi_cdcmpeq_check_nan.c",
"arm/aeabi_cfcmp.S",
"arm/aeabi_cfcmpeq_check_nan.c",
"arm/aeabi_dcmp.S",
"arm/aeabi_div0.c",
"arm/aeabi_drsub.c",
"arm/aeabi_fcmp.S",
"arm/aeabi_frsub.c",
"arm/aeabi_idivmod.S",
"arm/aeabi_ldivmod.S",
"arm/aeabi_memcmp.S",
"arm/aeabi_memcpy.S",
"arm/aeabi_memmove.S",
"arm/aeabi_memset.S",
"arm/aeabi_uidivmod.S",
"arm/aeabi_uldivmod.S",
}
func builtinFiles(target string) []string {
builtins := append([]string{}, genericBuiltins...) // copy genericBuiltins
if strings.HasPrefix(target, "arm") {
builtins = append(builtins, aeabiBuiltins...)
}
return builtins
}
// builtinsDir returns the directory where the sources for compiler-rt are kept.
func builtinsDir() string {
return filepath.Join(sourceDir(), "lib", "compiler-rt", "lib", "builtins")
}
// Get the builtins archive, possibly generating it as needed.
func loadBuiltins(target string) (path string, err error) {
// Try to load a precompiled compiler-rt library.
precompiledPath := filepath.Join(sourceDir(), "pkg", target, "compiler-rt.a")
if _, err := os.Stat(precompiledPath); err == nil {
// Found a precompiled compiler-rt for this OS/architecture. Return the
// path directly.
return precompiledPath, nil
}
outfile := "librt-" + target + ".a"
builtinsDir := builtinsDir()
builtins := builtinFiles(target)
srcs := make([]string, len(builtins))
for i, name := range builtins {
srcs[i] = filepath.Join(builtinsDir, name)
}
if path, err := cacheLoad(outfile, commands["clang"][0], srcs); path != "" || err != nil {
return path, err
}
var cachepath string
err = compileBuiltins(target, func(path string) error {
path, err := cacheStore(path, outfile, commands["clang"][0], srcs)
cachepath = path
return err
})
return cachepath, err
}
// compileBuiltins compiles builtins from compiler-rt into a static library.
// When it succeeds, it will call the callback with the resulting path. The path
// will be removed after callback returns. If callback returns an error, this is
// passed through to the return value of this function.
func compileBuiltins(target string, callback func(path string) error) error {
builtinsDir := builtinsDir()
builtins := builtinFiles(target)
srcs := make([]string, len(builtins))
for i, name := range builtins {
srcs[i] = filepath.Join(builtinsDir, name)
}
dirPrefix := "tinygo-builtins"
remapDir := filepath.Join(os.TempDir(), dirPrefix)
dir, err := ioutil.TempDir(os.TempDir(), dirPrefix)
if err != nil {
return err
}
defer os.RemoveAll(dir)
// Compile all builtins.
// TODO: use builtins optimized for a given target if available.
objs := make([]string, 0, len(builtins))
for _, name := range builtins {
objname := name
if strings.LastIndexByte(objname, '/') >= 0 {
objname = objname[strings.LastIndexByte(objname, '/'):]
}
objpath := filepath.Join(dir, objname+".o")
objs = append(objs, objpath)
srcpath := filepath.Join(builtinsDir, name)
// Note: -fdebug-prefix-map is necessary to make the output archive
// reproducible. Otherwise the temporary directory is stored in the
// archive itself, which varies each run.
err := execCommand(commands["clang"], "-c", "-Oz", "-g", "-Werror", "-Wall", "-std=c11", "-fshort-enums", "-nostdlibinc", "-ffunction-sections", "-fdata-sections", "--target="+target, "-fdebug-prefix-map="+dir+"="+remapDir, "-o", objpath, srcpath)
if err != nil {
return &commandError{"failed to build", srcpath, err}
}
}
// Put all builtins in an archive to link as a static library.
// Note: this does not create a symbol index, but ld.lld doesn't seem to
// care.
arpath := filepath.Join(dir, "librt.a")
arfile, err := os.Create(arpath)
if err != nil {
return err
}
defer arfile.Close()
arwriter := ar.NewWriter(arfile)
err = arwriter.WriteGlobalHeader()
if err != nil {
return &os.PathError{"write ar header", arpath, err}
}
for _, objpath := range objs {
name := filepath.Base(objpath)
objfile, err := os.Open(objpath)
if err != nil {
return err
}
defer objfile.Close()
st, err := objfile.Stat()
if err != nil {
return err
}
arwriter.WriteHeader(&ar.Header{
Name: name,
ModTime: time.Unix(0, 0),
Uid: 0,
Gid: 0,
Mode: 0644,
Size: st.Size(),
})
n, err := io.Copy(arwriter, objfile)
if err != nil {
return err
}
if n != st.Size() {
return errors.New("file modified during ar creation: " + arpath)
}
}
// Give the caller the resulting file. The callback must copy the file,
// because after it returns the temporary directory will be removed.
arfile.Close()
return callback(arpath)
}
+70 -446
View File
@@ -12,16 +12,12 @@ package cgo
// source file parsing.
import (
"fmt"
"go/ast"
"go/scanner"
"go/token"
"path/filepath"
"sort"
"strconv"
"strings"
"github.com/google/shlex"
"golang.org/x/tools/go/ast/astutil"
)
@@ -46,7 +42,7 @@ type cgoPackage struct {
// constantInfo stores some information about a CGo constant found by libclang
// and declared in the Go AST.
type constantInfo struct {
expr ast.Expr
expr *ast.BasicLit
pos token.Pos
}
@@ -73,11 +69,9 @@ type typedefInfo struct {
// elaboratedTypeInfo contains some information about an elaborated type
// (struct, union) found in the C AST.
type elaboratedTypeInfo struct {
typeExpr *ast.StructType
pos token.Pos
bitfields []bitfieldInfo
unionSize int64 // union size in bytes, nonzero when union getters/setters should be created
unionAlign int64 // union alignment in bytes
typeExpr *ast.StructType
pos token.Pos
bitfields []bitfieldInfo
}
// bitfieldInfo contains information about a single bitfield in a struct. It
@@ -137,7 +131,6 @@ var builtinAliases = map[string]struct{}{
// somehow from C. This is done by adding some typedefs to get the size of each
// type.
const cgoTypes = `
# 1 "<cgo>"
typedef char _Cgo_char;
typedef signed char _Cgo_schar;
typedef unsigned char _Cgo_uchar;
@@ -170,28 +163,11 @@ func Process(files []*ast.File, dir string, fset *token.FileSet, cflags []string
enums: map[string]enumInfo{},
}
// Disable _FORTIFY_SOURCE as it causes problems on macOS.
// Note that it is only disabled for memcpy (etc) calls made from Go, which
// have better alternatives anyway.
cflags = append(cflags, "-D_FORTIFY_SOURCE=0")
// Add a new location for the following file.
generatedTokenPos := p.fset.AddFile(dir+"/!cgo.go", -1, 0)
generatedTokenPos.SetLines([]int{0})
p.generatedPos = generatedTokenPos.Pos(0)
// Find the absolute path for this package.
packagePath, err := filepath.Abs(fset.File(files[0].Pos()).Name())
if err != nil {
return nil, []error{
scanner.Error{
Pos: fset.Position(files[0].Pos()),
Msg: "cgo: cannot find absolute path: " + err.Error(), // TODO: wrap this error
},
}
}
packagePath = filepath.Dir(packagePath)
// Construct a new in-memory AST for CGo declarations of this package.
unsafeImport := &ast.ImportSpec{
Path: &ast.BasicLit{
@@ -208,7 +184,6 @@ func Process(files []*ast.File, dir string, fset *token.FileSet, cflags []string
Name: files[0].Name.Name,
},
Decls: []ast.Decl{
// import "unsafe"
&ast.GenDecl{
TokPos: p.generatedPos,
Tok: token.IMPORT,
@@ -216,32 +191,6 @@ func Process(files []*ast.File, dir string, fset *token.FileSet, cflags []string
unsafeImport,
},
},
// var _ unsafe.Pointer
// This avoids type errors when the unsafe package is never used.
&ast.GenDecl{
Tok: token.VAR,
Specs: []ast.Spec{
&ast.ValueSpec{
Names: []*ast.Ident{
&ast.Ident{
Name: "_",
Obj: &ast.Object{
Kind: ast.Var,
Name: "_",
},
},
},
Type: &ast.SelectorExpr{
X: &ast.Ident{
Name: "unsafe",
},
Sel: &ast.Ident{
Name: "Pointer",
},
},
},
},
},
},
Imports: []*ast.ImportSpec{unsafeImport},
}
@@ -255,7 +204,6 @@ func Process(files []*ast.File, dir string, fset *token.FileSet, cflags []string
}
// Find `import "C"` statements in the file.
var statements []*ast.GenDecl
for _, f := range files {
for i := 0; i < len(f.Decls); i++ {
decl := f.Decls[i]
@@ -272,16 +220,19 @@ func Process(files []*ast.File, dir string, fset *token.FileSet, cflags []string
}
path, err := strconv.Unquote(spec.Path.Value)
if err != nil {
// This should not happen. An import path that is not properly
// quoted should not exist in a correct AST.
panic("could not parse import path: " + err.Error())
}
if path != "C" {
continue
}
cgoComment := genDecl.Doc.Text()
// Found a CGo statement.
statements = append(statements, genDecl)
pos := genDecl.Pos()
if genDecl.Doc != nil {
pos = genDecl.Doc.Pos()
}
position := fset.PositionFor(pos, true)
p.parseFragment(cgoComment+cgoTypes, cflags, position.Filename, position.Line)
// Remove this import declaration.
f.Decls = append(f.Decls[:i], f.Decls[i+1:]...)
@@ -292,94 +243,6 @@ func Process(files []*ast.File, dir string, fset *token.FileSet, cflags []string
//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)
cflags = append(cflags, flags...)
default:
startPos := strings.LastIndex(line[4:colon], name) + 4
p.addErrorAfter(comment.Slash, comment.Text[:lineStart+startPos], "invalid #cgo line: "+name)
continue
}
}
}
}
// Process all CGo imports.
for _, genDecl := range statements {
cgoComment := genDecl.Doc.Text()
pos := genDecl.Pos()
if genDecl.Doc != nil {
pos = genDecl.Doc.Pos()
}
position := fset.PositionFor(pos, true)
p.parseFragment(cgoComment+cgoTypes, cflags, position.Filename, position.Line)
}
// Declare functions found by libclang.
p.addFuncDecls()
@@ -415,30 +278,6 @@ func Process(files []*ast.File, dir string, fset *token.FileSet, cflags []string
return p.generated, p.errors
}
// makePathsAbsolute converts some common path compiler flags (-I, -L) from
// relative flags into absolute flags, if they are relative. This is necessary
// because the C compiler is usually not invoked from the package path.
func makePathsAbsolute(args []string, packagePath string) {
nextIsPath := false
for i, arg := range args {
if nextIsPath {
if !filepath.IsAbs(arg) {
args[i] = filepath.Join(packagePath, arg)
}
}
if arg == "-I" || arg == "-L" {
nextIsPath = true
continue
}
if strings.HasPrefix(arg, "-I") || strings.HasPrefix(arg, "-L") {
path := arg[2:]
if !filepath.IsAbs(path) {
args[i] = arg[:2] + filepath.Join(packagePath, path)
}
}
}
}
// addFuncDecls adds the C function declarations found by libclang in the
// comment above the `import "C"` statement.
func (p *cgoPackage) addFuncDecls() {
@@ -504,18 +343,18 @@ func (p *cgoPackage) addFuncPtrDecls() {
if len(p.functions) == 0 {
return
}
gen := &ast.GenDecl{
TokPos: token.NoPos,
Tok: token.VAR,
Lparen: token.NoPos,
Rparen: token.NoPos,
}
names := make([]string, 0, len(p.functions))
for name := range p.functions {
names = append(names, name)
}
sort.Strings(names)
for _, name := range names {
gen := &ast.GenDecl{
TokPos: token.NoPos,
Tok: token.VAR,
Lparen: token.NoPos,
Rparen: token.NoPos,
}
fn := p.functions[name]
obj := &ast.Object{
Kind: ast.Typ,
@@ -540,32 +379,34 @@ func (p *cgoPackage) addFuncPtrDecls() {
}
obj.Decl = valueSpec
gen.Specs = append(gen.Specs, valueSpec)
p.generated.Decls = append(p.generated.Decls, gen)
}
p.generated.Decls = append(p.generated.Decls, gen)
}
// addConstDecls declares external C constants in the Go source.
// It adds code like the following to the AST:
//
// const C.CONST_INT = 5
// const C.CONST_FLOAT = 5.8
// // ...
// const (
// C.CONST_INT = 5
// C.CONST_FLOAT = 5.8
// // ...
// )
func (p *cgoPackage) addConstDecls() {
if len(p.constants) == 0 {
return
}
gen := &ast.GenDecl{
TokPos: token.NoPos,
Tok: token.CONST,
Lparen: token.NoPos,
Rparen: token.NoPos,
}
names := make([]string, 0, len(p.constants))
for name := range p.constants {
names = append(names, name)
}
sort.Strings(names)
for _, name := range names {
gen := &ast.GenDecl{
TokPos: token.NoPos,
Tok: token.CONST,
Lparen: token.NoPos,
Rparen: token.NoPos,
}
constVal := p.constants[name]
obj := &ast.Object{
Kind: ast.Con,
@@ -581,32 +422,34 @@ func (p *cgoPackage) addConstDecls() {
}
obj.Decl = valueSpec
gen.Specs = append(gen.Specs, valueSpec)
p.generated.Decls = append(p.generated.Decls, gen)
}
p.generated.Decls = append(p.generated.Decls, gen)
}
// addVarDecls declares external C globals in the Go source.
// It adds code like the following to the AST:
//
// var C.globalInt int
// var C.globalBool bool
// // ...
// var (
// C.globalInt int
// C.globalBool bool
// // ...
// )
func (p *cgoPackage) addVarDecls() {
if len(p.globals) == 0 {
return
}
gen := &ast.GenDecl{
TokPos: token.NoPos,
Tok: token.VAR,
Lparen: token.NoPos,
Rparen: token.NoPos,
}
names := make([]string, 0, len(p.globals))
for name := range p.globals {
names = append(names, name)
}
sort.Strings(names)
for _, name := range names {
gen := &ast.GenDecl{
TokPos: token.NoPos,
Tok: token.VAR,
Lparen: token.NoPos,
Rparen: token.NoPos,
}
global := p.globals[name]
obj := &ast.Object{
Kind: ast.Var,
@@ -622,29 +465,31 @@ func (p *cgoPackage) addVarDecls() {
}
obj.Decl = valueSpec
gen.Specs = append(gen.Specs, valueSpec)
p.generated.Decls = append(p.generated.Decls, gen)
}
p.generated.Decls = append(p.generated.Decls, gen)
}
// addTypeAliases aliases some built-in Go types with their equivalent C types.
// It adds code like the following to the AST:
//
// type C.int8_t = int8
// type C.int16_t = int16
// // ...
// type (
// C.int8_t = int8
// C.int16_t = int16
// // ...
// )
func (p *cgoPackage) addTypeAliases() {
aliasKeys := make([]string, 0, len(cgoAliases))
for key := range cgoAliases {
aliasKeys = append(aliasKeys, key)
}
sort.Strings(aliasKeys)
gen := &ast.GenDecl{
TokPos: token.NoPos,
Tok: token.TYPE,
Lparen: token.NoPos,
Rparen: token.NoPos,
}
for _, typeName := range aliasKeys {
gen := &ast.GenDecl{
TokPos: token.NoPos,
Tok: token.TYPE,
Lparen: token.NoPos,
Rparen: token.NoPos,
}
goTypeName := cgoAliases[typeName]
obj := &ast.Object{
Kind: ast.Typ,
@@ -664,24 +509,24 @@ func (p *cgoPackage) addTypeAliases() {
}
obj.Decl = typeSpec
gen.Specs = append(gen.Specs, typeSpec)
p.generated.Decls = append(p.generated.Decls, gen)
}
p.generated.Decls = append(p.generated.Decls, gen)
}
func (p *cgoPackage) addTypedefs() {
if len(p.typedefs) == 0 {
return
}
gen := &ast.GenDecl{
TokPos: token.NoPos,
Tok: token.TYPE,
}
names := make([]string, 0, len(p.typedefs))
for name := range p.typedefs {
names = append(names, name)
}
sort.Strings(names)
for _, name := range names {
gen := &ast.GenDecl{
TokPos: token.NoPos,
Tok: token.TYPE,
}
typedef := p.typedefs[name]
typeName := "C." + name
isAlias := true
@@ -710,8 +555,8 @@ func (p *cgoPackage) addTypedefs() {
}
obj.Decl = typeSpec
gen.Specs = append(gen.Specs, typeSpec)
p.generated.Decls = append(p.generated.Decls, gen)
}
p.generated.Decls = append(p.generated.Decls, gen)
}
// addElaboratedTypes adds C elaborated types as aliases. These are the "struct
@@ -723,47 +568,29 @@ func (p *cgoPackage) addElaboratedTypes() {
if len(p.elaboratedTypes) == 0 {
return
}
gen := &ast.GenDecl{
TokPos: token.NoPos,
Tok: token.TYPE,
}
names := make([]string, 0, len(p.elaboratedTypes))
for name := range p.elaboratedTypes {
names = append(names, name)
}
sort.Strings(names)
for _, name := range names {
gen := &ast.GenDecl{
TokPos: token.NoPos,
Tok: token.TYPE,
}
typ := p.elaboratedTypes[name]
typeName := "C." + name
obj := &ast.Object{
Kind: ast.Typ,
Name: typeName,
}
typeExpr := typ.typeExpr
if typ.unionSize != 0 {
// Create getters/setters.
for _, field := range typ.typeExpr.Fields.List {
if len(field.Names) != 1 {
p.addError(typ.pos, fmt.Sprintf("union must have field with a single name, it has %d names", len(field.Names)))
continue
}
p.createUnionAccessor(field, typeName)
}
// Convert to a single-field struct type.
typeExpr = p.makeUnionField(typ)
if typeExpr == nil {
// There was an error, that was already added to the list of
// errors.
continue
}
}
typeSpec := &ast.TypeSpec{
Name: &ast.Ident{
NamePos: typ.pos,
Name: typeName,
Obj: obj,
},
Type: typeExpr,
Type: typ.typeExpr,
}
obj.Decl = typeSpec
gen.Specs = append(gen.Specs, typeSpec)
@@ -772,185 +599,8 @@ func (p *cgoPackage) addElaboratedTypes() {
p.createBitfieldGetter(bitfield, typeName)
p.createBitfieldSetter(bitfield, typeName)
}
p.generated.Decls = append(p.generated.Decls, gen)
}
}
// makeUnionField creates a new struct from an existing *elaboratedTypeInfo,
// that has just a single field that must be accessed through special accessors.
// It returns nil when there is an error. In case of an error, that error has
// already been added to the list of errors using p.addError.
func (p *cgoPackage) makeUnionField(typ *elaboratedTypeInfo) *ast.StructType {
unionFieldTypeName, ok := map[int64]string{
1: "uint8",
2: "uint16",
4: "uint32",
8: "uint64",
}[typ.unionAlign]
if !ok {
p.addError(typ.typeExpr.Struct, fmt.Sprintf("expected union alignment to be one of 1, 2, 4, or 8, but got %d", typ.unionAlign))
return nil
}
var unionFieldType ast.Expr = &ast.Ident{
NamePos: token.NoPos,
Name: unionFieldTypeName,
}
if typ.unionSize != typ.unionAlign {
// A plain struct{uintX} isn't enough, we have to make a
// struct{[N]uintX} to make the union big enough.
if typ.unionSize/typ.unionAlign*typ.unionAlign != typ.unionSize {
p.addError(typ.typeExpr.Struct, fmt.Sprintf("union alignment (%d) must be a multiple of union alignment (%d)", typ.unionSize, typ.unionAlign))
return nil
}
unionFieldType = &ast.ArrayType{
Len: &ast.BasicLit{
Kind: token.INT,
Value: strconv.FormatInt(typ.unionSize/typ.unionAlign, 10),
},
Elt: unionFieldType,
}
}
return &ast.StructType{
Struct: typ.typeExpr.Struct,
Fields: &ast.FieldList{
Opening: typ.typeExpr.Fields.Opening,
List: []*ast.Field{&ast.Field{
Names: []*ast.Ident{
&ast.Ident{
NamePos: typ.typeExpr.Fields.Opening,
Name: "$union",
},
},
Type: unionFieldType,
}},
Closing: typ.typeExpr.Fields.Closing,
},
}
}
// createUnionAccessor creates a function that returns a typed pointer to a
// union field for each field in a union. For example:
//
// func (union *C.union_1) unionfield_d() *float64 {
// return (*float64)(unsafe.Pointer(&union.$union))
// }
//
// Where C.union_1 is defined as:
//
// type C.union_1 struct{
// $union uint64
// }
//
// The returned pointer can be used to get or set the field, or get the pointer
// to a subfield.
func (p *cgoPackage) createUnionAccessor(field *ast.Field, typeName string) {
if len(field.Names) != 1 {
panic("number of names in union field must be exactly 1")
}
fieldName := field.Names[0]
pos := fieldName.NamePos
// The method receiver.
receiver := &ast.SelectorExpr{
X: &ast.Ident{
NamePos: pos,
Name: "union",
Obj: nil,
},
Sel: &ast.Ident{
NamePos: pos,
Name: "$union",
},
}
// Get the address of the $union field.
receiverPtr := &ast.UnaryExpr{
Op: token.AND,
X: receiver,
}
// Cast to unsafe.Pointer.
sourcePointer := &ast.CallExpr{
Fun: &ast.SelectorExpr{
X: &ast.Ident{Name: "unsafe"},
Sel: &ast.Ident{Name: "Pointer"},
},
Args: []ast.Expr{receiverPtr},
}
// Cast to the target pointer type.
targetPointer := &ast.CallExpr{
Lparen: pos,
Fun: &ast.ParenExpr{
Lparen: pos,
X: &ast.StarExpr{
X: field.Type,
},
Rparen: pos,
},
Args: []ast.Expr{sourcePointer},
Rparen: pos,
}
// Create the accessor function.
accessor := &ast.FuncDecl{
Recv: &ast.FieldList{
Opening: pos,
List: []*ast.Field{
&ast.Field{
Names: []*ast.Ident{
&ast.Ident{
NamePos: pos,
Name: "union",
},
},
Type: &ast.StarExpr{
Star: pos,
X: &ast.Ident{
NamePos: pos,
Name: typeName,
Obj: nil,
},
},
},
},
Closing: pos,
},
Name: &ast.Ident{
NamePos: pos,
Name: "unionfield_" + fieldName.Name,
},
Type: &ast.FuncType{
Func: pos,
Params: &ast.FieldList{
Opening: pos,
Closing: pos,
},
Results: &ast.FieldList{
List: []*ast.Field{
&ast.Field{
Type: &ast.StarExpr{
Star: pos,
X: field.Type,
},
},
},
},
},
Body: &ast.BlockStmt{
Lbrace: pos,
List: []ast.Stmt{
&ast.ReturnStmt{
Return: pos,
Results: []ast.Expr{
targetPointer,
},
},
},
Rbrace: pos,
},
}
p.generated.Decls = append(p.generated.Decls, accessor)
p.generated.Decls = append(p.generated.Decls, gen)
}
// createBitfieldGetter creates a bitfield getter function like the following:
@@ -1255,16 +905,16 @@ func (p *cgoPackage) addEnumTypes() {
if len(p.enums) == 0 {
return
}
gen := &ast.GenDecl{
TokPos: token.NoPos,
Tok: token.TYPE,
}
names := make([]string, 0, len(p.enums))
for name := range p.enums {
names = append(names, name)
}
sort.Strings(names)
for _, name := range names {
gen := &ast.GenDecl{
TokPos: token.NoPos,
Tok: token.TYPE,
}
typ := p.enums[name]
typeName := "C.enum_" + name
obj := &ast.Object{
@@ -1281,8 +931,8 @@ func (p *cgoPackage) addEnumTypes() {
}
obj.Decl = typeSpec
gen.Specs = append(gen.Specs, typeSpec)
p.generated.Decls = append(p.generated.Decls, gen)
}
p.generated.Decls = append(p.generated.Decls, gen)
}
// findMissingCGoNames traverses the AST and finds all C.something names. Only
@@ -1345,29 +995,3 @@ func (p *cgoPackage) walker(cursor *astutil.Cursor) bool {
}
return true
}
// renameFieldKeywords renames all reserved words in Go to some other field name
// with a "_" prefix. For example, it renames `type` to `_type`.
//
// See: https://golang.org/cmd/cgo/#hdr-Go_references_to_C
func renameFieldKeywords(fieldList *ast.FieldList) {
renameFieldName(fieldList, "type")
}
// renameFieldName renames a given field name to a name with a "_" prepended. It
// makes sure to do the same thing for any field sharing the same name.
func renameFieldName(fieldList *ast.FieldList, name string) {
var ident *ast.Ident
for _, f := range fieldList.List {
for _, n := range f.Names {
if n.Name == name {
ident = n
}
}
}
if ident == nil {
return
}
renameFieldName(fieldList, "_"+name)
ident.Name = "_" + ident.Name
}
-145
View File
@@ -1,145 +0,0 @@
package cgo
import (
"bytes"
"flag"
"fmt"
"go/ast"
"go/format"
"go/parser"
"go/token"
"go/types"
"io/ioutil"
"path/filepath"
"regexp"
"runtime"
"strings"
"testing"
)
// Pass -update to go test to update the output of the test files.
var flagUpdate = flag.Bool("update", false, "Update images based on test output.")
// normalizeResult normalizes Go source code that comes out of tests across
// platforms and Go versions.
func normalizeResult(result string) string {
actual := strings.Replace(result, "\r\n", "\n", -1)
// 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
}
func TestCGo(t *testing.T) {
var cflags = []string{"--target=armv6m-none-eabi"}
for _, name := range []string{"basic", "errors", "types", "flags", "const"} {
name := name // avoid a race condition
t.Run(name, func(t *testing.T) {
// Read the AST in memory.
path := filepath.Join("testdata", name+".go")
fset := token.NewFileSet()
f, err := parser.ParseFile(fset, path, nil, parser.ParseComments)
if err != nil {
t.Fatal("could not parse Go source file:", err)
}
// Process the AST with CGo.
cgoAST, cgoErrors := Process([]*ast.File{f}, "testdata", fset, cflags)
// Check the AST for type errors.
var typecheckErrors []error
config := types.Config{
Error: func(err error) {
typecheckErrors = append(typecheckErrors, err)
},
Importer: simpleImporter{},
Sizes: types.SizesFor("gccgo", "arm"),
}
_, err = config.Check("", fset, []*ast.File{f, cgoAST}, nil)
if err != nil && len(typecheckErrors) == 0 {
// Only report errors when no type errors are found (an
// unexpected condition).
t.Error(err)
}
// Store the (formatted) output in a buffer. Format it, so it
// becomes easier to read (and will hopefully change less with CGo
// changes).
buf := &bytes.Buffer{}
if len(cgoErrors) != 0 {
buf.WriteString("// CGo errors:\n")
for _, err := range cgoErrors {
buf.WriteString(formatDiagnostic(err))
}
buf.WriteString("\n")
}
if len(typecheckErrors) != 0 {
buf.WriteString("// Type checking errors after CGo processing:\n")
for _, err := range typecheckErrors {
buf.WriteString(formatDiagnostic(err))
}
buf.WriteString("\n")
}
err = format.Node(buf, fset, cgoAST)
if err != nil {
t.Errorf("could not write out CGo AST: %v", err)
}
actual := normalizeResult(string(buf.Bytes()))
// Read the file with the expected output, to compare against.
outfile := filepath.Join("testdata", name+".out.go")
expectedBytes, err := ioutil.ReadFile(outfile)
if err != nil {
t.Fatalf("could not read expected output: %v", err)
}
expected := strings.Replace(string(expectedBytes), "\r\n", "\n", -1)
// Check whether the output is as expected.
if expected != actual {
// It is not. Test failed.
if *flagUpdate {
// Update the file with the expected data.
err := ioutil.WriteFile(outfile, []byte(actual), 0666)
if err != nil {
t.Error("could not write updated output file:", err)
}
return
}
t.Errorf("output did not match:\n%s", string(actual))
}
})
}
}
// simpleImporter implements the types.Importer interface, but only allows
// importing the unsafe package.
type simpleImporter struct {
}
// Import implements the Importer interface. For testing usage only: it only
// supports importing the unsafe package.
func (i simpleImporter) Import(path string) (*types.Package, error) {
switch path {
case "unsafe":
return types.Unsafe, nil
default:
return nil, fmt.Errorf("importer not implemented for package %s", path)
}
}
// formatDiagnostics formats the error message to be an indented comment. It
// also fixes Windows path name issues (backward slashes).
func formatDiagnostic(err error) string {
msg := err.Error()
if runtime.GOOS == "windows" {
// Fix Windows path slashes.
msg = strings.Replace(msg, "testdata\\", "testdata/", -1)
}
return "// " + msg + "\n"
}
-218
View File
@@ -1,218 +0,0 @@
package cgo
// This file implements a parser of a subset of the C language, just enough to
// parse common #define statements to Go constant expressions.
import (
"fmt"
"go/ast"
"go/scanner"
"go/token"
"strings"
)
// parseConst parses the given string as a C constant.
func parseConst(pos token.Pos, fset *token.FileSet, value string) (ast.Expr, *scanner.Error) {
t := newTokenizer(pos, fset, value)
expr, err := parseConstExpr(t)
if t.token != token.EOF {
return nil, &scanner.Error{
Pos: t.fset.Position(t.pos),
Msg: "unexpected token " + t.token.String(),
}
}
return expr, err
}
// parseConstExpr parses a stream of C tokens to a Go expression.
func parseConstExpr(t *tokenizer) (ast.Expr, *scanner.Error) {
switch t.token {
case token.LPAREN:
lparen := t.pos
t.Next()
x, err := parseConstExpr(t)
if err != nil {
return nil, err
}
if t.token != token.RPAREN {
return nil, unexpectedToken(t, token.RPAREN)
}
expr := &ast.ParenExpr{
Lparen: lparen,
X: x,
Rparen: t.pos,
}
t.Next()
return expr, nil
case token.INT, token.FLOAT, token.STRING, token.CHAR:
expr := &ast.BasicLit{
ValuePos: t.pos,
Kind: t.token,
Value: t.value,
}
t.Next()
return expr, nil
case token.IDENT:
expr := &ast.Ident{
NamePos: t.pos,
Name: "C." + t.value,
}
t.Next()
return expr, nil
case token.EOF:
return nil, &scanner.Error{
Pos: t.fset.Position(t.pos),
Msg: "empty constant",
}
default:
return nil, &scanner.Error{
Pos: t.fset.Position(t.pos),
Msg: fmt.Sprintf("unexpected token %s", t.token),
}
}
}
// unexpectedToken returns an error of the form "unexpected token FOO, expected
// BAR".
func unexpectedToken(t *tokenizer, expected token.Token) *scanner.Error {
return &scanner.Error{
Pos: t.fset.Position(t.pos),
Msg: fmt.Sprintf("unexpected token %s, expected %s", t.token, expected),
}
}
// tokenizer reads C source code and converts it to Go tokens.
type tokenizer struct {
pos token.Pos
fset *token.FileSet
token token.Token
value string
buf string
}
// newTokenizer initializes a new tokenizer, positioned at the first token in
// the string.
func newTokenizer(start token.Pos, fset *token.FileSet, buf string) *tokenizer {
t := &tokenizer{
pos: start,
fset: fset,
buf: buf,
token: token.ILLEGAL,
}
t.Next() // Parse the first token.
return t
}
// Next consumes the next token in the stream. There is no return value, read
// the next token from the pos, token and value properties.
func (t *tokenizer) Next() {
t.pos += token.Pos(len(t.value))
for {
if len(t.buf) == 0 {
t.token = token.EOF
return
}
c := t.buf[0]
switch {
case c == ' ' || c == '\f' || c == '\n' || c == '\r' || c == '\t' || c == '\v':
// Skip whitespace.
// Based on this source, not sure whether it represents C whitespace:
// https://en.cppreference.com/w/cpp/string/byte/isspace
t.pos++
t.buf = t.buf[1:]
case c == '(' || c == ')':
// Single-character tokens.
switch c {
case '(':
t.token = token.LPAREN
case ')':
t.token = token.RPAREN
}
t.value = t.buf[:1]
t.buf = t.buf[1:]
return
case c >= '0' && c <= '9':
// Numeric constant (int, float, etc.).
// Find the last non-numeric character.
tokenLen := len(t.buf)
hasDot := false
for i, c := range t.buf {
if c == '.' {
hasDot = true
}
if c >= '0' && c <= '9' || c == '.' || c == '_' || c >= 'a' && c <= 'z' || c >= 'A' && c <= 'Z' {
tokenLen = i + 1
} else {
break
}
}
t.value = t.buf[:tokenLen]
t.buf = t.buf[tokenLen:]
if hasDot {
// Integer constants are more complicated than this but this is
// a close approximation.
// https://en.cppreference.com/w/cpp/language/integer_literal
t.token = token.FLOAT
t.value = strings.TrimRight(t.value, "f")
} else {
t.token = token.INT
t.value = strings.TrimRight(t.value, "uUlL")
}
return
case c >= 'A' && c <= 'Z' || c >= 'a' && c <= 'z' || c == '_':
// Identifier. Find all remaining tokens that are part of this
// identifier.
tokenLen := len(t.buf)
for i, c := range t.buf {
if c >= '0' && c <= '9' || c >= 'A' && c <= 'Z' || c >= 'a' && c <= 'z' || c == '_' {
tokenLen = i + 1
} else {
break
}
}
t.value = t.buf[:tokenLen]
t.buf = t.buf[tokenLen:]
t.token = token.IDENT
return
case c == '"':
// String constant. Find the first '"' character that is not
// preceded by a backslash.
escape := false
tokenLen := len(t.buf)
for i, c := range t.buf {
if i != 0 && c == '"' && !escape {
tokenLen = i + 1
break
}
if !escape {
escape = c == '\\'
}
}
t.token = token.STRING
t.value = t.buf[:tokenLen]
t.buf = t.buf[tokenLen:]
return
case c == '\'':
// Char (rune) constant. Find the first '\'' character that is not
// preceded by a backslash.
escape := false
tokenLen := len(t.buf)
for i, c := range t.buf {
if i != 0 && c == '\'' && !escape {
tokenLen = i + 1
break
}
if !escape {
escape = c == '\\'
}
}
t.token = token.CHAR
t.value = t.buf[:tokenLen]
t.buf = t.buf[tokenLen:]
return
default:
t.token = token.ILLEGAL
return
}
}
}
-60
View File
@@ -1,60 +0,0 @@
package cgo
import (
"bytes"
"go/format"
"go/token"
"strings"
"testing"
)
func TestParseConst(t *testing.T) {
// Test converting a C constant to a Go constant.
for _, tc := range []struct {
C string
Go string
}{
{`5`, `5`},
{`(5)`, `(5)`},
{`(((5)))`, `(5)`},
{`)`, `error: 1:1: unexpected token )`},
{`5)`, `error: 1:2: unexpected token )`},
{" \t)", `error: 1:4: unexpected token )`},
{`5.8f`, `5.8`},
{`foo`, `C.foo`},
{``, `error: 1:1: empty constant`}, // empty constants not allowed in Go
{`"foo"`, `"foo"`},
{`"a\\n"`, `"a\\n"`},
{`"a\n"`, `"a\n"`},
{`"a\""`, `"a\""`},
{`'a'`, `'a'`},
{`0b10`, `0b10`},
{`0x1234_5678`, `0x1234_5678`},
{`5 5`, `error: 1:3: unexpected token INT`}, // test for a bugfix
} {
fset := token.NewFileSet()
startPos := fset.AddFile("", -1, 1000).Pos(0)
expr, err := parseConst(startPos, fset, tc.C)
s := "<invalid>"
if err != nil {
if !strings.HasPrefix(tc.Go, "error: ") {
t.Errorf("expected value %#v for C constant %#v but got error %#v", tc.Go, tc.C, err.Error())
continue
}
s = "error: " + err.Error()
} else if expr != nil {
// Serialize the Go constant to a string, for more readable test
// cases.
buf := &bytes.Buffer{}
err := format.Node(buf, fset, expr)
if err != nil {
t.Errorf("could not format expr from C constant %#v: %v", tc.C, err)
continue
}
s = buf.String()
}
if s != tc.Go {
t.Errorf("C constant %#v was parsed to %#v while expecting %#v", tc.C, s, tc.Go)
}
}
}
+162 -175
View File
@@ -15,7 +15,7 @@ import (
)
/*
#include <clang-c/Index.h> // if this fails, install libclang-10-dev
#include <clang-c/Index.h> // if this fails, install libclang-8-dev
#include <stdlib.h>
#include <stdint.h>
@@ -109,8 +109,7 @@ func (p *cgoPackage) parseFragment(fragment string, cflags []string, posFilename
C.CXTranslationUnit_DetailedPreprocessingRecord,
&unit)
if errCode != 0 {
// This is probably a bug in the usage of libclang.
panic("cgo: failed to parse source with libclang")
panic("loader: failed to parse source with libclang")
}
defer C.clang_disposeTranslationUnit(unit)
@@ -119,8 +118,27 @@ func (p *cgoPackage) parseFragment(fragment string, cflags []string, posFilename
spelling := getString(C.clang_getDiagnosticSpelling(diagnostic))
severity := diagnosticSeverity[C.clang_getDiagnosticSeverity(diagnostic)]
location := C.clang_getDiagnosticLocation(diagnostic)
pos := p.getClangLocationPosition(location, unit)
p.addError(pos, severity+": "+spelling)
var libclangFilename C.CXString
var line C.unsigned
var column C.unsigned
C.clang_getPresumedLocation(location, &libclangFilename, &line, &column)
filename := getString(libclangFilename)
if filepath.IsAbs(filename) {
// Relative paths for readability, like other Go parser errors.
relpath, err := filepath.Rel(p.dir, filename)
if err == nil {
filename = relpath
}
}
p.errors = append(p.errors, &scanner.Error{
Pos: token.Position{
Filename: filename,
Offset: 0, // not provided by clang_getPresumedLocation
Line: int(line),
Column: int(column),
},
Msg: severity + ": " + spelling,
})
}
for i := 0; i < numDiagnostics; i++ {
diagnostic := C.clang_getDiagnostic(unit, C.uint(i))
@@ -132,6 +150,7 @@ func (p *cgoPackage) parseFragment(fragment string, cflags []string, posFilename
addDiagnostic(C.clang_getDiagnosticInSet(diagnostics, C.uint(j)))
}
}
return
}
ref := storedRefs.Put(p)
@@ -218,17 +237,14 @@ func tinygo_clang_globals_visitor(c, parent C.GoCXCursor, client_data C.CXClient
var startOffset, endOffset C.unsigned
C.clang_getExpansionLocation(start, &file, nil, nil, &startOffset)
if file == nil {
p.addError(pos, "internal error: could not find file where macro is defined")
break
panic("could not find file where macro is defined")
}
C.clang_getExpansionLocation(end, &endFile, nil, nil, &endOffset)
if file != endFile {
p.addError(pos, "internal error: expected start and end location of a macro to be in the same file")
break
panic("expected start and end location of a #define to be in the same file")
}
if startOffset > endOffset {
p.addError(pos, "internal error: start offset of macro is after end offset")
break
panic("startOffset > endOffset")
}
// read file contents and extract the relevant byte range
@@ -236,29 +252,59 @@ func tinygo_clang_globals_visitor(c, parent C.GoCXCursor, client_data C.CXClient
var size C.size_t
sourcePtr := C.clang_getFileContents(tu, file, &size)
if endOffset >= C.uint(size) {
p.addError(pos, "internal error: end offset of macro lies after end of file")
break
panic("endOffset lies after end of file")
}
source := string(((*[1 << 28]byte)(unsafe.Pointer(sourcePtr)))[startOffset:endOffset:endOffset])
if !strings.HasPrefix(source, name) {
p.addError(pos, fmt.Sprintf("internal error: expected macro value to start with %#v, got %#v", name, source))
break
panic(fmt.Sprintf("expected #define value to start with %#v, got %#v", name, source))
}
value := source[len(name):]
// Try to convert this #define into a Go constant expression.
expr, err := parseConst(pos+token.Pos(len(name)), p.fset, value)
if err != nil {
p.errors = append(p.errors, err)
value := strings.TrimSpace(source[len(name):])
for len(value) != 0 && value[0] == '(' && value[len(value)-1] == ')' {
value = strings.TrimSpace(value[1 : len(value)-1])
}
if expr != nil {
// Parsing was successful.
p.constants[name] = constantInfo{expr, pos}
if len(value) == 0 {
// Pretend it doesn't exist at all.
return C.CXChildVisit_Continue
}
// For information about integer literals:
// https://en.cppreference.com/w/cpp/language/integer_literal
if value[0] == '"' {
// string constant
p.constants[name] = constantInfo{&ast.BasicLit{pos, token.STRING, value}, pos}
return C.CXChildVisit_Continue
}
if value[0] == '\'' {
// char constant
p.constants[name] = constantInfo{&ast.BasicLit{pos, token.CHAR, value}, pos}
return C.CXChildVisit_Continue
}
// assume it's a number (int or float)
value = strings.Replace(value, "'", "", -1) // remove ' chars
value = strings.TrimRight(value, "lu") // remove llu suffixes etc.
// find the first non-number
nonnum := byte(0)
for i := 0; i < len(value); i++ {
if value[i] < '0' || value[i] > '9' {
nonnum = value[i]
break
}
}
// determine number type based on the first non-number
switch nonnum {
case 0:
// no non-number found, must be an integer
p.constants[name] = constantInfo{&ast.BasicLit{pos, token.INT, value}, pos}
case 'x', 'X':
// hex integer constant
// TODO: may also be a floating point number per C++17.
p.constants[name] = constantInfo{&ast.BasicLit{pos, token.INT, value}, pos}
case '.', 'e':
// float constant
value = strings.TrimRight(value, "fFlL")
p.constants[name] = constantInfo{&ast.BasicLit{pos, token.FLOAT, value}, pos}
default:
// unknown type, ignore
}
case C.CXCursor_EnumDecl:
// Visit all enums, because the fields may be used even when the enum
// type itself is not.
typ := C.tinygo_clang_getCursorType(c)
p.makeASTType(typ, pos)
}
return C.CXChildVisit_Continue
}
@@ -270,16 +316,11 @@ func getString(clangString C.CXString) (s string) {
return
}
// getCursorPosition returns a usable token.Pos from a libclang cursor.
// getCursorPosition returns a usable token.Pos from a libclang cursor. If the
// file for this cursor has not been seen before, it is read from libclang
// (which already has the file in memory) and added to the token.FileSet.
func (p *cgoPackage) getCursorPosition(cursor C.GoCXCursor) token.Pos {
return p.getClangLocationPosition(C.tinygo_clang_getCursorLocation(cursor), C.tinygo_clang_Cursor_getTranslationUnit(cursor))
}
// getClangLocationPosition returns a usable token.Pos based on a libclang
// location and translation unit. If the file for this cursor has not been seen
// before, it is read from libclang (which already has the file in memory) and
// added to the token.FileSet.
func (p *cgoPackage) getClangLocationPosition(location C.CXSourceLocation, tu C.CXTranslationUnit) token.Pos {
location := C.tinygo_clang_getCursorLocation(cursor)
var file C.CXFile
var line C.unsigned
var column C.unsigned
@@ -293,6 +334,7 @@ func (p *cgoPackage) getClangLocationPosition(location C.CXSourceLocation, tu C.
if _, ok := p.tokenFiles[filename]; !ok {
// File has not been seen before in this package, add line information
// now by reading the file from libclang.
tu := C.tinygo_clang_Cursor_getTranslationUnit(cursor)
var size C.size_t
sourcePtr := C.clang_getFileContents(tu, file, &size)
source := ((*[1 << 28]byte)(unsafe.Pointer(sourcePtr)))[:size:size]
@@ -306,62 +348,7 @@ func (p *cgoPackage) getClangLocationPosition(location C.CXSourceLocation, tu C.
f.SetLines(lines)
p.tokenFiles[filename] = f
}
positionFile := p.tokenFiles[filename]
// Check for alternative line/column information (set with a line directive).
var filename2String C.CXString
var line2 C.unsigned
var column2 C.unsigned
C.clang_getPresumedLocation(location, &filename2String, &line2, &column2)
filename2 := getString(filename2String)
if filename2 != filename || line2 != line || column2 != column {
// The location was changed with a preprocessor directive.
// TODO: this only works for locations that are added in order. Adding
// line/column info to a file that already has line/column info after
// the given offset is ignored.
positionFile.AddLineColumnInfo(int(offset), filename2, int(line2), int(column2))
}
return positionFile.Pos(int(offset))
}
// addError is a utility function to add an error to the list of errors. It will
// convert the token position to a line/column position first, and call
// addErrorAt.
func (p *cgoPackage) addError(pos token.Pos, msg string) {
p.addErrorAt(p.fset.PositionFor(pos, true), msg)
}
// addErrorAfter is like addError, but adds the text `after` to the source
// location.
func (p *cgoPackage) addErrorAfter(pos token.Pos, after, msg string) {
position := p.fset.PositionFor(pos, true)
lines := strings.Split(after, "\n")
if len(lines) != 1 {
// Adjust lines.
// For why we can't just do pos+token.Pos(len(after)), see:
// https://github.com/golang/go/issues/35803
position.Line += len(lines) - 1
position.Column = len(lines[len(lines)-1]) + 1
} else {
position.Column += len(after)
}
p.addErrorAt(position, msg)
}
// addErrorAt is a utility function to add an error to the list of errors.
func (p *cgoPackage) addErrorAt(position token.Position, msg string) {
if filepath.IsAbs(position.Filename) {
// Relative paths for readability, like other Go parser errors.
relpath, err := filepath.Rel(p.dir, position.Filename)
if err == nil {
position.Filename = relpath
}
}
p.errors = append(p.errors, scanner.Error{
Pos: position,
Msg: msg,
})
return p.tokenFiles[filename].Pos(int(offset))
}
// makeASTType return the ast.Expr for the given libclang type. In other words,
@@ -472,7 +459,7 @@ func (p *cgoPackage) makeASTType(typ C.CXType, pos token.Pos) ast.Expr {
// This happens for some very special purpose architectures
// (DSPs etc.) that are not currently targeted.
// https://www.embecosm.com/2017/04/18/non-8-bit-char-support-in-clang-and-llvm/
p.addError(pos, fmt.Sprintf("unknown char width: %d", typeSize))
panic("unknown char width")
}
switch underlyingType.kind {
case C.CXType_Char_S:
@@ -521,44 +508,48 @@ func (p *cgoPackage) makeASTType(typ C.CXType, pos token.Pos) ast.Expr {
case C.CXType_Enum:
return p.makeASTType(underlying, pos)
default:
typeKindSpelling := getString(C.clang_getTypeKindSpelling(underlying.kind))
p.addError(pos, fmt.Sprintf("unknown elaborated type (libclang type kind %s)", typeKindSpelling))
typeName = "<unknown>"
panic("unknown elaborated type")
}
case C.CXType_Record:
cursor := C.tinygo_clang_getTypeDeclaration(typ)
name := getString(C.tinygo_clang_getCursorSpelling(cursor))
var cgoRecordPrefix string
switch C.tinygo_clang_getCursorKind(cursor) {
case C.CXCursor_StructDecl:
cgoRecordPrefix = "struct_"
case C.CXCursor_UnionDecl:
cgoRecordPrefix = "union_"
default:
// makeASTRecordType will create an appropriate error.
cgoRecordPrefix = "record_"
}
if name == "" {
// Anonymous record, probably inside a typedef.
typeInfo := p.makeASTRecordType(cursor, pos)
if typeInfo.bitfields != nil || typeInfo.unionSize != 0 {
// This record is a union or is a struct with bitfields, so we
// have to declare it as a named type (for getters/setters to
// work).
typeExpr, bitfieldList := p.makeASTRecordType(cursor, pos)
if bitfieldList != nil {
// This struct has bitfields, so we have to declare it as a
// named type (for bitfield getters/setters to work).
p.anonStructNum++
cgoName := cgoRecordPrefix + strconv.Itoa(p.anonStructNum)
p.elaboratedTypes[cgoName] = typeInfo
cgoName := "struct_" + strconv.Itoa(p.anonStructNum)
p.elaboratedTypes[cgoName] = &elaboratedTypeInfo{
typeExpr: typeExpr,
pos: pos,
bitfields: bitfieldList,
}
return &ast.Ident{
NamePos: pos,
Name: "C." + cgoName,
}
}
return typeInfo.typeExpr
return typeExpr
} else {
cgoName := cgoRecordPrefix + name
var cgoName string
switch C.tinygo_clang_getCursorKind(cursor) {
case C.CXCursor_StructDecl:
cgoName = "struct_" + name
case C.CXCursor_UnionDecl:
cgoName = "union_" + name
default:
panic("unknown record declaration")
}
if _, ok := p.elaboratedTypes[cgoName]; !ok {
p.elaboratedTypes[cgoName] = nil // predeclare (to avoid endless recursion)
p.elaboratedTypes[cgoName] = p.makeASTRecordType(cursor, pos)
typeExpr, bitfieldList := p.makeASTRecordType(cursor, pos)
p.elaboratedTypes[cgoName] = &elaboratedTypeInfo{
typeExpr: typeExpr,
pos: pos,
bitfields: bitfieldList,
}
}
return &ast.Ident{
NamePos: pos,
@@ -594,10 +585,14 @@ func (p *cgoPackage) makeASTType(typ C.CXType, pos token.Pos) ast.Expr {
}
if typeName == "" {
// Report this as an error.
typeSpelling := getString(C.clang_getTypeSpelling(typ))
typeKindSpelling := getString(C.clang_getTypeKindSpelling(typ.kind))
p.addError(pos, fmt.Sprintf("unknown C type: %v (libclang type kind %s)", typeSpelling, typeKindSpelling))
typeName = "C.<unknown>"
spelling := getString(C.clang_getTypeSpelling(typ))
p.errors = append(p.errors, scanner.Error{
Pos: p.fset.PositionFor(pos, true),
Msg: fmt.Sprintf("unknown C type: %v (libclang type kind %d)", spelling, typ.kind),
})
// Fallback, probably incorrect but at least the error points to an odd
// type name.
typeName = "C." + spelling
}
return &ast.Ident{
NamePos: pos,
@@ -606,8 +601,9 @@ func (p *cgoPackage) makeASTType(typ C.CXType, pos token.Pos) ast.Expr {
}
// makeASTRecordType parses a C record (struct or union) and translates it into
// a Go struct type.
func (p *cgoPackage) makeASTRecordType(cursor C.GoCXCursor, pos token.Pos) *elaboratedTypeInfo {
// a Go struct type. Unions are implemented by setting the first field to a
// zero-lengt "C union" field, which cannot be written in Go directly.
func (p *cgoPackage) makeASTRecordType(cursor C.GoCXCursor, pos token.Pos) (*ast.StructType, []bitfieldInfo) {
fieldList := &ast.FieldList{
Opening: pos,
Closing: pos,
@@ -624,53 +620,53 @@ func (p *cgoPackage) makeASTRecordType(cursor C.GoCXCursor, pos token.Pos) *elab
}{fieldList, p, &inBitfield, &bitfieldNum, &bitfieldList})
defer storedRefs.Remove(ref)
C.tinygo_clang_visitChildren(cursor, C.CXCursorVisitor(C.tinygo_clang_struct_visitor), C.CXClientData(ref))
renameFieldKeywords(fieldList)
switch C.tinygo_clang_getCursorKind(cursor) {
case C.CXCursor_StructDecl:
return &elaboratedTypeInfo{
typeExpr: &ast.StructType{
Struct: pos,
Fields: fieldList,
},
pos: pos,
bitfields: bitfieldList,
}
return &ast.StructType{
Struct: pos,
Fields: fieldList,
}, bitfieldList
case C.CXCursor_UnionDecl:
typeInfo := &elaboratedTypeInfo{
typeExpr: &ast.StructType{
Struct: pos,
Fields: fieldList,
},
pos: pos,
bitfields: bitfieldList,
}
if len(fieldList.List) <= 1 {
// Useless union, treat it as a regular struct.
return typeInfo
}
if bitfieldList != nil {
// This is valid C... but please don't do this.
p.addError(pos, "bitfield in a union is not supported")
p.errors = append(p.errors, scanner.Error{
Pos: p.fset.PositionFor(pos, true),
Msg: fmt.Sprintf("bitfield in a union is not supported"),
})
}
typ := C.tinygo_clang_getCursorType(cursor)
alignInBytes := int64(C.clang_Type_getAlignOf(typ))
sizeInBytes := int64(C.clang_Type_getSizeOf(typ))
if sizeInBytes == 0 {
p.addError(pos, "zero-length union is not supported")
if len(fieldList.List) > 1 {
// Insert a special field at the front (of zero width) as a
// marker that this is struct is actually a union. This is done
// by giving the field a name that cannot be expressed directly
// in Go.
// Other parts of the compiler look at the first element in a
// struct (of size > 2) to know whether this is a union.
// Note that we don't have to insert it for single-element
// unions as they're basically equivalent to a struct.
unionMarker := &ast.Field{
Type: &ast.StructType{
Struct: pos,
},
}
unionMarker.Names = []*ast.Ident{
&ast.Ident{
NamePos: pos,
Name: "C union",
Obj: &ast.Object{
Kind: ast.Var,
Name: "C union",
Decl: unionMarker,
},
},
}
fieldList.List = append([]*ast.Field{unionMarker}, fieldList.List...)
}
typeInfo.unionSize = sizeInBytes
typeInfo.unionAlign = alignInBytes
return typeInfo
return &ast.StructType{
Struct: pos,
Fields: fieldList,
}, bitfieldList
default:
cursorKind := C.tinygo_clang_getCursorKind(cursor)
cursorKindSpelling := getString(C.clang_getCursorKindSpelling(cursorKind))
p.addError(pos, fmt.Sprintf("expected StructDecl or UnionDecl, not %s", cursorKindSpelling))
return &elaboratedTypeInfo{
typeExpr: &ast.StructType{
Struct: pos,
},
pos: pos,
}
panic("unknown record declaration")
}
}
@@ -688,17 +684,8 @@ func tinygo_clang_struct_visitor(c, parent C.GoCXCursor, client_data C.CXClientD
inBitfield := passed.inBitfield
bitfieldNum := passed.bitfieldNum
bitfieldList := passed.bitfieldList
pos := p.getCursorPosition(c)
switch cursorKind := C.tinygo_clang_getCursorKind(c); cursorKind {
case C.CXCursor_FieldDecl:
// Expected. This is a regular field.
case C.CXCursor_StructDecl, C.CXCursor_UnionDecl:
// Ignore. The next field will be the struct/union itself.
return C.CXChildVisit_Continue
default:
cursorKindSpelling := getString(C.clang_getCursorKindSpelling(cursorKind))
p.addError(pos, fmt.Sprintf("expected FieldDecl in struct or union, not %s", cursorKindSpelling))
return C.CXChildVisit_Continue
if C.tinygo_clang_getCursorKind(c) != C.CXCursor_FieldDecl {
panic("expected field inside cursor")
}
name := getString(C.tinygo_clang_getCursorSpelling(c))
if name == "" {
@@ -707,6 +694,7 @@ func tinygo_clang_struct_visitor(c, parent C.GoCXCursor, client_data C.CXClientD
return C.CXChildVisit_Continue
}
typ := C.tinygo_clang_getCursorType(c)
pos := p.getCursorPosition(c)
field := &ast.Field{
Type: p.makeASTType(typ, p.getCursorPosition(c)),
}
@@ -715,8 +703,7 @@ func tinygo_clang_struct_visitor(c, parent C.GoCXCursor, client_data C.CXClientD
bitfieldOffset := offsetof % alignOf
if bitfieldOffset != 0 {
if C.tinygo_clang_Cursor_isBitField(c) != 1 {
p.addError(pos, "expected a bitfield")
return C.CXChildVisit_Continue
panic("expected a bitfield")
}
if !*inBitfield {
*bitfieldNum++
+4 -7
View File
@@ -1,14 +1,11 @@
// +build !byollvm
// +build !llvm9
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
#cgo linux CFLAGS: -I/usr/lib/llvm-8/include
#cgo darwin CFLAGS: -I/usr/local/opt/llvm/include
#cgo linux LDFLAGS: -L/usr/lib/llvm-8/lib -lclang
#cgo darwin LDFLAGS: -L/usr/local/opt/llvm/lib -lclang -lffi
*/
import "C"
-14
View File
@@ -1,14 +0,0 @@
// +build !byollvm
// +build llvm9
package cgo
/*
#cgo linux CFLAGS: -I/usr/lib/llvm-9/include
#cgo darwin CFLAGS: -I/usr/local/opt/llvm@9/include
#cgo freebsd CFLAGS: -I/usr/local/llvm9/include
#cgo linux LDFLAGS: -L/usr/lib/llvm-9/lib -lclang
#cgo darwin LDFLAGS: -L/usr/local/opt/llvm@9/lib -lclang -lffi
#cgo freebsd LDFLAGS: -L/usr/local/llvm9/lib -lclang
*/
import "C"
+1 -1
View File
@@ -3,7 +3,7 @@
// are slightly different from the ones defined in libclang.go, but they
// should be ABI compatible.
#include <clang-c/Index.h> // if this fails, install libclang-10-dev
#include <clang-c/Index.h> // if this fails, install libclang-8-dev
CXCursor tinygo_clang_getTranslationUnitCursor(CXTranslationUnit tu) {
return clang_getTranslationUnitCursor(tu);
-301
View File
@@ -1,301 +0,0 @@
// Copyright 2018 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// This file has been copied from the Go 1.13 release tree.
// Checking of compiler and linker flags.
// We must avoid flags like -fplugin=, which can allow
// arbitrary code execution during the build.
// Do not make changes here without carefully
// considering the implications.
// (That's why the code is isolated in a file named security.go.)
//
// Note that -Wl,foo means split foo on commas and pass to
// the linker, so that -Wl,-foo,bar means pass -foo bar to
// the linker. Similarly -Wa,foo for the assembler and so on.
// If any of these are permitted, the wildcard portion must
// disallow commas.
//
// Note also that GNU binutils accept any argument @foo
// as meaning "read more flags from the file foo", so we must
// guard against any command-line argument beginning with @,
// even things like "-I @foo".
// We use safeArg (which is even more conservative)
// to reject these.
//
// Even worse, gcc -I@foo (one arg) turns into cc1 -I @foo (two args),
// so although gcc doesn't expand the @foo, cc1 will.
// So out of paranoia, we reject @ at the beginning of every
// flag argument that might be split into its own argument.
package cgo
import (
"fmt"
"os"
"regexp"
"strings"
"unicode/utf8"
)
var re = regexp.MustCompile
var validCompilerFlags = []*regexp.Regexp{
re(`-D([A-Za-z_].*)`),
re(`-F([^@\-].*)`),
re(`-I([^@\-].*)`),
re(`-O`),
re(`-O([^@\-].*)`),
re(`-W`),
re(`-W([^@,]+)`), // -Wall but not -Wa,-foo.
re(`-Wa,-mbig-obj`),
re(`-Wp,-D([A-Za-z_].*)`),
re(`-ansi`),
re(`-f(no-)?asynchronous-unwind-tables`),
re(`-f(no-)?blocks`),
re(`-f(no-)builtin-[a-zA-Z0-9_]*`),
re(`-f(no-)?common`),
re(`-f(no-)?constant-cfstrings`),
re(`-fdiagnostics-show-note-include-stack`),
re(`-f(no-)?eliminate-unused-debug-types`),
re(`-f(no-)?exceptions`),
re(`-f(no-)?fast-math`),
re(`-f(no-)?inline-functions`),
re(`-finput-charset=([^@\-].*)`),
re(`-f(no-)?fat-lto-objects`),
re(`-f(no-)?keep-inline-dllexport`),
re(`-f(no-)?lto`),
re(`-fmacro-backtrace-limit=(.+)`),
re(`-fmessage-length=(.+)`),
re(`-f(no-)?modules`),
re(`-f(no-)?objc-arc`),
re(`-f(no-)?objc-nonfragile-abi`),
re(`-f(no-)?objc-legacy-dispatch`),
re(`-f(no-)?omit-frame-pointer`),
re(`-f(no-)?openmp(-simd)?`),
re(`-f(no-)?permissive`),
re(`-f(no-)?(pic|PIC|pie|PIE)`),
re(`-f(no-)?plt`),
re(`-f(no-)?rtti`),
re(`-f(no-)?split-stack`),
re(`-f(no-)?stack-(.+)`),
re(`-f(no-)?strict-aliasing`),
re(`-f(un)signed-char`),
re(`-f(no-)?use-linker-plugin`), // safe if -B is not used; we don't permit -B
re(`-f(no-)?visibility-inlines-hidden`),
re(`-fsanitize=(.+)`),
re(`-ftemplate-depth-(.+)`),
re(`-fvisibility=(.+)`),
re(`-g([^@\-].*)?`),
re(`-m32`),
re(`-m64`),
re(`-m(abi|arch|cpu|fpu|tune)=([^@\-].*)`),
re(`-m(no-)?v?aes`),
re(`-marm`),
re(`-m(no-)?avx[0-9a-z]*`),
re(`-mfloat-abi=([^@\-].*)`),
re(`-mfpmath=[0-9a-z,+]*`),
re(`-m(no-)?avx[0-9a-z.]*`),
re(`-m(no-)?ms-bitfields`),
re(`-m(no-)?stack-(.+)`),
re(`-mmacosx-(.+)`),
re(`-mios-simulator-version-min=(.+)`),
re(`-miphoneos-version-min=(.+)`),
re(`-mtvos-simulator-version-min=(.+)`),
re(`-mtvos-version-min=(.+)`),
re(`-mwatchos-simulator-version-min=(.+)`),
re(`-mwatchos-version-min=(.+)`),
re(`-mnop-fun-dllimport`),
re(`-m(no-)?sse[0-9.]*`),
re(`-m(no-)?ssse3`),
re(`-mthumb(-interwork)?`),
re(`-mthreads`),
re(`-mwindows`),
re(`--param=ssp-buffer-size=[0-9]*`),
re(`-pedantic(-errors)?`),
re(`-pipe`),
re(`-pthread`),
re(`-?-std=([^@\-].*)`),
re(`-?-stdlib=([^@\-].*)`),
re(`--sysroot=([^@\-].*)`),
re(`-w`),
re(`-x([^@\-].*)`),
re(`-v`),
}
var validCompilerFlagsWithNextArg = []string{
"-arch",
"-D",
"-I",
"-framework",
"-isysroot",
"-isystem",
"--sysroot",
"-target",
"-x",
}
var validLinkerFlags = []*regexp.Regexp{
re(`-F([^@\-].*)`),
re(`-l([^@\-].*)`),
re(`-L([^@\-].*)`),
re(`-O`),
re(`-O([^@\-].*)`),
re(`-f(no-)?(pic|PIC|pie|PIE)`),
re(`-f(no-)?openmp(-simd)?`),
re(`-fsanitize=([^@\-].*)`),
re(`-flat_namespace`),
re(`-g([^@\-].*)?`),
re(`-headerpad_max_install_names`),
re(`-m(abi|arch|cpu|fpu|tune)=([^@\-].*)`),
re(`-mfloat-abi=([^@\-].*)`),
re(`-mmacosx-(.+)`),
re(`-mios-simulator-version-min=(.+)`),
re(`-miphoneos-version-min=(.+)`),
re(`-mthreads`),
re(`-mwindows`),
re(`-(pic|PIC|pie|PIE)`),
re(`-pthread`),
re(`-rdynamic`),
re(`-shared`),
re(`-?-static([-a-z0-9+]*)`),
re(`-?-stdlib=([^@\-].*)`),
re(`-v`),
// Note that any wildcards in -Wl need to exclude comma,
// since -Wl splits its argument at commas and passes
// them all to the linker uninterpreted. Allowing comma
// in a wildcard would allow tunnelling arbitrary additional
// linker arguments through one of these.
re(`-Wl,--(no-)?allow-multiple-definition`),
re(`-Wl,--(no-)?allow-shlib-undefined`),
re(`-Wl,--(no-)?as-needed`),
re(`-Wl,-Bdynamic`),
re(`-Wl,-berok`),
re(`-Wl,-Bstatic`),
re(`-WL,-O([^@,\-][^,]*)?`),
re(`-Wl,-d[ny]`),
re(`-Wl,--disable-new-dtags`),
re(`-Wl,-e[=,][a-zA-Z0-9]*`),
re(`-Wl,--enable-new-dtags`),
re(`-Wl,--end-group`),
re(`-Wl,--(no-)?export-dynamic`),
re(`-Wl,-framework,[^,@\-][^,]+`),
re(`-Wl,-headerpad_max_install_names`),
re(`-Wl,--no-undefined`),
re(`-Wl,-R([^@\-][^,@]*$)`),
re(`-Wl,--just-symbols[=,]([^,@\-][^,@]+)`),
re(`-Wl,-rpath(-link)?[=,]([^,@\-][^,]+)`),
re(`-Wl,-s`),
re(`-Wl,-search_paths_first`),
re(`-Wl,-sectcreate,([^,@\-][^,]+),([^,@\-][^,]+),([^,@\-][^,]+)`),
re(`-Wl,--start-group`),
re(`-Wl,-?-static`),
re(`-Wl,-?-subsystem,(native|windows|console|posix|xbox)`),
re(`-Wl,-syslibroot[=,]([^,@\-][^,]+)`),
re(`-Wl,-undefined[=,]([^,@\-][^,]+)`),
re(`-Wl,-?-unresolved-symbols=[^,]+`),
re(`-Wl,--(no-)?warn-([^,]+)`),
re(`-Wl,-z,(no)?execstack`),
re(`-Wl,-z,relro`),
re(`[a-zA-Z0-9_/].*\.(a|o|obj|dll|dylib|so)`), // direct linker inputs: x.o or libfoo.so (but not -foo.o or @foo.o)
re(`\./.*\.(a|o|obj|dll|dylib|so)`),
}
var validLinkerFlagsWithNextArg = []string{
"-arch",
"-F",
"-l",
"-L",
"-framework",
"-isysroot",
"--sysroot",
"-target",
"-Wl,-framework",
"-Wl,-rpath",
"-Wl,-R",
"-Wl,--just-symbols",
"-Wl,-undefined",
}
func checkCompilerFlags(name string, list []string) error {
return checkFlags(name, list, validCompilerFlags, validCompilerFlagsWithNextArg)
}
func checkLinkerFlags(name string, list []string) error {
return checkFlags(name, list, validLinkerFlags, validLinkerFlagsWithNextArg)
}
func checkFlags(name string, list []string, valid []*regexp.Regexp, validNext []string) error {
// Let users override rules with $CGO_CFLAGS_ALLOW, $CGO_CFLAGS_DISALLOW, etc.
var (
allow *regexp.Regexp
disallow *regexp.Regexp
)
if env := os.Getenv("CGO_" + name + "_ALLOW"); env != "" {
r, err := regexp.Compile(env)
if err != nil {
return fmt.Errorf("parsing $CGO_%s_ALLOW: %v", name, err)
}
allow = r
}
if env := os.Getenv("CGO_" + name + "_DISALLOW"); env != "" {
r, err := regexp.Compile(env)
if err != nil {
return fmt.Errorf("parsing $CGO_%s_DISALLOW: %v", name, err)
}
disallow = r
}
Args:
for i := 0; i < len(list); i++ {
arg := list[i]
if disallow != nil && disallow.FindString(arg) == arg {
goto Bad
}
if allow != nil && allow.FindString(arg) == arg {
continue Args
}
for _, re := range valid {
if re.FindString(arg) == arg { // must be complete match
continue Args
}
}
for _, x := range validNext {
if arg == x {
if i+1 < len(list) && safeArg(list[i+1]) {
i++
continue Args
}
// Permit -Wl,-framework -Wl,name.
if i+1 < len(list) &&
strings.HasPrefix(arg, "-Wl,") &&
strings.HasPrefix(list[i+1], "-Wl,") &&
safeArg(list[i+1][4:]) &&
!strings.Contains(list[i+1][4:], ",") {
i++
continue Args
}
if i+1 < len(list) {
return fmt.Errorf("invalid flag: %s %s (see https://golang.org/s/invalidflag)", arg, list[i+1])
}
return fmt.Errorf("invalid flag: %s without argument (see https://golang.org/s/invalidflag)", arg)
}
}
Bad:
return fmt.Errorf("invalid flag: %s", arg)
}
return nil
}
func safeArg(name string) bool {
if name == "" {
return false
}
c := name[0]
return '0' <= c && c <= '9' || 'A' <= c && c <= 'Z' || 'a' <= c && c <= 'z' || c == '.' || c == '_' || c == '/' || c >= utf8.RuneSelf
}
-260
View File
@@ -1,260 +0,0 @@
// Copyright 2018 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// This file has been copied from the Go 1.13 release tree.
package cgo
import (
"os"
"testing"
)
var goodCompilerFlags = [][]string{
{"-DFOO"},
{"-Dfoo=bar"},
{"-F/Qt"},
{"-I/"},
{"-I/etc/passwd"},
{"-I."},
{"-O"},
{"-O2"},
{"-Osmall"},
{"-W"},
{"-Wall"},
{"-fobjc-arc"},
{"-fno-objc-arc"},
{"-fomit-frame-pointer"},
{"-fno-omit-frame-pointer"},
{"-fpic"},
{"-fno-pic"},
{"-fPIC"},
{"-fno-PIC"},
{"-fpie"},
{"-fno-pie"},
{"-fPIE"},
{"-fno-PIE"},
{"-fsplit-stack"},
{"-fno-split-stack"},
{"-fstack-xxx"},
{"-fno-stack-xxx"},
{"-fsanitize=hands"},
{"-g"},
{"-ggdb"},
{"-march=souza"},
{"-mcpu=123"},
{"-mfpu=123"},
{"-mtune=happybirthday"},
{"-mstack-overflow"},
{"-mno-stack-overflow"},
{"-mmacosx-version"},
{"-mnop-fun-dllimport"},
{"-pthread"},
{"-std=c99"},
{"-xc"},
{"-D", "FOO"},
{"-D", "foo=bar"},
{"-I", "."},
{"-I", "/etc/passwd"},
{"-I", "世界"},
{"-framework", "Chocolate"},
{"-x", "c"},
{"-v"},
}
var badCompilerFlags = [][]string{
{"-D@X"},
{"-D-X"},
{"-F@dir"},
{"-F-dir"},
{"-I@dir"},
{"-I-dir"},
{"-O@1"},
{"-Wa,-foo"},
{"-W@foo"},
{"-g@gdb"},
{"-g-gdb"},
{"-march=@dawn"},
{"-march=-dawn"},
{"-std=@c99"},
{"-std=-c99"},
{"-x@c"},
{"-x-c"},
{"-D", "@foo"},
{"-D", "-foo"},
{"-I", "@foo"},
{"-I", "-foo"},
{"-framework", "-Caffeine"},
{"-framework", "@Home"},
{"-x", "--c"},
{"-x", "@obj"},
}
func TestCheckCompilerFlags(t *testing.T) {
for _, f := range goodCompilerFlags {
if err := checkCompilerFlags("test", f); err != nil {
t.Errorf("unexpected error for %q: %v", f, err)
}
}
for _, f := range badCompilerFlags {
if err := checkCompilerFlags("test", f); err == nil {
t.Errorf("missing error for %q", f)
}
}
}
var goodLinkerFlags = [][]string{
{"-Fbar"},
{"-lbar"},
{"-Lbar"},
{"-fpic"},
{"-fno-pic"},
{"-fPIC"},
{"-fno-PIC"},
{"-fpie"},
{"-fno-pie"},
{"-fPIE"},
{"-fno-PIE"},
{"-fsanitize=hands"},
{"-g"},
{"-ggdb"},
{"-march=souza"},
{"-mcpu=123"},
{"-mfpu=123"},
{"-mtune=happybirthday"},
{"-pic"},
{"-pthread"},
{"-Wl,-rpath,foo"},
{"-Wl,-rpath,$ORIGIN/foo"},
{"-Wl,-R", "/foo"},
{"-Wl,-R", "foo"},
{"-Wl,-R,foo"},
{"-Wl,--just-symbols=foo"},
{"-Wl,--just-symbols,foo"},
{"-Wl,--warn-error"},
{"-Wl,--no-warn-error"},
{"foo.so"},
{"_世界.dll"},
{"./x.o"},
{"libcgosotest.dylib"},
{"-F", "framework"},
{"-l", "."},
{"-l", "/etc/passwd"},
{"-l", "世界"},
{"-L", "framework"},
{"-framework", "Chocolate"},
{"-v"},
{"-Wl,-framework", "-Wl,Chocolate"},
{"-Wl,-framework,Chocolate"},
{"-Wl,-unresolved-symbols=ignore-all"},
}
var badLinkerFlags = [][]string{
{"-DFOO"},
{"-Dfoo=bar"},
{"-W"},
{"-Wall"},
{"-fobjc-arc"},
{"-fno-objc-arc"},
{"-fomit-frame-pointer"},
{"-fno-omit-frame-pointer"},
{"-fsplit-stack"},
{"-fno-split-stack"},
{"-fstack-xxx"},
{"-fno-stack-xxx"},
{"-mstack-overflow"},
{"-mno-stack-overflow"},
{"-mnop-fun-dllimport"},
{"-std=c99"},
{"-xc"},
{"-D", "FOO"},
{"-D", "foo=bar"},
{"-I", "FOO"},
{"-L", "@foo"},
{"-L", "-foo"},
{"-x", "c"},
{"-D@X"},
{"-D-X"},
{"-I@dir"},
{"-I-dir"},
{"-O@1"},
{"-Wa,-foo"},
{"-W@foo"},
{"-g@gdb"},
{"-g-gdb"},
{"-march=@dawn"},
{"-march=-dawn"},
{"-std=@c99"},
{"-std=-c99"},
{"-x@c"},
{"-x-c"},
{"-D", "@foo"},
{"-D", "-foo"},
{"-I", "@foo"},
{"-I", "-foo"},
{"-l", "@foo"},
{"-l", "-foo"},
{"-framework", "-Caffeine"},
{"-framework", "@Home"},
{"-Wl,-framework,-Caffeine"},
{"-Wl,-framework", "-Wl,@Home"},
{"-Wl,-framework", "@Home"},
{"-Wl,-framework,Chocolate,@Home"},
{"-x", "--c"},
{"-x", "@obj"},
{"-Wl,-rpath,@foo"},
{"-Wl,-R,foo,bar"},
{"-Wl,-R,@foo"},
{"-Wl,--just-symbols,@foo"},
{"../x.o"},
}
func TestCheckLinkerFlags(t *testing.T) {
for _, f := range goodLinkerFlags {
if err := checkLinkerFlags("test", f); err != nil {
t.Errorf("unexpected error for %q: %v", f, err)
}
}
for _, f := range badLinkerFlags {
if err := checkLinkerFlags("test", f); err == nil {
t.Errorf("missing error for %q", f)
}
}
}
func TestCheckFlagAllowDisallow(t *testing.T) {
if err := checkCompilerFlags("TEST", []string{"-disallow"}); err == nil {
t.Fatalf("missing error for -disallow")
}
os.Setenv("CGO_TEST_ALLOW", "-disallo")
if err := checkCompilerFlags("TEST", []string{"-disallow"}); err == nil {
t.Fatalf("missing error for -disallow with CGO_TEST_ALLOW=-disallo")
}
os.Setenv("CGO_TEST_ALLOW", "-disallow")
if err := checkCompilerFlags("TEST", []string{"-disallow"}); err != nil {
t.Fatalf("unexpected error for -disallow with CGO_TEST_ALLOW=-disallow: %v", err)
}
os.Unsetenv("CGO_TEST_ALLOW")
if err := checkCompilerFlags("TEST", []string{"-Wall"}); err != nil {
t.Fatalf("unexpected error for -Wall: %v", err)
}
os.Setenv("CGO_TEST_DISALLOW", "-Wall")
if err := checkCompilerFlags("TEST", []string{"-Wall"}); err == nil {
t.Fatalf("missing error for -Wall with CGO_TEST_DISALLOW=-Wall")
}
os.Setenv("CGO_TEST_ALLOW", "-Wall") // disallow wins
if err := checkCompilerFlags("TEST", []string{"-Wall"}); err == nil {
t.Fatalf("missing error for -Wall with CGO_TEST_DISALLOW=-Wall and CGO_TEST_ALLOW=-Wall")
}
os.Setenv("CGO_TEST_ALLOW", "-fplugin.*")
os.Setenv("CGO_TEST_DISALLOW", "-fplugin=lint.so")
if err := checkCompilerFlags("TEST", []string{"-fplugin=faster.so"}); err != nil {
t.Fatalf("unexpected error for -fplugin=faster.so: %v", err)
}
if err := checkCompilerFlags("TEST", []string{"-fplugin=lint.so"}); err == nil {
t.Fatalf("missing error for -fplugin=lint.so: %v", err)
}
}
-3
View File
@@ -1,3 +0,0 @@
package main
import "C"
-26
View File
@@ -1,26 +0,0 @@
package main
import "unsafe"
var _ unsafe.Pointer
type C.int16_t = int16
type C.int32_t = int32
type C.int64_t = int64
type C.int8_t = int8
type C.uint16_t = uint16
type C.uint32_t = uint32
type C.uint64_t = uint64
type C.uint8_t = uint8
type C.uintptr_t = uintptr
type C.char uint8
type C.int int32
type C.long int32
type C.longlong int64
type C.schar int8
type C.short int16
type C.uchar uint8
type C.uint uint32
type C.ulong uint32
type C.ulonglong uint64
type C.ushort uint16
-12
View File
@@ -1,12 +0,0 @@
package main
/*
#define foo 3
#define bar foo
*/
import "C"
const (
Foo = C.foo
Bar = C.bar
)
-29
View File
@@ -1,29 +0,0 @@
package main
import "unsafe"
var _ unsafe.Pointer
const C.bar = C.foo
const C.foo = 3
type C.int16_t = int16
type C.int32_t = int32
type C.int64_t = int64
type C.int8_t = int8
type C.uint16_t = uint16
type C.uint32_t = uint32
type C.uint64_t = uint64
type C.uint8_t = uint8
type C.uintptr_t = uintptr
type C.char uint8
type C.int int32
type C.long int32
type C.longlong int64
type C.schar int8
type C.short int16
type C.uchar uint8
type C.uint uint32
type C.ulong uint32
type C.ulonglong uint64
type C.ushort uint16
-33
View File
@@ -1,33 +0,0 @@
package main
/*
#warning some warning
typedef struct {
int x;
int y;
} point_t;
typedef someType noType; // undefined type
#define SOME_CONST_1 5) // invalid const syntax
#define SOME_CONST_2 6) // const not used (so no error)
#define SOME_CONST_3 1234 // const too large for byte
*/
import "C"
// Make sure that errors for the following lines won't change with future
// additions to the CGo preamble.
//line errors.go:100
var (
// constant too large
_ C.uint8_t = 2 << 10
// z member does not exist
_ C.point_t = C.point_t{z: 3}
// constant has syntax error
_ = C.SOME_CONST_1
_ byte = C.SOME_CONST_3
)
-43
View File
@@ -1,43 +0,0 @@
// CGo errors:
// testdata/errors.go:4:2: warning: some warning
// testdata/errors.go:11:9: error: unknown type name 'someType'
// testdata/errors.go:13:23: unexpected token )
// Type checking errors after CGo processing:
// testdata/errors.go:102: 2 << 10 (untyped int constant 2048) overflows uint8
// testdata/errors.go:105: unknown field z in struct literal
// testdata/errors.go:108: undeclared name: C.SOME_CONST_1
// testdata/errors.go:110: C.SOME_CONST_3 (untyped int constant 1234) overflows byte
package main
import "unsafe"
var _ unsafe.Pointer
const C.SOME_CONST_3 = 1234
type C.int16_t = int16
type C.int32_t = int32
type C.int64_t = int64
type C.int8_t = int8
type C.uint16_t = uint16
type C.uint32_t = uint32
type C.uint64_t = uint64
type C.uint8_t = uint8
type C.uintptr_t = uintptr
type C.char uint8
type C.int int32
type C.long int32
type C.longlong int64
type C.schar int8
type C.short int16
type C.uchar uint8
type C.uint uint32
type C.ulong uint32
type C.ulonglong uint64
type C.ushort uint16
type C.point_t = struct {
x C.int
y C.int
}
-30
View File
@@ -1,30 +0,0 @@
package main
/*
// this name doesn't exist
#cgo NOFLAGS: -foo
// unknown flag
#cgo CFLAGS: -fdoes-not-exist -DNOTDEFINED
#cgo CFLAGS: -DFOO
#cgo CFLAGS: -Iinclude
#include "foo.h"
#if defined(FOO)
#define BAR 3
#else
#define BAR 5
#endif
#if defined(NOTDEFINED)
#warning flag must not be defined
#endif
*/
import "C"
var (
_ = C.BAR
_ = C.FOO_H
)
-33
View File
@@ -1,33 +0,0 @@
// CGo errors:
// testdata/flags.go:5:7: invalid #cgo line: NOFLAGS
// testdata/flags.go:8:13: invalid flag: -fdoes-not-exist
package main
import "unsafe"
var _ unsafe.Pointer
const C.BAR = 3
const C.FOO_H = 1
type C.int16_t = int16
type C.int32_t = int32
type C.int64_t = int64
type C.int8_t = int8
type C.uint16_t = uint16
type C.uint32_t = uint32
type C.uint64_t = uint64
type C.uint8_t = uint8
type C.uintptr_t = uintptr
type C.char uint8
type C.int int32
type C.long int32
type C.longlong int64
type C.schar int8
type C.short int16
type C.uchar uint8
type C.uint uint32
type C.ulong uint32
type C.ulonglong uint64
type C.ushort uint16
-1
View File
@@ -1 +0,0 @@
#define FOO_H 1
-165
View File
@@ -1,165 +0,0 @@
package main
/*
// Simple typedef.
typedef int myint;
// Structs, with or without name.
typedef struct {
int x;
int y;
} point2d_t;
typedef struct point3d {
int x;
int y;
int z;
} point3d_t;
// Structs with reserved field names.
struct type1 {
// All these fields should be renamed.
int type;
int _type;
int __type;
};
struct type2 {
// This field should not be renamed.
int _type;
};
// Unions.
typedef union {
// Union should be treated as a struct.
int i;
} union1_t;
typedef union {
// Union must contain a single field and have special getters/setters.
int i;
double d;
short s;
} union3_t;
typedef union union2d {
int i;
double d[2];
} union2d_t;
typedef union {
unsigned char arr[10];
} unionarray_t;
// Nested structs and unions.
typedef struct {
point2d_t begin;
point2d_t end;
int tag;
union {
point2d_t area;
point3d_t solid;
} coord;
} struct_nested_t;
typedef union {
point3d_t point;
unionarray_t array;
union3_t thing;
} union_nested_t;
// Enums. These define constant numbers. All these constants must be given the
// correct number.
typedef enum option {
optionA,
optionB,
optionC = -5,
optionD,
optionE = 10,
optionF,
optionG,
} option_t;
enum unused {
unused1 = 5,
};
// Anonymous enum.
typedef enum {
option2A = 20,
} option2_t;
// Various types that are usually translated directly to Go types, but storing
// them in a struct reveals them.
typedef struct {
float f;
double d;
int *ptr;
} types_t;
// Arrays.
typedef int myIntArray[10];
// Bitfields.
typedef struct {
unsigned char start;
unsigned char a : 5;
unsigned char b : 1;
unsigned char c : 2;
unsigned char :0; // new field
unsigned char d : 6;
unsigned char e : 3;
// Note that C++ allows bitfields bigger than the underlying type.
} bitfield_t;
*/
import "C"
var (
// Simple typedefs.
_ C.myint
// Structs.
_ C.point2d_t
_ C.point3d_t
_ C.struct_point3d
// Structs with reserved field names.
_ C.struct_type1
_ C.struct_type2
// Unions.
_ C.union1_t
_ C.union3_t
_ C.union2d_t
_ C.unionarray_t
// Nested structs and unions.
_ C.struct_nested_t
_ C.union_nested_t
// Enums (anonymous and named).
_ C.option_t
_ C.enum_option
_ C.option2_t
// Various types.
_ C.types_t
// Arrays.
_ C.myIntArray
)
// Test bitfield accesses.
func accessBitfields() {
var x C.bitfield_t
x.start = 3
x.set_bitfield_a(4)
x.set_bitfield_b(1)
x.set_bitfield_c(2)
x.d = 10
x.e = 5
var _ C.uchar = x.bitfield_a()
}
// Test union accesses.
func accessUnion() {
var union1 C.union1_t
union1.i = 5
var union2d C.union2d_t
var _ *C.int = union2d.unionfield_i()
var _ *[2]float64 = union2d.unionfield_d()
}
-145
View File
@@ -1,145 +0,0 @@
package main
import "unsafe"
var _ unsafe.Pointer
const C.option2A = 20
const C.optionA = 0
const C.optionB = 1
const C.optionC = -5
const C.optionD = -4
const C.optionE = 10
const C.optionF = 11
const C.optionG = 12
const C.unused1 = 5
type C.int16_t = int16
type C.int32_t = int32
type C.int64_t = int64
type C.int8_t = int8
type C.uint16_t = uint16
type C.uint32_t = uint32
type C.uint64_t = uint64
type C.uint8_t = uint8
type C.uintptr_t = uintptr
type C.char uint8
type C.int int32
type C.long int32
type C.longlong int64
type C.schar int8
type C.short int16
type C.uchar uint8
type C.uint uint32
type C.ulong uint32
type C.ulonglong uint64
type C.ushort uint16
type C.bitfield_t = C.struct_4
type C.myIntArray = [10]C.int
type C.myint = C.int
type C.option2_t = C.uint
type C.option_t = C.enum_option
type C.point2d_t = struct {
x C.int
y C.int
}
type C.point3d_t = C.struct_point3d
type C.struct_nested_t = struct {
begin C.point2d_t
end C.point2d_t
tag C.int
coord C.union_2
}
type C.types_t = struct {
f float32
d float64
ptr *C.int
}
type C.union1_t = struct{ i C.int }
type C.union2d_t = C.union_union2d
type C.union3_t = C.union_1
type C.union_nested_t = C.union_3
type C.unionarray_t = struct{ arr [10]C.uchar }
func (s *C.struct_4) bitfield_a() C.uchar {
return s.__bitfield_1 & 0x1f
}
func (s *C.struct_4) set_bitfield_a(value C.uchar) {
s.__bitfield_1 = s.__bitfield_1&^0x1f | value&0x1f<<0
}
func (s *C.struct_4) bitfield_b() C.uchar {
return s.__bitfield_1 >> 5 & 0x1
}
func (s *C.struct_4) set_bitfield_b(value C.uchar) {
s.__bitfield_1 = s.__bitfield_1&^0x20 | value&0x1<<5
}
func (s *C.struct_4) bitfield_c() C.uchar {
return s.__bitfield_1 >> 6
}
func (s *C.struct_4) set_bitfield_c(value C.uchar,
) { s.__bitfield_1 = s.__bitfield_1&0x3f | value<<6 }
type C.struct_4 struct {
start C.uchar
__bitfield_1 C.uchar
d C.uchar
e C.uchar
}
type C.struct_point3d struct {
x C.int
y C.int
z C.int
}
type C.struct_type1 struct {
_type C.int
__type C.int
___type C.int
}
type C.struct_type2 struct{ _type C.int }
func (union *C.union_1) unionfield_i() *C.int {
return (*C.int)(unsafe.Pointer(&union.$union))
}
func (union *C.union_1) unionfield_d() *float64 {
return (*float64)(unsafe.Pointer(&union.$union))
}
func (union *C.union_1) unionfield_s() *C.short {
return (*C.short)(unsafe.Pointer(&union.$union))
}
type C.union_1 struct{ $union uint64 }
func (union *C.union_2) unionfield_area() *C.point2d_t {
return (*C.point2d_t)(unsafe.Pointer(&union.$union))
}
func (union *C.union_2) unionfield_solid() *C.point3d_t {
return (*C.point3d_t)(unsafe.Pointer(&union.$union))
}
type C.union_2 struct{ $union [3]uint32 }
func (union *C.union_3) unionfield_point() *C.point3d_t {
return (*C.point3d_t)(unsafe.Pointer(&union.$union))
}
func (union *C.union_3) unionfield_array() *C.unionarray_t {
return (*C.unionarray_t)(unsafe.Pointer(&union.$union))
}
func (union *C.union_3) unionfield_thing() *C.union3_t {
return (*C.union3_t)(unsafe.Pointer(&union.$union))
}
type C.union_3 struct{ $union [2]uint64 }
func (union *C.union_union2d) unionfield_i() *C.int {
return (*C.int)(unsafe.Pointer(&union.$union))
}
func (union *C.union_union2d) unionfield_d() *[2]float64 {
return (*[2]float64)(unsafe.Pointer(&union.$union))
}
type C.union_union2d struct{ $union [2]uint64 }
type C.enum_option C.int
type C.enum_unused C.uint
+45
View File
@@ -0,0 +1,45 @@
package main
import (
"errors"
"os"
"os/exec"
"runtime"
"strings"
)
// Commands used by the compilation process might have different file names
// across operating systems and distributions.
var commands = map[string][]string{
"clang": {"clang-8"},
"ld.lld": {"ld.lld-8", "ld.lld"},
"wasm-ld": {"wasm-ld-8", "wasm-ld"},
}
func init() {
// Add the path to a Homebrew-installed LLVM 8 for ease of use (no need to
// manually set $PATH).
if runtime.GOOS == "darwin" {
commands["clang"] = append(commands["clang"], "/usr/local/opt/llvm/bin/clang-8")
commands["ld.lld"] = append(commands["ld.lld"], "/usr/local/opt/llvm/bin/ld.lld")
commands["wasm-ld"] = append(commands["wasm-ld"], "/usr/local/opt/llvm/bin/wasm-ld")
}
}
func execCommand(cmdNames []string, args ...string) error {
for _, cmdName := range cmdNames {
cmd := exec.Command(cmdName, args...)
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
err := cmd.Run()
if err != nil {
if err, ok := err.(*exec.Error); ok && err.Err == exec.ErrNotFound {
// this command was not found, try the next
continue
}
return err
}
return nil
}
return errors.New("none of these commands were found in your $PATH: " + strings.Join(cmdNames, " "))
}
-278
View File
@@ -1,278 +0,0 @@
// Package compileopts contains the configuration for a single to-be-built
// binary.
package compileopts
import (
"errors"
"fmt"
"path/filepath"
"regexp"
"strconv"
"strings"
"github.com/tinygo-org/tinygo/goenv"
)
// Config keeps all configuration affecting the build in a single struct.
type Config struct {
Options *Options
Target *TargetSpec
GoMinorVersion int
ClangHeaders string // Clang built-in header include path
TestConfig TestConfig
}
// FuncValueImplementation is an enum for the particular implementations of Go
// func values.
type FuncValueImplementation int
// These constants describe the various possible implementations of Go func
// values.
const (
FuncValueNone FuncValueImplementation = iota
// A func value is implemented as a pair of pointers:
// {context, function pointer}
// where the context may be a pointer to a heap-allocated struct containing
// the free variables, or it may be undef if the function being pointed to
// doesn't need a context. The function pointer is a regular function
// pointer.
FuncValueDoubleword
// As funcValueDoubleword, but with the function pointer replaced by a
// unique ID per function signature. Function values are called by using a
// switch statement and choosing which function to call.
FuncValueSwitch
)
// Triple returns the LLVM target triple, like armv6m-none-eabi.
func (c *Config) Triple() string {
return c.Target.Triple
}
// CPU returns the LLVM CPU name, like atmega328p or arm7tdmi. It may return an
// empty string if the CPU name is not known.
func (c *Config) CPU() string {
return c.Target.CPU
}
// Features returns a list of features this CPU supports. For example, for a
// RISC-V processor, that could be ["+a", "+c", "+m"]. For many targets, an
// empty list will be returned.
func (c *Config) Features() []string {
return c.Target.Features
}
// GOOS returns the GOOS of the target. This might not always be the actual OS:
// for example, bare-metal targets will usually pretend to be linux to get the
// standard library to compile.
func (c *Config) GOOS() string {
return c.Target.GOOS
}
// GOARCH returns the GOARCH of the target. This might not always be the actual
// archtecture: for example, the AVR target is not supported by the Go standard
// library so such targets will usually pretend to be linux/arm.
func (c *Config) GOARCH() string {
return c.Target.GOARCH
}
// BuildTags returns the complete list of build tags used during this build.
func (c *Config) BuildTags() []string {
tags := append(c.Target.BuildTags, []string{"tinygo", "gc." + c.GC(), "scheduler." + c.Scheduler()}...)
for i := 1; i <= c.GoMinorVersion; i++ {
tags = append(tags, fmt.Sprintf("go1.%d", i))
}
if extraTags := strings.Fields(c.Options.Tags); len(extraTags) != 0 {
tags = append(tags, extraTags...)
}
return tags
}
// CgoEnabled returns true if (and only if) CGo is enabled. It is true by
// default and false if CGO_ENABLED is set to "0".
func (c *Config) CgoEnabled() bool {
return goenv.Get("CGO_ENABLED") == "1"
}
// GC returns the garbage collection strategy in use on this platform. Valid
// values are "none", "leaking", "extalloc", and "conservative".
func (c *Config) GC() string {
if c.Options.GC != "" {
return c.Options.GC
}
if c.Target.GC != "" {
return c.Target.GC
}
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
// that can be traced by the garbage collector.
func (c *Config) NeedsStackObjects() bool {
switch c.GC() {
case "conservative", "extalloc":
for _, tag := range c.BuildTags() {
if tag == "baremetal" {
return false
}
}
return true
default:
return false
}
}
// Scheduler returns the scheduler implementation. Valid values are "coroutines"
// and "tasks".
func (c *Config) Scheduler() string {
if c.Options.Scheduler != "" {
return c.Options.Scheduler
}
if c.Target.Scheduler != "" {
return c.Target.Scheduler
}
// Fall back to coroutines, which are supported everywhere.
return "coroutines"
}
// FuncImplementation picks an appropriate func value implementation for the
// target.
func (c *Config) FuncImplementation() FuncValueImplementation {
// Always pick the switch implementation, as it allows the use of blocking
// inside a function that is used as a func value.
switch c.Scheduler() {
case "none", "coroutines":
return FuncValueSwitch
case "tasks":
return FuncValueDoubleword
default:
panic("unknown scheduler type")
}
}
// PanicStrategy returns the panic strategy selected for this target. Valid
// values are "print" (print the panic value, then exit) or "trap" (issue a trap
// instruction).
func (c *Config) PanicStrategy() string {
return c.Options.PanicStrategy
}
// CFlags returns the flags to pass to the C compiler. This is necessary for CGo
// preprocessing.
func (c *Config) CFlags() []string {
cflags := append([]string{}, c.Options.CFlags...)
for _, flag := range c.Target.CFlags {
cflags = append(cflags, strings.Replace(flag, "{root}", goenv.Get("TINYGOROOT"), -1))
}
if c.Target.Libc == "picolibc" {
root := goenv.Get("TINYGOROOT")
cflags = append(cflags, "-nostdlibinc", "-Xclang", "-internal-isystem", "-Xclang", filepath.Join(root, "lib", "picolibc", "newlib", "libc", "include"))
cflags = append(cflags, "-I"+filepath.Join(root, "lib/picolibc-include"))
}
return cflags
}
// LDFlags returns the flags to pass to the linker. A few more flags are needed
// (like the one for the compiler runtime), but this represents the majority of
// the flags.
func (c *Config) LDFlags() []string {
root := goenv.Get("TINYGOROOT")
// Merge and adjust LDFlags.
ldflags := append([]string{}, c.Options.LDFlags...)
for _, flag := range c.Target.LDFlags {
ldflags = append(ldflags, strings.Replace(flag, "{root}", root, -1))
}
ldflags = append(ldflags, "-L", root)
if c.Target.GOARCH == "wasm" {
// Round heap size to next multiple of 65536 (the WebAssembly page
// size).
heapSize := (c.Options.HeapSize + (65536 - 1)) &^ (65536 - 1)
ldflags = append(ldflags, "--initial-memory="+strconv.FormatInt(heapSize, 10))
}
if c.Target.LinkerScript != "" {
ldflags = append(ldflags, "-T", c.Target.LinkerScript)
}
return ldflags
}
// ExtraFiles returns the list of extra files to be built and linked with the
// executable. This can include extra C and assembly files.
func (c *Config) ExtraFiles() []string {
return c.Target.ExtraFiles
}
// DumpSSA returns whether to dump Go SSA while compiling (-dumpssa flag). Only
// enable this for debugging.
func (c *Config) DumpSSA() bool {
return c.Options.DumpSSA
}
// VerifyIR returns whether to run extra checks on the IR. This is normally
// disabled but enabled during testing.
func (c *Config) VerifyIR() bool {
return c.Options.VerifyIR
}
// Debug returns whether to add debug symbols to the IR, for debugging with GDB
// and similar.
func (c *Config) Debug() bool {
return c.Options.Debug
}
// Programmer returns the flash method and OpenOCD interface name given a
// particular configuration. It may either be all configured in the target JSON
// file or be modified using the -programmmer command-line option.
func (c *Config) Programmer() (method, openocdInterface string) {
switch c.Options.Programmer {
case "":
// No configuration supplied.
return c.Target.FlashMethod, c.Target.OpenOCDInterface
case "openocd", "msd", "command":
// The -programmer flag only specifies the flash method.
return c.Options.Programmer, c.Target.OpenOCDInterface
default:
// The -programmer flag specifies something else, assume it specifies
// the OpenOCD interface name.
return "openocd", c.Options.Programmer
}
}
// OpenOCDConfiguration returns a list of command line arguments to OpenOCD.
// This list of command-line arguments is based on the various OpenOCD-related
// flags in the target specification.
func (c *Config) OpenOCDConfiguration() (args []string, err error) {
_, openocdInterface := c.Programmer()
if openocdInterface == "" {
return nil, errors.New("OpenOCD programmer not set")
}
if !regexp.MustCompile("^[\\p{L}0-9_-]+$").MatchString(openocdInterface) {
return nil, fmt.Errorf("OpenOCD programmer has an invalid name: %#v", openocdInterface)
}
if c.Target.OpenOCDTarget == "" {
return nil, errors.New("OpenOCD chip not set")
}
if !regexp.MustCompile("^[\\p{L}0-9_-]+$").MatchString(c.Target.OpenOCDTarget) {
return nil, fmt.Errorf("OpenOCD target has an invalid name: %#v", c.Target.OpenOCDTarget)
}
if c.Target.OpenOCDTransport != "" && c.Target.OpenOCDTransport != "swd" {
return nil, fmt.Errorf("unknown OpenOCD transport: %#v", c.Target.OpenOCDTransport)
}
args = []string{"-f", "interface/" + openocdInterface + ".cfg"}
if c.Target.OpenOCDTransport != "" {
args = append(args, "-c", "transport select "+c.Target.OpenOCDTransport)
}
args = append(args, "-f", "target/"+c.Target.OpenOCDTarget+".cfg")
return args, nil
}
type TestConfig struct {
CompileTestBinary bool
// TODO: Filter the test functions to run, include verbose flag, etc
}
-23
View File
@@ -1,23 +0,0 @@
package compileopts
// Options contains extra options to give to the compiler. These options are
// usually passed from the command line.
type Options struct {
Target string
Opt string
GC string
PanicStrategy string
Scheduler string
PrintIR bool
DumpSSA bool
VerifyIR bool
Debug bool
PrintSizes string
CFlags []string
LDFlags []string
Tags string
WasmAbi string
HeapSize int64
TestConfig TestConfig
Programmer string
}
-301
View File
@@ -1,301 +0,0 @@
package compileopts
// This file loads a target specification from a JSON file.
import (
"encoding/json"
"errors"
"io"
"os"
"path/filepath"
"runtime"
"strings"
"github.com/tinygo-org/tinygo/goenv"
)
// Target specification for a given target. Used for bare metal targets.
//
// The target specification is mostly inspired by Rust:
// https://doc.rust-lang.org/nightly/nightly-rustc/rustc_target/spec/struct.TargetOptions.html
// https://github.com/shepmaster/rust-arduino-blink-led-no-core-with-cargo/blob/master/blink/arduino.json
type TargetSpec struct {
Inherits []string `json:"inherits"`
Triple string `json:"llvm-target"`
CPU string `json:"cpu"`
Features []string `json:"features"`
GOOS string `json:"goos"`
GOARCH string `json:"goarch"`
BuildTags []string `json:"build-tags"`
GC string `json:"gc"`
Scheduler string `json:"scheduler"`
Compiler string `json:"compiler"`
Linker string `json:"linker"`
RTLib string `json:"rtlib"` // compiler runtime library (libgcc, compiler-rt)
Libc string `json:"libc"`
CFlags []string `json:"cflags"`
LDFlags []string `json:"ldflags"`
LinkerScript string `json:"linkerscript"`
ExtraFiles []string `json:"extra-files"`
Emulator []string `json:"emulator"`
FlashCommand string `json:"flash-command"`
GDB string `json:"gdb"`
PortReset string `json:"flash-1200-bps-reset"`
FlashMethod string `json:"flash-method"`
FlashVolume string `json:"msd-volume-name"`
FlashFilename string `json:"msd-firmware-name"`
UF2FamilyID string `json:"uf2-family-id"`
OpenOCDInterface string `json:"openocd-interface"`
OpenOCDTarget string `json:"openocd-target"`
OpenOCDTransport string `json:"openocd-transport"`
JLinkDevice string `json:"jlink-device"`
}
// copyProperties copies all properties that are set in spec2 into itself.
func (spec *TargetSpec) copyProperties(spec2 *TargetSpec) {
// TODO: simplify this using reflection? Inherits and BuildTags are special
// cases, but the rest can simply be copied if set.
spec.Inherits = append(spec.Inherits, spec2.Inherits...)
if spec2.Triple != "" {
spec.Triple = spec2.Triple
}
if spec2.CPU != "" {
spec.CPU = spec2.CPU
}
spec.Features = append(spec.Features, spec2.Features...)
if spec2.GOOS != "" {
spec.GOOS = spec2.GOOS
}
if spec2.GOARCH != "" {
spec.GOARCH = spec2.GOARCH
}
spec.BuildTags = append(spec.BuildTags, spec2.BuildTags...)
if spec2.GC != "" {
spec.GC = spec2.GC
}
if spec2.Scheduler != "" {
spec.Scheduler = spec2.Scheduler
}
if spec2.Compiler != "" {
spec.Compiler = spec2.Compiler
}
if spec2.Linker != "" {
spec.Linker = spec2.Linker
}
if spec2.RTLib != "" {
spec.RTLib = spec2.RTLib
}
if spec2.Libc != "" {
spec.Libc = spec2.Libc
}
spec.CFlags = append(spec.CFlags, spec2.CFlags...)
spec.LDFlags = append(spec.LDFlags, spec2.LDFlags...)
if spec2.LinkerScript != "" {
spec.LinkerScript = spec2.LinkerScript
}
spec.ExtraFiles = append(spec.ExtraFiles, spec2.ExtraFiles...)
if len(spec2.Emulator) != 0 {
spec.Emulator = spec2.Emulator
}
if spec2.FlashCommand != "" {
spec.FlashCommand = spec2.FlashCommand
}
if spec2.GDB != "" {
spec.GDB = spec2.GDB
}
if spec2.PortReset != "" {
spec.PortReset = spec2.PortReset
}
if spec2.FlashMethod != "" {
spec.FlashMethod = spec2.FlashMethod
}
if spec2.FlashVolume != "" {
spec.FlashVolume = spec2.FlashVolume
}
if spec2.FlashFilename != "" {
spec.FlashFilename = spec2.FlashFilename
}
if spec2.UF2FamilyID != "" {
spec.UF2FamilyID = spec2.UF2FamilyID
}
if spec2.OpenOCDInterface != "" {
spec.OpenOCDInterface = spec2.OpenOCDInterface
}
if spec2.OpenOCDTarget != "" {
spec.OpenOCDTarget = spec2.OpenOCDTarget
}
if spec2.OpenOCDTransport != "" {
spec.OpenOCDTransport = spec2.OpenOCDTransport
}
if spec2.JLinkDevice != "" {
spec.JLinkDevice = spec2.JLinkDevice
}
}
// load reads a target specification from the JSON in the given io.Reader. It
// may load more targets specified using the "inherits" property.
func (spec *TargetSpec) load(r io.Reader) error {
err := json.NewDecoder(r).Decode(spec)
if err != nil {
return err
}
return nil
}
// loadFromGivenStr loads the TargetSpec from the given string that could be:
// - targets/ directory inside the compiler sources
// - a relative or absolute path to custom (project specific) target specification .json file;
// the Inherits[] could contain the files from target folder (ex. stm32f4disco)
// as well as path to custom files (ex. myAwesomeProject.json)
func (spec *TargetSpec) loadFromGivenStr(str string) error {
path := ""
if strings.HasSuffix(str, ".json") {
path, _ = filepath.Abs(str)
} else {
path = filepath.Join(goenv.Get("TINYGOROOT"), "targets", strings.ToLower(str)+".json")
}
fp, err := os.Open(path)
if err != nil {
return err
}
defer fp.Close()
return spec.load(fp)
}
// resolveInherits loads inherited targets, recursively.
func (spec *TargetSpec) resolveInherits() error {
// First create a new spec with all the inherited properties.
newSpec := &TargetSpec{}
for _, name := range spec.Inherits {
subtarget := &TargetSpec{}
err := subtarget.loadFromGivenStr(name)
if err != nil {
return err
}
err = subtarget.resolveInherits()
if err != nil {
return err
}
newSpec.copyProperties(subtarget)
}
// When all properties are loaded, make sure they are properly inherited.
newSpec.copyProperties(spec)
*spec = *newSpec
return nil
}
// Load a target specification.
func LoadTarget(target string) (*TargetSpec, error) {
if target == "" {
// Configure based on GOOS/GOARCH environment variables (falling back to
// 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{
"386": "i386",
"amd64": "x86_64",
"arm64": "aarch64",
}[goarch]
if llvmarch == "" {
llvmarch = goarch
}
target = llvmarch + "--" + llvmos
if goarch == "arm" {
target += "-gnueabihf"
}
return defaultTarget(goos, goarch, target)
}
// See whether there is a target specification for this target (e.g.
// Arduino).
spec := &TargetSpec{}
err := spec.loadFromGivenStr(target)
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
} 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",
"x86_64": "amd64",
"aarch64": "arm64",
"armv7": "arm",
}[tripleSplit[0]]
if goarch == "" {
goarch = tripleSplit[0]
}
return defaultTarget(goos, goarch, strings.Join(tripleSplit, "-"))
}
}
func defaultTarget(goos, goarch, triple string) (*TargetSpec, error) {
// No target spec available. Use the default one, useful on most systems
// with a regular OS.
spec := TargetSpec{
Triple: triple,
GOOS: goos,
GOARCH: goarch,
BuildTags: []string{goos, goarch},
Compiler: "clang",
Linker: "cc",
CFlags: []string{"--target=" + triple},
GDB: "gdb",
PortReset: "false",
FlashMethod: "native",
}
if goos == "darwin" {
spec.LDFlags = append(spec.LDFlags, "-Wl,-dead_strip")
} else {
spec.LDFlags = append(spec.LDFlags, "-no-pie", "-Wl,--gc-sections") // WARNING: clang < 5.0 requires -nopie
}
if goarch != runtime.GOARCH {
// Some educated guesses as to how to invoke helper programs.
if goarch == "arm" && goos == "linux" {
spec.CFlags = append(spec.CFlags, "--sysroot=/usr/arm-linux-gnueabihf")
spec.Linker = "arm-linux-gnueabihf-gcc"
spec.GDB = "arm-linux-gnueabihf-gdb"
spec.Emulator = []string{"qemu-arm", "-L", "/usr/arm-linux-gnueabihf"}
}
if goarch == "arm64" && goos == "linux" {
spec.CFlags = append(spec.CFlags, "--sysroot=/usr/aarch64-linux-gnu")
spec.Linker = "aarch64-linux-gnu-gcc"
spec.GDB = "aarch64-linux-gnu-gdb"
spec.Emulator = []string{"qemu-aarch64", "-L", "/usr/aarch64-linux-gnu"}
}
if goarch == "386" {
spec.CFlags = []string{"-m32"}
spec.LDFlags = []string{"-m32"}
}
}
return &spec, nil
}
-19
View File
@@ -1,19 +0,0 @@
package compileopts
import "testing"
func TestLoadTarget(t *testing.T) {
_, err := LoadTarget("arduino")
if err != nil {
t.Error("LoadTarget test failed:", err)
}
_, err = LoadTarget("notexist")
if err == nil {
t.Error("LoadTarget should have failed with non existing target")
}
if err.Error() != "expected a full LLVM target or a custom target in -target flag" {
t.Error("LoadTarget failed for wrong reason:", err)
}
}
+80 -143
View File
@@ -4,19 +4,16 @@ package compiler
// required by the Go programming language.
import (
"fmt"
"go/token"
"go/types"
"golang.org/x/tools/go/ssa"
"tinygo.org/x/go-llvm"
)
// createLookupBoundsCheck emits a bounds check before doing a lookup into a
// emitLookupBoundsCheck emits a bounds check before doing a lookup into a
// slice. This is required by the Go language spec: an index out of bounds must
// cause a panic.
func (b *builder) createLookupBoundsCheck(arrayLen, index llvm.Value, indexType types.Type) {
if b.fn.IsNoBounds() {
func (c *Compiler) emitLookupBoundsCheck(frame *Frame, arrayLen, index llvm.Value, indexType types.Type) {
if frame.fn.IsNoBounds() {
// The //go:nobounds pragma was added to the function to avoid bounds
// checking.
return
@@ -25,30 +22,42 @@ func (b *builder) createLookupBoundsCheck(arrayLen, index llvm.Value, indexType
if index.Type().IntTypeWidth() < arrayLen.Type().IntTypeWidth() {
// Sometimes, the index can be e.g. an uint8 or int8, and we have to
// correctly extend that type.
if indexType.Underlying().(*types.Basic).Info()&types.IsUnsigned == 0 {
index = b.CreateZExt(index, arrayLen.Type(), "")
if indexType.(*types.Basic).Info()&types.IsUnsigned == 0 {
index = c.builder.CreateZExt(index, arrayLen.Type(), "")
} else {
index = b.CreateSExt(index, arrayLen.Type(), "")
index = c.builder.CreateSExt(index, arrayLen.Type(), "")
}
} else if index.Type().IntTypeWidth() > arrayLen.Type().IntTypeWidth() {
// The index is bigger than the array length type, so extend it.
arrayLen = b.CreateZExt(arrayLen, index.Type(), "")
arrayLen = c.builder.CreateZExt(arrayLen, index.Type(), "")
}
faultBlock := c.ctx.AddBasicBlock(frame.fn.LLVMFn, "lookup.outofbounds")
nextBlock := c.ctx.AddBasicBlock(frame.fn.LLVMFn, "lookup.next")
frame.blockExits[frame.currentBlock] = nextBlock // adjust outgoing block for phi nodes
// Now do the bounds check: index >= arrayLen
outOfBounds := b.CreateICmp(llvm.IntUGE, index, arrayLen, "")
b.createRuntimeAssert(outOfBounds, "lookup", "lookupPanic")
outOfBounds := c.builder.CreateICmp(llvm.IntUGE, index, arrayLen, "")
c.builder.CreateCondBr(outOfBounds, faultBlock, nextBlock)
// Fail: this is a nil pointer, exit with a panic.
c.builder.SetInsertPointAtEnd(faultBlock)
c.createRuntimeCall("lookupPanic", nil, "")
c.builder.CreateUnreachable()
// Ok: this is a valid pointer.
c.builder.SetInsertPointAtEnd(nextBlock)
}
// createSliceBoundsCheck emits a bounds check before a slicing operation to make
// emitSliceBoundsCheck emits a bounds check before a slicing operation to make
// sure it is within bounds.
//
// This function is both used for slicing a slice (low and high have their
// normal meaning) and for creating a new slice, where 'capacity' means the
// biggest possible slice capacity, 'low' means len and 'high' means cap. The
// logic is the same in both cases.
func (b *builder) createSliceBoundsCheck(capacity, low, high, max llvm.Value, lowType, highType, maxType *types.Basic) {
if b.fn.IsNoBounds() {
func (c *Compiler) emitSliceBoundsCheck(frame *Frame, capacity, low, high, max llvm.Value, lowType, highType, maxType *types.Basic) {
if frame.fn.IsNoBounds() {
// The //go:nobounds pragma was added to the function to avoid bounds
// checking.
return
@@ -66,164 +75,92 @@ func (b *builder) createSliceBoundsCheck(capacity, low, high, max llvm.Value, lo
capacityType = max.Type()
}
if capacityType != capacity.Type() {
capacity = b.CreateZExt(capacity, capacityType, "")
capacity = c.builder.CreateZExt(capacity, capacityType, "")
}
// Extend low and high to be the same size as capacity.
if low.Type().IntTypeWidth() < capacityType.IntTypeWidth() {
if lowType.Info()&types.IsUnsigned != 0 {
low = b.CreateZExt(low, capacityType, "")
low = c.builder.CreateZExt(low, capacityType, "")
} else {
low = b.CreateSExt(low, capacityType, "")
low = c.builder.CreateSExt(low, capacityType, "")
}
}
if high.Type().IntTypeWidth() < capacityType.IntTypeWidth() {
if highType.Info()&types.IsUnsigned != 0 {
high = b.CreateZExt(high, capacityType, "")
high = c.builder.CreateZExt(high, capacityType, "")
} else {
high = b.CreateSExt(high, capacityType, "")
high = c.builder.CreateSExt(high, capacityType, "")
}
}
if max.Type().IntTypeWidth() < capacityType.IntTypeWidth() {
if maxType.Info()&types.IsUnsigned != 0 {
max = b.CreateZExt(max, capacityType, "")
max = c.builder.CreateZExt(max, capacityType, "")
} else {
max = b.CreateSExt(max, capacityType, "")
max = c.builder.CreateSExt(max, capacityType, "")
}
}
faultBlock := c.ctx.AddBasicBlock(frame.fn.LLVMFn, "slice.outofbounds")
nextBlock := c.ctx.AddBasicBlock(frame.fn.LLVMFn, "slice.next")
frame.blockExits[frame.currentBlock] = nextBlock // adjust outgoing block for phi nodes
// Now do the bounds check: low > high || high > capacity
outOfBounds1 := b.CreateICmp(llvm.IntUGT, low, high, "slice.lowhigh")
outOfBounds2 := b.CreateICmp(llvm.IntUGT, high, max, "slice.highmax")
outOfBounds3 := b.CreateICmp(llvm.IntUGT, max, capacity, "slice.maxcap")
outOfBounds := b.CreateOr(outOfBounds1, outOfBounds2, "slice.lowmax")
outOfBounds = b.CreateOr(outOfBounds, outOfBounds3, "slice.lowcap")
b.createRuntimeAssert(outOfBounds, "slice", "slicePanic")
outOfBounds1 := c.builder.CreateICmp(llvm.IntUGT, low, high, "slice.lowhigh")
outOfBounds2 := c.builder.CreateICmp(llvm.IntUGT, high, max, "slice.highmax")
outOfBounds3 := c.builder.CreateICmp(llvm.IntUGT, max, capacity, "slice.maxcap")
outOfBounds := c.builder.CreateOr(outOfBounds1, outOfBounds2, "slice.lowmax")
outOfBounds = c.builder.CreateOr(outOfBounds, outOfBounds3, "slice.lowcap")
c.builder.CreateCondBr(outOfBounds, faultBlock, nextBlock)
// Fail: this is a nil pointer, exit with a panic.
c.builder.SetInsertPointAtEnd(faultBlock)
c.createRuntimeCall("slicePanic", nil, "")
c.builder.CreateUnreachable()
// Ok: this is a valid pointer.
c.builder.SetInsertPointAtEnd(nextBlock)
}
// createChanBoundsCheck creates a bounds check before creating a new channel to
// check that the value is not too big for runtime.chanMake.
func (b *builder) createChanBoundsCheck(elementSize uint64, bufSize llvm.Value, bufSizeType *types.Basic, pos token.Pos) {
if b.fn.IsNoBounds() {
// The //go:nobounds pragma was added to the function to avoid bounds
// checking.
return
}
// Check whether the bufSize parameter must be cast to a wider integer for
// comparison.
if bufSize.Type().IntTypeWidth() < b.uintptrType.IntTypeWidth() {
if bufSizeType.Info()&types.IsUnsigned != 0 {
// Unsigned, so zero-extend to uint type.
bufSizeType = types.Typ[types.Uint]
bufSize = b.CreateZExt(bufSize, b.intType, "")
} else {
// Signed, so sign-extend to int type.
bufSizeType = types.Typ[types.Int]
bufSize = b.CreateSExt(bufSize, b.intType, "")
}
}
// Calculate (^uintptr(0)) >> 1, which is the max value that fits in an
// uintptr if uintptrs were signed.
maxBufSize := llvm.ConstLShr(llvm.ConstNot(llvm.ConstInt(b.uintptrType, 0, false)), llvm.ConstInt(b.uintptrType, 1, false))
if elementSize > maxBufSize.ZExtValue() {
b.addError(pos, fmt.Sprintf("channel element type is too big (%v bytes)", elementSize))
return
}
// Avoid divide-by-zero.
if elementSize == 0 {
elementSize = 1
}
// Make the maxBufSize actually the maximum allowed value (in number of
// elements in the channel buffer).
maxBufSize = llvm.ConstUDiv(maxBufSize, llvm.ConstInt(b.uintptrType, elementSize, false))
// Make sure maxBufSize has the same type as bufSize.
if maxBufSize.Type() != bufSize.Type() {
maxBufSize = llvm.ConstZExt(maxBufSize, bufSize.Type())
}
// Do the check for a too large (or negative) buffer size.
bufSizeTooBig := b.CreateICmp(llvm.IntUGE, bufSize, maxBufSize, "")
b.createRuntimeAssert(bufSizeTooBig, "chan", "chanMakePanic")
}
// createNilCheck checks whether the given pointer is nil, and panics if it is.
// It has no effect in well-behaved programs, but makes sure no uncaught nil
// emitNilCheck checks whether the given pointer is nil, and panics if it is. It
// has no effect in well-behaved programs, but makes sure no uncaught nil
// pointer dereferences exist in valid Go code.
func (b *builder) createNilCheck(inst ssa.Value, ptr llvm.Value, blockPrefix string) {
func (c *Compiler) emitNilCheck(frame *Frame, ptr llvm.Value, blockPrefix string) {
// Check whether we need to emit this check at all.
if !ptr.IsAGlobalValue().IsNil() {
return
}
switch inst := inst.(type) {
case *ssa.IndexAddr:
// This pointer is the result of an index operation into a slice or
// array. Such slices/arrays are already bounds checked so the pointer
// must be a valid (non-nil) pointer. No nil checking is necessary.
return
case *ssa.Convert:
// This is a pointer that comes from a conversion from unsafe.Pointer.
// Don't do nil checking because this is unsafe code and the code should
// know what it is doing.
// Note: all *ssa.Convert instructions that result in a pointer must
// come from unsafe.Pointer. Testing here for unsafe.Pointer to be sure.
if inst.X.Type() == types.Typ[types.UnsafePointer] {
return
}
}
// Check whether this is a nil pointer.
faultBlock := c.ctx.AddBasicBlock(frame.fn.LLVMFn, blockPrefix+".nil")
nextBlock := c.ctx.AddBasicBlock(frame.fn.LLVMFn, blockPrefix+".next")
frame.blockExits[frame.currentBlock] = nextBlock // adjust outgoing block for phi nodes
// Compare against nil.
// We previously used a hack to make sure this wouldn't break escape
// analysis, but this is not necessary anymore since
// https://reviews.llvm.org/D60047 has been merged.
nilptr := llvm.ConstPointerNull(ptr.Type())
isnil := b.CreateICmp(llvm.IntEQ, ptr, nilptr, "")
// Emit the nil check in IR.
b.createRuntimeAssert(isnil, blockPrefix, "nilPanic")
}
// createNegativeShiftCheck creates an assertion that panics if the given shift value is negative.
// This function assumes that the shift value is signed.
func (b *builder) createNegativeShiftCheck(shift llvm.Value) {
if b.fn.IsNoBounds() {
// Function disabled bounds checking - skip shift check.
return
var isnil llvm.Value
if ptr.Type().PointerAddressSpace() == 0 {
// Do the nil check using the isnil builtin, which marks the parameter
// as nocapture.
// The reason it has to go through a builtin, is that a regular icmp
// instruction may capture the pointer in LLVM semantics, see
// https://reviews.llvm.org/D60047 for details. Pointer capturing
// unfortunately breaks escape analysis, so we use this trick to let the
// functionattr pass know that this pointer doesn't really escape.
ptr = c.builder.CreateBitCast(ptr, c.i8ptrType, "")
isnil = c.createRuntimeCall("isnil", []llvm.Value{ptr}, "")
} else {
// Do the nil check using a regular icmp. This can happen with function
// pointers on AVR, which don't benefit from escape analysis anyway.
nilptr := llvm.ConstPointerNull(ptr.Type())
isnil = c.builder.CreateICmp(llvm.IntEQ, ptr, nilptr, "")
}
c.builder.CreateCondBr(isnil, faultBlock, nextBlock)
// isNegative = shift < 0
isNegative := b.CreateICmp(llvm.IntSLT, shift, llvm.ConstInt(shift.Type(), 0, false), "")
b.createRuntimeAssert(isNegative, "shift", "negativeShiftPanic")
}
// 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).
func (b *builder) createRuntimeAssert(assert llvm.Value, blockPrefix, assertFunc string) {
// Check whether we can resolve this check at compile time.
if !assert.IsAConstantInt().IsNil() {
val := assert.ZExtValue()
if val == 0 {
// Everything is constant so the check does not have to be emitted
// in IR. This avoids emitting some redundant IR.
return
}
}
faultBlock := b.ctx.AddBasicBlock(b.fn.LLVMFn, blockPrefix+".throw")
nextBlock := b.ctx.AddBasicBlock(b.fn.LLVMFn, blockPrefix+".next")
b.blockExits[b.currentBlock] = nextBlock // adjust outgoing block for phi nodes
// Now branch to the out-of-bounds or the regular block.
b.CreateCondBr(assert, faultBlock, nextBlock)
// Fail: the assert triggered so panic.
b.SetInsertPointAtEnd(faultBlock)
b.createRuntimeCall(assertFunc, nil, "")
b.CreateUnreachable()
// Ok: assert didn't trigger so continue normally.
b.SetInsertPointAtEnd(nextBlock)
// Fail: this is a nil pointer, exit with a panic.
c.builder.SetInsertPointAtEnd(faultBlock)
c.createRuntimeCall("nilPanic", nil, "")
c.builder.CreateUnreachable()
// Ok: this is a valid pointer.
c.builder.SetInsertPointAtEnd(nextBlock)
}
+63 -167
View File
@@ -1,9 +1,7 @@
package compiler
import (
"go/types"
"strconv"
"golang.org/x/tools/go/ssa"
"tinygo.org/x/go-llvm"
)
@@ -12,80 +10,59 @@ import (
// The maximum number of arguments that can be expanded from a single struct. If
// a struct contains more fields, it is passed as a struct without expanding.
const maxFieldsPerParam = 3
const MaxFieldsPerParam = 3
// paramInfo contains some information collected about a function parameter,
// useful while declaring or defining a function.
type paramInfo struct {
llvmType llvm.Type
name string // name, possibly with suffixes for e.g. struct fields
flags paramFlags
}
// paramFlags identifies parameter attributes for flags. Most importantly, it
// determines which parameters are dereferenceable_or_null and which aren't.
type paramFlags uint8
const (
// Parameter may have the deferenceable_or_null attribute. This attribute
// cannot be applied to unsafe.Pointer and to the data pointer of slices.
paramIsDeferenceableOrNull = 1 << iota
)
// createCall creates a new call to runtime.<fnName> with the given arguments.
func (b *builder) createRuntimeCall(fnName string, args []llvm.Value, name string) llvm.Value {
fullName := "runtime." + fnName
fn := b.mod.NamedFunction(fullName)
if fn.IsNil() {
panic("trying to call non-existent function: " + fullName)
// Shortcut: create a call to runtime.<fnName> with the given arguments.
func (c *Compiler) createRuntimeCall(fnName string, args []llvm.Value, name string) llvm.Value {
runtimePkg := c.ir.Program.ImportedPackage("runtime")
member := runtimePkg.Members[fnName]
if member == nil {
panic("trying to call runtime." + fnName)
}
args = append(args, llvm.Undef(b.i8ptrType)) // unused context parameter
args = append(args, llvm.ConstPointerNull(b.i8ptrType)) // coroutine handle
return b.createCall(fn, args, name)
fn := c.ir.GetFunction(member.(*ssa.Function))
if !fn.IsExported() {
args = append(args, llvm.Undef(c.i8ptrType)) // unused context parameter
args = append(args, llvm.ConstPointerNull(c.i8ptrType)) // coroutine handle
}
return c.createCall(fn.LLVMFn, args, name)
}
// createCall creates a call to the given function with the arguments possibly
// expanded.
func (b *builder) createCall(fn llvm.Value, args []llvm.Value, name string) llvm.Value {
// Create a call to the given function with the arguments possibly expanded.
func (c *Compiler) createCall(fn llvm.Value, args []llvm.Value, name string) llvm.Value {
expanded := make([]llvm.Value, 0, len(args))
for _, arg := range args {
fragments := b.expandFormalParam(arg)
fragments := c.expandFormalParam(arg)
expanded = append(expanded, fragments...)
}
return b.CreateCall(fn, expanded, name)
return c.builder.CreateCall(fn, expanded, name)
}
// Expand an argument type to a list that can be used in a function call
// parameter list.
func expandFormalParamType(t llvm.Type, name string, goType types.Type) []paramInfo {
func (c *Compiler) expandFormalParamType(t llvm.Type) []llvm.Type {
switch t.TypeKind() {
case llvm.StructTypeKind:
fieldInfos := flattenAggregateType(t, name, goType)
if len(fieldInfos) <= maxFieldsPerParam {
return fieldInfos
fields := c.flattenAggregateType(t)
if len(fields) <= MaxFieldsPerParam {
return fields
} else {
// failed to lower
return []llvm.Type{t}
}
}
// TODO: split small arrays
return []paramInfo{
{
llvmType: t,
name: name,
flags: getTypeFlags(goType),
},
default:
// TODO: split small arrays
return []llvm.Type{t}
}
}
// expandFormalParamOffsets returns a list of offsets from the start of an
// object of type t after it would have been split up by expandFormalParam. This
// is useful for debug information, where it is necessary to know the offset
// from the start of the combined object.
func (b *builder) expandFormalParamOffsets(t llvm.Type) []uint64 {
// Expand an argument type to a list of offsets from the start of the object.
// Used together with expandFormalParam to get the offset of each value from the
// start of the non-expanded value.
func (c *Compiler) expandFormalParamOffsets(t llvm.Type) []uint64 {
switch t.TypeKind() {
case llvm.StructTypeKind:
fields := b.flattenAggregateTypeOffsets(t)
if len(fields) <= maxFieldsPerParam {
fields := c.flattenAggregateTypeOffsets(t)
if len(fields) <= MaxFieldsPerParam {
return fields
} else {
// failed to lower
@@ -97,17 +74,14 @@ func (b *builder) expandFormalParamOffsets(t llvm.Type) []uint64 {
}
}
// expandFormalParam splits a formal param value into pieces, so it can be
// passed directly as part of a function call. For example, it splits up small
// structs into individual fields. It is the equivalent of expandFormalParamType
// for parameter values.
func (b *builder) expandFormalParam(v llvm.Value) []llvm.Value {
// Equivalent of expandFormalParamType for parameter values.
func (c *Compiler) expandFormalParam(v llvm.Value) []llvm.Value {
switch v.Type().TypeKind() {
case llvm.StructTypeKind:
fieldInfos := flattenAggregateType(v.Type(), "", nil)
if len(fieldInfos) <= maxFieldsPerParam {
fields := b.flattenAggregate(v)
if len(fields) != len(fieldInfos) {
fieldTypes := c.flattenAggregateType(v.Type())
if len(fieldTypes) <= MaxFieldsPerParam {
fields := c.flattenAggregate(v)
if len(fields) != len(fieldTypes) {
panic("type and value param lowering don't match")
}
return fields
@@ -123,98 +97,24 @@ func (b *builder) expandFormalParam(v llvm.Value) []llvm.Value {
// Try to flatten a struct type to a list of types. Returns a 1-element slice
// with the passed in type if this is not possible.
func flattenAggregateType(t llvm.Type, name string, goType types.Type) []paramInfo {
typeFlags := getTypeFlags(goType)
func (c *Compiler) flattenAggregateType(t llvm.Type) []llvm.Type {
switch t.TypeKind() {
case llvm.StructTypeKind:
paramInfos := make([]paramInfo, 0, t.StructElementTypesCount())
for i, subfield := range t.StructElementTypes() {
suffix := strconv.Itoa(i)
if goType != nil {
// Try to come up with a good suffix for this struct field,
// depending on which Go type it's based on.
switch goType := goType.Underlying().(type) {
case *types.Interface:
suffix = []string{"typecode", "value"}[i]
case *types.Slice:
suffix = []string{"data", "len", "cap"}[i]
case *types.Struct:
suffix = goType.Field(i).Name()
case *types.Basic:
switch goType.Kind() {
case types.Complex64, types.Complex128:
suffix = []string{"r", "i"}[i]
case types.String:
suffix = []string{"data", "len"}[i]
}
case *types.Signature:
suffix = []string{"context", "funcptr"}[i]
}
}
subInfos := flattenAggregateType(subfield, name+"."+suffix, extractSubfield(goType, i))
for i := range subInfos {
subInfos[i].flags |= typeFlags
}
paramInfos = append(paramInfos, subInfos...)
fields := make([]llvm.Type, 0, t.StructElementTypesCount())
for _, subfield := range t.StructElementTypes() {
subfields := c.flattenAggregateType(subfield)
fields = append(fields, subfields...)
}
return paramInfos
return fields
default:
return []paramInfo{
{
llvmType: t,
name: name,
flags: typeFlags,
},
}
return []llvm.Type{t}
}
}
// getTypeFlags returns the type flags for a given type. It will not recurse
// into sub-types (such as in structs).
func getTypeFlags(t types.Type) paramFlags {
if t == nil {
return 0
}
switch t.Underlying().(type) {
case *types.Pointer:
// Pointers in Go must either point to an object or be nil.
return paramIsDeferenceableOrNull
case *types.Chan, *types.Map:
// Channels and maps are implemented as pointers pointing to some
// object, and follow the same rules as *types.Pointer.
return paramIsDeferenceableOrNull
default:
return 0
}
}
// extractSubfield extracts a field from a struct, or returns null if this is
// not a struct and thus no subfield can be obtained.
func extractSubfield(t types.Type, field int) types.Type {
if t == nil {
return nil
}
switch t := t.Underlying().(type) {
case *types.Struct:
return t.Field(field).Type()
case *types.Interface, *types.Slice, *types.Basic, *types.Signature:
// These Go types are (sometimes) implemented as LLVM structs but can't
// really be split further up in Go (with the possible exception of
// complex numbers).
return nil
default:
// This should be unreachable.
panic("cannot split subfield: " + t.String())
}
}
// flattenAggregateTypeOffset returns the offsets from the start of an object of
// type t if this object were flattened like in flattenAggregate. Used together
// with flattenAggregate to know the start indices of each value in the
// non-flattened object.
//
// Note: this is an implementation detail, use expandFormalParamOffsets instead.
func (c *compilerContext) flattenAggregateTypeOffsets(t llvm.Type) []uint64 {
// Return the offsets from the start of the object if this object type were
// flattened like in flattenAggregate. Used together with flattenAggregate to
// know the start indices of each value in the non-flattened object.
func (c *Compiler) flattenAggregateTypeOffsets(t llvm.Type) []uint64 {
switch t.TypeKind() {
case llvm.StructTypeKind:
fields := make([]uint64, 0, t.StructElementTypesCount())
@@ -232,15 +132,15 @@ func (c *compilerContext) flattenAggregateTypeOffsets(t llvm.Type) []uint64 {
}
}
// flattenAggregate breaks down a struct into its elementary values for argument
// passing. It is the value equivalent of flattenAggregateType
func (b *builder) flattenAggregate(v llvm.Value) []llvm.Value {
// Break down a struct into its elementary types for argument passing. The value
// equivalent of flattenAggregateType
func (c *Compiler) flattenAggregate(v llvm.Value) []llvm.Value {
switch v.Type().TypeKind() {
case llvm.StructTypeKind:
fields := make([]llvm.Value, 0, v.Type().StructElementTypesCount())
for i := range v.Type().StructElementTypes() {
subfield := b.CreateExtractValue(v, i, "")
subfields := b.flattenAggregate(subfield)
subfield := c.builder.CreateExtractValue(v, i, "")
subfields := c.flattenAggregate(subfield)
fields = append(fields, subfields...)
}
return fields
@@ -249,29 +149,25 @@ func (b *builder) flattenAggregate(v llvm.Value) []llvm.Value {
}
}
// collapseFormalParam combines an aggregate object back into the original
// value. This is used to join multiple LLVM parameters into a single Go value
// in the function entry block.
func (b *builder) collapseFormalParam(t llvm.Type, fields []llvm.Value) llvm.Value {
param, remaining := b.collapseFormalParamInternal(t, fields)
// Collapse a list of fields into its original value.
func (c *Compiler) collapseFormalParam(t llvm.Type, fields []llvm.Value) llvm.Value {
param, remaining := c.collapseFormalParamInternal(t, fields)
if len(remaining) != 0 {
panic("failed to expand back all fields")
}
return param
}
// collapseFormalParamInternal is an implementation detail of
// collapseFormalParam: it works by recursing until there are no fields left.
func (b *builder) collapseFormalParamInternal(t llvm.Type, fields []llvm.Value) (llvm.Value, []llvm.Value) {
// Returns (value, remainingFields). Used by collapseFormalParam.
func (c *Compiler) collapseFormalParamInternal(t llvm.Type, fields []llvm.Value) (llvm.Value, []llvm.Value) {
switch t.TypeKind() {
case llvm.StructTypeKind:
flattened := flattenAggregateType(t, "", nil)
if len(flattened) <= maxFieldsPerParam {
value := llvm.ConstNull(t)
if len(c.flattenAggregateType(t)) <= MaxFieldsPerParam {
value := c.getZeroValue(t)
for i, subtyp := range t.StructElementTypes() {
structField, remaining := b.collapseFormalParamInternal(subtyp, fields)
structField, remaining := c.collapseFormalParamInternal(subtyp, fields)
fields = remaining
value = b.CreateInsertValue(value, structField, i, "")
value = c.builder.CreateInsertValue(value, structField, i, "")
}
return value, fields
} else {
+106 -111
View File
@@ -4,86 +4,98 @@ package compiler
// or pseudo-operations that are lowered during goroutine lowering.
import (
"fmt"
"go/types"
"github.com/tinygo-org/tinygo/compiler/llvmutil"
"golang.org/x/tools/go/ssa"
"tinygo.org/x/go-llvm"
)
func (b *builder) createMakeChan(expr *ssa.MakeChan) llvm.Value {
elementSize := b.targetData.TypeAllocSize(b.getLLVMType(expr.Type().(*types.Chan).Elem()))
elementSizeValue := llvm.ConstInt(b.uintptrType, elementSize, false)
bufSize := b.getValue(expr.Size)
b.createChanBoundsCheck(elementSize, bufSize, expr.Size.Type().Underlying().(*types.Basic), expr.Pos())
if bufSize.Type().IntTypeWidth() < b.uintptrType.IntTypeWidth() {
bufSize = b.CreateZExt(bufSize, b.uintptrType, "")
} else if bufSize.Type().IntTypeWidth() > b.uintptrType.IntTypeWidth() {
bufSize = b.CreateTrunc(bufSize, b.uintptrType, "")
// emitMakeChan returns a new channel value for the given channel type.
func (c *Compiler) emitMakeChan(expr *ssa.MakeChan) (llvm.Value, error) {
chanType := c.getLLVMType(expr.Type())
size := c.targetData.TypeAllocSize(chanType.ElementType())
sizeValue := llvm.ConstInt(c.uintptrType, size, false)
ptr := c.createRuntimeCall("alloc", []llvm.Value{sizeValue}, "chan.alloc")
ptr = c.builder.CreateBitCast(ptr, chanType, "chan")
// Set the elementSize field
elementSizePtr := c.builder.CreateGEP(ptr, []llvm.Value{
llvm.ConstInt(c.ctx.Int32Type(), 0, false),
llvm.ConstInt(c.ctx.Int32Type(), 0, false),
}, "")
elementSize := c.targetData.TypeAllocSize(c.getLLVMType(expr.Type().(*types.Chan).Elem()))
if elementSize > 0xffff {
return ptr, c.makeError(expr.Pos(), fmt.Sprintf("element size is %d bytes, which is bigger than the maximum of %d bytes", elementSize, 0xffff))
}
return b.createRuntimeCall("chanMake", []llvm.Value{elementSizeValue, bufSize}, "")
elementSizeValue := llvm.ConstInt(c.ctx.Int16Type(), elementSize, false)
c.builder.CreateStore(elementSizeValue, elementSizePtr)
return ptr, nil
}
// createChanSend emits a pseudo chan send operation. It is lowered to the
// actual channel send operation during goroutine lowering.
func (b *builder) createChanSend(instr *ssa.Send) {
ch := b.getValue(instr.Chan)
chanValue := b.getValue(instr.X)
// emitChanSend emits a pseudo chan send operation. It is lowered to the actual
// channel send operation during goroutine lowering.
func (c *Compiler) emitChanSend(frame *Frame, instr *ssa.Send) {
ch := c.getValue(frame, instr.Chan)
chanValue := c.getValue(frame, instr.X)
// store value-to-send
valueType := b.getLLVMType(instr.X.Type())
valueAlloca, valueAllocaCast, valueAllocaSize := b.createTemporaryAlloca(valueType, "chan.value")
b.CreateStore(chanValue, valueAlloca)
valueType := c.getLLVMType(instr.X.Type())
valueAlloca, valueAllocaCast, valueAllocaSize := c.createTemporaryAlloca(valueType, "chan.value")
c.builder.CreateStore(chanValue, valueAlloca)
// Do the send.
b.createRuntimeCall("chanSend", []llvm.Value{ch, valueAllocaCast}, "")
coroutine := c.createRuntimeCall("getCoroutine", nil, "")
c.createRuntimeCall("chanSend", []llvm.Value{coroutine, ch, valueAllocaCast}, "")
// End the lifetime of the alloca.
// This also works around a bug in CoroSplit, at least in LLVM 8:
// https://bugs.llvm.org/show_bug.cgi?id=41742
b.emitLifetimeEnd(valueAllocaCast, valueAllocaSize)
c.emitLifetimeEnd(valueAllocaCast, valueAllocaSize)
}
// createChanRecv emits a pseudo chan receive operation. It is lowered to the
// emitChanRecv emits a pseudo chan receive operation. It is lowered to the
// actual channel receive operation during goroutine lowering.
func (b *builder) createChanRecv(unop *ssa.UnOp) llvm.Value {
valueType := b.getLLVMType(unop.X.Type().(*types.Chan).Elem())
ch := b.getValue(unop.X)
func (c *Compiler) emitChanRecv(frame *Frame, unop *ssa.UnOp) llvm.Value {
valueType := c.getLLVMType(unop.X.Type().(*types.Chan).Elem())
ch := c.getValue(frame, unop.X)
// Allocate memory to receive into.
valueAlloca, valueAllocaCast, valueAllocaSize := b.createTemporaryAlloca(valueType, "chan.value")
valueAlloca, valueAllocaCast, valueAllocaSize := c.createTemporaryAlloca(valueType, "chan.value")
// Do the receive.
commaOk := b.createRuntimeCall("chanRecv", []llvm.Value{ch, valueAllocaCast}, "")
received := b.CreateLoad(valueAlloca, "chan.received")
b.emitLifetimeEnd(valueAllocaCast, valueAllocaSize)
coroutine := c.createRuntimeCall("getCoroutine", nil, "")
c.createRuntimeCall("chanRecv", []llvm.Value{coroutine, ch, valueAllocaCast}, "")
received := c.builder.CreateLoad(valueAlloca, "chan.received")
c.emitLifetimeEnd(valueAllocaCast, valueAllocaSize)
if unop.CommaOk {
tuple := llvm.Undef(b.ctx.StructType([]llvm.Type{valueType, b.ctx.Int1Type()}, false))
tuple = b.CreateInsertValue(tuple, received, 0, "")
tuple = b.CreateInsertValue(tuple, commaOk, 1, "")
commaOk := c.createRuntimeCall("getTaskStateData", []llvm.Value{coroutine}, "chan.commaOk.wide")
commaOk = c.builder.CreateTrunc(commaOk, c.ctx.Int1Type(), "chan.commaOk")
tuple := llvm.Undef(c.ctx.StructType([]llvm.Type{valueType, c.ctx.Int1Type()}, false))
tuple = c.builder.CreateInsertValue(tuple, received, 0, "")
tuple = c.builder.CreateInsertValue(tuple, commaOk, 1, "")
return tuple
} else {
return received
}
}
// createChanClose closes the given channel.
func (b *builder) createChanClose(param ssa.Value) {
ch := b.getValue(param)
b.createRuntimeCall("chanClose", []llvm.Value{ch}, "")
// emitChanClose closes the given channel.
func (c *Compiler) emitChanClose(frame *Frame, param ssa.Value) {
ch := c.getValue(frame, param)
c.createRuntimeCall("chanClose", []llvm.Value{ch}, "")
}
// createSelect emits all IR necessary for a select statements. That's a
// emitSelect emits all IR necessary for a select statements. That's a
// non-trivial amount of code because select is very complex to implement.
func (b *builder) createSelect(expr *ssa.Select) llvm.Value {
func (c *Compiler) emitSelect(frame *Frame, expr *ssa.Select) llvm.Value {
if len(expr.States) == 0 {
// Shortcuts for some simple selects.
llvmType := b.getLLVMType(expr.Type())
llvmType := c.getLLVMType(expr.Type())
if expr.Blocking {
// Blocks forever:
// select {}
b.createRuntimeCall("deadlock", nil, "")
c.createRuntimeCall("deadlock", nil, "")
return llvm.Undef(llvmType)
} else {
// No-op:
@@ -91,7 +103,7 @@ func (b *builder) createSelect(expr *ssa.Select) llvm.Value {
// default:
// }
retval := llvm.Undef(llvmType)
retval = b.CreateInsertValue(retval, llvm.ConstInt(b.intType, 0xffffffffffffffff, true), 0, "")
retval = c.builder.CreateInsertValue(retval, llvm.ConstInt(c.intType, 0xffffffffffffffff, true), 0, "")
return retval // {-1, false}
}
}
@@ -109,30 +121,30 @@ func (b *builder) createSelect(expr *ssa.Select) llvm.Value {
recvbufAlign := 0
hasReceives := false
var selectStates []llvm.Value
chanSelectStateType := b.getLLVMRuntimeType("chanSelectState")
chanSelectStateType := c.getLLVMRuntimeType("chanSelectState")
for _, state := range expr.States {
ch := b.getValue(state.Chan)
selectState := llvm.ConstNull(chanSelectStateType)
selectState = b.CreateInsertValue(selectState, ch, 0, "")
ch := c.getValue(frame, state.Chan)
selectState := c.getZeroValue(chanSelectStateType)
selectState = c.builder.CreateInsertValue(selectState, ch, 0, "")
switch state.Dir {
case types.RecvOnly:
// Make sure the receive buffer is big enough and has the correct alignment.
llvmType := b.getLLVMType(state.Chan.Type().(*types.Chan).Elem())
if size := b.targetData.TypeAllocSize(llvmType); size > recvbufSize {
llvmType := c.getLLVMType(state.Chan.Type().(*types.Chan).Elem())
if size := c.targetData.TypeAllocSize(llvmType); size > recvbufSize {
recvbufSize = size
}
if align := b.targetData.ABITypeAlignment(llvmType); align > recvbufAlign {
if align := c.targetData.ABITypeAlignment(llvmType); align > recvbufAlign {
recvbufAlign = align
}
hasReceives = true
case types.SendOnly:
// Store this value in an alloca and put a pointer to this alloca
// in the send state.
sendValue := b.getValue(state.Send)
alloca := llvmutil.CreateEntryBlockAlloca(b.Builder, sendValue.Type(), "select.send.value")
b.CreateStore(sendValue, alloca)
ptr := b.CreateBitCast(alloca, b.i8ptrType, "")
selectState = b.CreateInsertValue(selectState, ptr, 1, "")
sendValue := c.getValue(frame, state.Send)
alloca := c.createEntryBlockAlloca(sendValue.Type(), "select.send.value")
c.builder.CreateStore(sendValue, alloca)
ptr := c.builder.CreateBitCast(alloca, c.i8ptrType, "")
selectState = c.builder.CreateInsertValue(selectState, ptr, 1, "")
default:
panic("unreachable")
}
@@ -140,74 +152,57 @@ func (b *builder) createSelect(expr *ssa.Select) llvm.Value {
}
// Create a receive buffer, where the received value will be stored.
recvbuf := llvm.Undef(b.i8ptrType)
recvbuf := llvm.Undef(c.i8ptrType)
if hasReceives {
allocaType := llvm.ArrayType(b.ctx.Int8Type(), int(recvbufSize))
recvbufAlloca, _, _ := b.createTemporaryAlloca(allocaType, "select.recvbuf.alloca")
allocaType := llvm.ArrayType(c.ctx.Int8Type(), int(recvbufSize))
recvbufAlloca := c.builder.CreateAlloca(allocaType, "select.recvbuf.alloca")
recvbufAlloca.SetAlignment(recvbufAlign)
recvbuf = b.CreateGEP(recvbufAlloca, []llvm.Value{
llvm.ConstInt(b.ctx.Int32Type(), 0, false),
llvm.ConstInt(b.ctx.Int32Type(), 0, false),
recvbuf = c.builder.CreateGEP(recvbufAlloca, []llvm.Value{
llvm.ConstInt(c.ctx.Int32Type(), 0, false),
llvm.ConstInt(c.ctx.Int32Type(), 0, false),
}, "select.recvbuf")
}
// Create the states slice (allocated on the stack).
statesAllocaType := llvm.ArrayType(chanSelectStateType, len(selectStates))
statesAlloca, statesI8, statesSize := b.createTemporaryAlloca(statesAllocaType, "select.states.alloca")
statesAlloca := c.builder.CreateAlloca(statesAllocaType, "select.states.alloca")
for i, state := range selectStates {
// Set each slice element to the appropriate channel.
gep := b.CreateGEP(statesAlloca, []llvm.Value{
llvm.ConstInt(b.ctx.Int32Type(), 0, false),
llvm.ConstInt(b.ctx.Int32Type(), uint64(i), false),
gep := c.builder.CreateGEP(statesAlloca, []llvm.Value{
llvm.ConstInt(c.ctx.Int32Type(), 0, false),
llvm.ConstInt(c.ctx.Int32Type(), uint64(i), false),
}, "")
b.CreateStore(state, gep)
c.builder.CreateStore(state, gep)
}
statesPtr := b.CreateGEP(statesAlloca, []llvm.Value{
llvm.ConstInt(b.ctx.Int32Type(), 0, false),
llvm.ConstInt(b.ctx.Int32Type(), 0, false),
statesPtr := c.builder.CreateGEP(statesAlloca, []llvm.Value{
llvm.ConstInt(c.ctx.Int32Type(), 0, false),
llvm.ConstInt(c.ctx.Int32Type(), 0, false),
}, "select.states")
statesLen := llvm.ConstInt(b.uintptrType, uint64(len(selectStates)), false)
statesLen := llvm.ConstInt(c.uintptrType, uint64(len(selectStates)), false)
// Convert the 'blocking' flag on this select into a LLVM value.
blockingInt := uint64(0)
if expr.Blocking {
blockingInt = 1
}
blockingValue := llvm.ConstInt(c.ctx.Int1Type(), blockingInt, false)
// Do the select in the runtime.
var results llvm.Value
if expr.Blocking {
// Stack-allocate operation structures.
// If these were simply created as a slice, they would heap-allocate.
chBlockAllocaType := llvm.ArrayType(b.getLLVMRuntimeType("channelBlockedList"), len(selectStates))
chBlockAlloca, chBlockAllocaPtr, chBlockSize := b.createTemporaryAlloca(chBlockAllocaType, "select.block.alloca")
chBlockLen := llvm.ConstInt(b.uintptrType, uint64(len(selectStates)), false)
chBlockPtr := b.CreateGEP(chBlockAlloca, []llvm.Value{
llvm.ConstInt(b.ctx.Int32Type(), 0, false),
llvm.ConstInt(b.ctx.Int32Type(), 0, false),
}, "select.block")
results = b.createRuntimeCall("chanSelect", []llvm.Value{
recvbuf,
statesPtr, statesLen, statesLen, // []chanSelectState
chBlockPtr, chBlockLen, chBlockLen, // []channelBlockList
}, "select.result")
// Terminate the lifetime of the operation structures.
b.emitLifetimeEnd(chBlockAllocaPtr, chBlockSize)
} else {
results = b.createRuntimeCall("tryChanSelect", []llvm.Value{
recvbuf,
statesPtr, statesLen, statesLen, // []chanSelectState
}, "select.result")
}
// Terminate the lifetime of the states alloca.
b.emitLifetimeEnd(statesI8, statesSize)
results := c.createRuntimeCall("chanSelect", []llvm.Value{
recvbuf,
statesPtr, statesLen, statesLen, // []chanSelectState
blockingValue,
}, "")
// The result value does not include all the possible received values,
// because we can't load them in advance. Instead, the *ssa.Extract
// instruction will treat a *ssa.Select specially and load it there inline.
// Store the receive alloca in a sidetable until we hit this extract
// instruction.
if b.selectRecvBuf == nil {
b.selectRecvBuf = make(map[*ssa.Select]llvm.Value)
if frame.selectRecvBuf == nil {
frame.selectRecvBuf = make(map[*ssa.Select]llvm.Value)
}
b.selectRecvBuf[expr] = recvbuf
frame.selectRecvBuf[expr] = recvbuf
return results
}
@@ -216,28 +211,28 @@ func (b *builder) createSelect(expr *ssa.Select) llvm.Value {
// when extracting a value from a select statement (*ssa.Select). Because
// *ssa.Select cannot load all values in advance, it does this later in the
// *ssa.Extract expression.
func (b *builder) getChanSelectResult(expr *ssa.Extract) llvm.Value {
func (c *Compiler) getChanSelectResult(frame *Frame, expr *ssa.Extract) llvm.Value {
if expr.Index == 0 {
// index
value := b.getValue(expr.Tuple)
index := b.CreateExtractValue(value, expr.Index, "")
if index.Type().IntTypeWidth() < b.intType.IntTypeWidth() {
index = b.CreateSExt(index, b.intType, "")
value := c.getValue(frame, expr.Tuple)
index := c.builder.CreateExtractValue(value, expr.Index, "")
if index.Type().IntTypeWidth() < c.intType.IntTypeWidth() {
index = c.builder.CreateSExt(index, c.intType, "")
}
return index
} else if expr.Index == 1 {
// comma-ok
value := b.getValue(expr.Tuple)
return b.CreateExtractValue(value, expr.Index, "")
value := c.getValue(frame, expr.Tuple)
return c.builder.CreateExtractValue(value, expr.Index, "")
} else {
// Select statements are (index, ok, ...) where ... is a number of
// received values, depending on how many receive statements there
// are. They are all combined into one alloca (because only one
// receive can proceed at a time) so we'll get that alloca, bitcast
// it to the correct type, and dereference it.
recvbuf := b.selectRecvBuf[expr.Tuple.(*ssa.Select)]
typ := llvm.PointerType(b.getLLVMType(expr.Type()), 0)
ptr := b.CreateBitCast(recvbuf, typ, "")
return b.CreateLoad(ptr, "")
recvbuf := frame.selectRecvBuf[expr.Tuple.(*ssa.Select)]
typ := llvm.PointerType(c.getLLVMType(expr.Type()), 0)
ptr := c.builder.CreateBitCast(recvbuf, typ, "")
return c.builder.CreateLoad(ptr, "")
}
}
+121
View File
@@ -0,0 +1,121 @@
package compiler
import (
"errors"
"fmt"
"tinygo.org/x/go-llvm"
)
func (c *Compiler) checkType(t llvm.Type, checked map[llvm.Type]struct{}, specials map[llvm.TypeKind]llvm.Type) {
if t.IsNil() {
panic(t)
}
// prevent infinite recursion for self-referential types
if _, ok := checked[t]; ok {
return
}
checked[t] = struct{}{}
// check for any context mismatches
switch {
case t.Context() == c.ctx:
// this is correct
case t.Context() == llvm.GlobalContext():
// somewhere we accidentally used the global context instead of a real context
panic(fmt.Errorf("type %q uses global context", t.String()))
default:
// we used some other context by accident
panic(fmt.Errorf("type %q uses context %v instead of the main context %v", t.Context(), c.ctx))
}
// if this is a composite type, check the components of the type
switch t.TypeKind() {
case llvm.VoidTypeKind, llvm.LabelTypeKind, llvm.TokenTypeKind, llvm.MetadataTypeKind:
// there should only be one of any of these
if s, ok := specials[t.TypeKind()]; !ok {
specials[t.TypeKind()] = t
} else if s != t {
panic(fmt.Errorf("duplicate special type %q: %v and %v", t.TypeKind().String(), t, s))
}
case llvm.FloatTypeKind, llvm.DoubleTypeKind, llvm.X86_FP80TypeKind, llvm.FP128TypeKind, llvm.PPC_FP128TypeKind:
// floating point numbers are primitives - nothing to recurse
case llvm.IntegerTypeKind:
// integers are primitives - nothing to recurse
case llvm.FunctionTypeKind:
// check arguments and return(s)
for _, v := range t.ParamTypes() {
c.checkType(v, checked, specials)
}
c.checkType(t.ReturnType(), checked, specials)
case llvm.StructTypeKind:
// check all elements
for _, v := range t.StructElementTypes() {
c.checkType(v, checked, specials)
}
case llvm.ArrayTypeKind:
// check element type
c.checkType(t.ElementType(), checked, specials)
case llvm.PointerTypeKind:
// check underlying type
c.checkType(t.ElementType(), checked, specials)
case llvm.VectorTypeKind:
// check element type
c.checkType(t.ElementType(), checked, specials)
}
}
func (c *Compiler) checkValue(v llvm.Value, checked map[llvm.Type]struct{}, specials map[llvm.TypeKind]llvm.Type) {
// check type
c.checkType(v.Type(), checked, specials)
}
func (c *Compiler) checkInstruction(inst llvm.Value, checked map[llvm.Type]struct{}, specials map[llvm.TypeKind]llvm.Type) {
// check value properties
c.checkValue(inst, checked, specials)
}
func (c *Compiler) checkBasicBlock(bb llvm.BasicBlock, checked map[llvm.Type]struct{}, specials map[llvm.TypeKind]llvm.Type) {
// check basic block value and type
c.checkValue(bb.AsValue(), checked, specials)
// check instructions
for inst := bb.FirstInstruction(); !inst.IsNil(); inst = llvm.NextInstruction(inst) {
c.checkInstruction(inst, checked, specials)
}
}
func (c *Compiler) checkFunction(fn llvm.Value, checked map[llvm.Type]struct{}, specials map[llvm.TypeKind]llvm.Type) {
// check function value and type
c.checkValue(fn, checked, specials)
// check basic blocks
for bb := fn.FirstBasicBlock(); !bb.IsNil(); bb = llvm.NextBasicBlock(bb) {
c.checkBasicBlock(bb, checked, specials)
}
}
func (c *Compiler) check() {
// check for any context mismatches
switch {
case c.mod.Context() == c.ctx:
// this is correct
case c.mod.Context() == llvm.GlobalContext():
// somewhere we accidentally used the global context instead of a real context
panic(errors.New("module uses global context"))
default:
// we used some other context by accident
panic(fmt.Errorf("module uses context %v instead of the main context %v", c.mod.Context(), c.ctx))
}
// base of type-check stack
checked := map[llvm.Type]struct{}{}
specials := map[llvm.TypeKind]llvm.Type{}
for fn := c.mod.FirstFunction(); !fn.IsNil(); fn = llvm.NextFunction(fn) {
c.checkFunction(fn, checked, specials)
}
for g := c.mod.FirstGlobal(); !g.IsNil(); g = llvm.NextGlobal(g) {
c.checkValue(g, checked, specials)
}
}
+1270 -1052
View File
File diff suppressed because it is too large Load Diff
+110 -163
View File
@@ -14,7 +14,6 @@ package compiler
// frames.
import (
"github.com/tinygo-org/tinygo/compiler/llvmutil"
"github.com/tinygo-org/tinygo/ir"
"golang.org/x/tools/go/ssa"
"tinygo.org/x/go-llvm"
@@ -23,100 +22,65 @@ import (
// deferInitFunc sets up this function for future deferred calls. It must be
// called from within the entry block when this function contains deferred
// calls.
func (b *builder) deferInitFunc() {
func (c *Compiler) deferInitFunc(frame *Frame) {
// Some setup.
b.deferFuncs = make(map[*ir.Function]int)
b.deferInvokeFuncs = make(map[string]int)
b.deferClosureFuncs = make(map[*ir.Function]int)
frame.deferFuncs = make(map[*ir.Function]int)
frame.deferInvokeFuncs = make(map[string]int)
frame.deferClosureFuncs = make(map[*ir.Function]int)
// Create defer list pointer.
deferType := llvm.PointerType(b.getLLVMRuntimeType("_defer"), 0)
b.deferPtr = b.CreateAlloca(deferType, "deferPtr")
b.CreateStore(llvm.ConstPointerNull(deferType), b.deferPtr)
deferType := llvm.PointerType(c.getLLVMRuntimeType("_defer"), 0)
frame.deferPtr = c.builder.CreateAlloca(deferType, "deferPtr")
c.builder.CreateStore(llvm.ConstPointerNull(deferType), frame.deferPtr)
}
// isInLoop checks if there is a path from a basic block to itself.
func isInLoop(start *ssa.BasicBlock) bool {
// Use a breadth-first search to scan backwards through the block graph.
queue := []*ssa.BasicBlock{start}
checked := map[*ssa.BasicBlock]struct{}{}
for len(queue) > 0 {
// pop a block off of the queue
block := queue[len(queue)-1]
queue = queue[:len(queue)-1]
// Search through predecessors.
// Searching backwards means that this is pretty fast when the block is close to the start of the function.
// Defers are often placed near the start of the function.
for _, pred := range block.Preds {
if pred == start {
// cycle found
return true
}
if _, ok := checked[pred]; ok {
// block already checked
continue
}
// add to queue and checked map
queue = append(queue, pred)
checked[pred] = struct{}{}
}
}
return false
}
// createDefer emits a single defer instruction, to be run when this function
// emitDefer emits a single defer instruction, to be run when this function
// returns.
func (b *builder) createDefer(instr *ssa.Defer) {
func (c *Compiler) emitDefer(frame *Frame, instr *ssa.Defer) {
// The pointer to the previous defer struct, which we will replace to
// make a linked list.
next := b.CreateLoad(b.deferPtr, "defer.next")
next := c.builder.CreateLoad(frame.deferPtr, "defer.next")
var values []llvm.Value
valueTypes := []llvm.Type{b.uintptrType, next.Type()}
valueTypes := []llvm.Type{c.uintptrType, next.Type()}
if instr.Call.IsInvoke() {
// Method call on an interface.
// Get callback type number.
methodName := instr.Call.Method.FullName()
if _, ok := b.deferInvokeFuncs[methodName]; !ok {
b.deferInvokeFuncs[methodName] = len(b.allDeferFuncs)
b.allDeferFuncs = append(b.allDeferFuncs, &instr.Call)
if _, ok := frame.deferInvokeFuncs[methodName]; !ok {
frame.deferInvokeFuncs[methodName] = len(frame.allDeferFuncs)
frame.allDeferFuncs = append(frame.allDeferFuncs, &instr.Call)
}
callback := llvm.ConstInt(b.uintptrType, uint64(b.deferInvokeFuncs[methodName]), false)
callback := llvm.ConstInt(c.uintptrType, uint64(frame.deferInvokeFuncs[methodName]), false)
// Collect all values to be put in the struct (starting with
// runtime._defer fields, followed by the call parameters).
itf := b.getValue(instr.Call.Value) // interface
typecode := b.CreateExtractValue(itf, 0, "invoke.func.typecode")
receiverValue := b.CreateExtractValue(itf, 1, "invoke.func.receiver")
values = []llvm.Value{callback, next, typecode, receiverValue}
valueTypes = append(valueTypes, b.uintptrType, b.i8ptrType)
itf := c.getValue(frame, instr.Call.Value) // interface
receiverValue := c.builder.CreateExtractValue(itf, 1, "invoke.func.receiver")
values = []llvm.Value{callback, next, receiverValue}
valueTypes = append(valueTypes, c.i8ptrType)
for _, arg := range instr.Call.Args {
val := b.getValue(arg)
val := c.getValue(frame, arg)
values = append(values, val)
valueTypes = append(valueTypes, val.Type())
}
} else if callee, ok := instr.Call.Value.(*ssa.Function); ok {
// Regular function call.
fn := b.ir.GetFunction(callee)
fn := c.ir.GetFunction(callee)
if _, ok := b.deferFuncs[fn]; !ok {
b.deferFuncs[fn] = len(b.allDeferFuncs)
b.allDeferFuncs = append(b.allDeferFuncs, fn)
if _, ok := frame.deferFuncs[fn]; !ok {
frame.deferFuncs[fn] = len(frame.allDeferFuncs)
frame.allDeferFuncs = append(frame.allDeferFuncs, fn)
}
callback := llvm.ConstInt(b.uintptrType, uint64(b.deferFuncs[fn]), false)
callback := llvm.ConstInt(c.uintptrType, uint64(frame.deferFuncs[fn]), false)
// Collect all values to be put in the struct (starting with
// runtime._defer fields).
values = []llvm.Value{callback, next}
for _, param := range instr.Call.Args {
llvmParam := b.getValue(param)
llvmParam := c.getValue(frame, param)
values = append(values, llvmParam)
valueTypes = append(valueTypes, llvmParam.Type())
}
@@ -128,23 +92,23 @@ func (b *builder) createDefer(instr *ssa.Defer) {
// pointer.
// TODO: ignore this closure entirely and put pointers to the free
// variables directly in the defer struct, avoiding a memory allocation.
closure := b.getValue(instr.Call.Value)
context := b.CreateExtractValue(closure, 0, "")
closure := c.getValue(frame, instr.Call.Value)
context := c.builder.CreateExtractValue(closure, 0, "")
// Get the callback number.
fn := b.ir.GetFunction(makeClosure.Fn.(*ssa.Function))
if _, ok := b.deferClosureFuncs[fn]; !ok {
b.deferClosureFuncs[fn] = len(b.allDeferFuncs)
b.allDeferFuncs = append(b.allDeferFuncs, makeClosure)
fn := c.ir.GetFunction(makeClosure.Fn.(*ssa.Function))
if _, ok := frame.deferClosureFuncs[fn]; !ok {
frame.deferClosureFuncs[fn] = len(frame.allDeferFuncs)
frame.allDeferFuncs = append(frame.allDeferFuncs, makeClosure)
}
callback := llvm.ConstInt(b.uintptrType, uint64(b.deferClosureFuncs[fn]), false)
callback := llvm.ConstInt(c.uintptrType, uint64(frame.deferClosureFuncs[fn]), false)
// Collect all values to be put in the struct (starting with
// runtime._defer fields, followed by all parameters including the
// context pointer).
values = []llvm.Value{callback, next}
for _, param := range instr.Call.Args {
llvmParam := b.getValue(param)
llvmParam := c.getValue(frame, param)
values = append(values, llvmParam)
valueTypes = append(valueTypes, llvmParam.Type())
}
@@ -152,41 +116,31 @@ func (b *builder) createDefer(instr *ssa.Defer) {
valueTypes = append(valueTypes, context.Type())
} else {
b.addError(instr.Pos(), "todo: defer on uncommon function call type")
c.addError(instr.Pos(), "todo: defer on uncommon function call type")
return
}
// Make a struct out of the collected values to put in the defer frame.
deferFrameType := b.ctx.StructType(valueTypes, false)
deferFrame := llvm.ConstNull(deferFrameType)
deferFrameType := c.ctx.StructType(valueTypes, false)
deferFrame := c.getZeroValue(deferFrameType)
for i, value := range values {
deferFrame = b.CreateInsertValue(deferFrame, value, i, "")
deferFrame = c.builder.CreateInsertValue(deferFrame, value, i, "")
}
// Put this struct in an allocation.
var alloca llvm.Value
if !isInLoop(instr.Block()) {
// This can safely use a stack allocation.
alloca = llvmutil.CreateEntryBlockAlloca(b.Builder, deferFrameType, "defer.alloca")
} else {
// This may be hit a variable number of times, so use a heap allocation.
size := b.targetData.TypeAllocSize(deferFrameType)
sizeValue := llvm.ConstInt(b.uintptrType, size, false)
allocCall := b.createRuntimeCall("alloc", []llvm.Value{sizeValue}, "defer.alloc.call")
alloca = b.CreateBitCast(allocCall, llvm.PointerType(deferFrameType, 0), "defer.alloc")
// Put this struct in an alloca.
alloca := c.builder.CreateAlloca(deferFrameType, "defer.alloca")
c.builder.CreateStore(deferFrame, alloca)
if c.needsStackObjects() {
c.trackPointer(alloca)
}
if b.NeedsStackObjects() {
b.trackPointer(alloca)
}
b.CreateStore(deferFrame, alloca)
// Push it on top of the linked list by replacing deferPtr.
allocaCast := b.CreateBitCast(alloca, next.Type(), "defer.alloca.cast")
b.CreateStore(allocaCast, b.deferPtr)
allocaCast := c.builder.CreateBitCast(alloca, next.Type(), "defer.alloca.cast")
c.builder.CreateStore(allocaCast, frame.deferPtr)
}
// createRunDefers emits code to run all deferred functions.
func (b *builder) createRunDefers() {
// emitRunDefers emits code to run all deferred functions.
func (c *Compiler) emitRunDefers(frame *Frame) {
// Add a loop like the following:
// for stack != nil {
// _stack := stack
@@ -203,44 +157,44 @@ func (b *builder) createRunDefers() {
// }
// Create loop.
loophead := b.ctx.AddBasicBlock(b.fn.LLVMFn, "rundefers.loophead")
loop := b.ctx.AddBasicBlock(b.fn.LLVMFn, "rundefers.loop")
unreachable := b.ctx.AddBasicBlock(b.fn.LLVMFn, "rundefers.default")
end := b.ctx.AddBasicBlock(b.fn.LLVMFn, "rundefers.end")
b.CreateBr(loophead)
loophead := c.ctx.AddBasicBlock(frame.fn.LLVMFn, "rundefers.loophead")
loop := c.ctx.AddBasicBlock(frame.fn.LLVMFn, "rundefers.loop")
unreachable := c.ctx.AddBasicBlock(frame.fn.LLVMFn, "rundefers.default")
end := c.ctx.AddBasicBlock(frame.fn.LLVMFn, "rundefers.end")
c.builder.CreateBr(loophead)
// Create loop head:
// for stack != nil {
b.SetInsertPointAtEnd(loophead)
deferData := b.CreateLoad(b.deferPtr, "")
stackIsNil := b.CreateICmp(llvm.IntEQ, deferData, llvm.ConstPointerNull(deferData.Type()), "stackIsNil")
b.CreateCondBr(stackIsNil, end, loop)
c.builder.SetInsertPointAtEnd(loophead)
deferData := c.builder.CreateLoad(frame.deferPtr, "")
stackIsNil := c.builder.CreateICmp(llvm.IntEQ, deferData, llvm.ConstPointerNull(deferData.Type()), "stackIsNil")
c.builder.CreateCondBr(stackIsNil, end, loop)
// Create loop body:
// _stack := stack
// stack = stack.next
// switch stack.callback {
b.SetInsertPointAtEnd(loop)
nextStackGEP := b.CreateInBoundsGEP(deferData, []llvm.Value{
llvm.ConstInt(b.ctx.Int32Type(), 0, false),
llvm.ConstInt(b.ctx.Int32Type(), 1, false), // .next field
c.builder.SetInsertPointAtEnd(loop)
nextStackGEP := c.builder.CreateInBoundsGEP(deferData, []llvm.Value{
llvm.ConstInt(c.ctx.Int32Type(), 0, false),
llvm.ConstInt(c.ctx.Int32Type(), 1, false), // .next field
}, "stack.next.gep")
nextStack := b.CreateLoad(nextStackGEP, "stack.next")
b.CreateStore(nextStack, b.deferPtr)
gep := b.CreateInBoundsGEP(deferData, []llvm.Value{
llvm.ConstInt(b.ctx.Int32Type(), 0, false),
llvm.ConstInt(b.ctx.Int32Type(), 0, false), // .callback field
nextStack := c.builder.CreateLoad(nextStackGEP, "stack.next")
c.builder.CreateStore(nextStack, frame.deferPtr)
gep := c.builder.CreateInBoundsGEP(deferData, []llvm.Value{
llvm.ConstInt(c.ctx.Int32Type(), 0, false),
llvm.ConstInt(c.ctx.Int32Type(), 0, false), // .callback field
}, "callback.gep")
callback := b.CreateLoad(gep, "callback")
sw := b.CreateSwitch(callback, unreachable, len(b.allDeferFuncs))
callback := c.builder.CreateLoad(gep, "callback")
sw := c.builder.CreateSwitch(callback, unreachable, len(frame.allDeferFuncs))
for i, callback := range b.allDeferFuncs {
for i, callback := range frame.allDeferFuncs {
// Create switch case, for example:
// case 0:
// // run first deferred call
block := b.ctx.AddBasicBlock(b.fn.LLVMFn, "rundefers.callback")
sw.AddCase(llvm.ConstInt(b.uintptrType, uint64(i), false), block)
b.SetInsertPointAtEnd(block)
block := c.ctx.AddBasicBlock(frame.fn.LLVMFn, "rundefers.callback")
sw.AddCase(llvm.ConstInt(c.uintptrType, uint64(i), false), block)
c.builder.SetInsertPointAtEnd(block)
switch callback := callback.(type) {
case *ssa.CallCommon:
// Call on an interface value.
@@ -249,112 +203,105 @@ func (b *builder) createRunDefers() {
}
// Get the real defer struct type and cast to it.
valueTypes := []llvm.Type{b.uintptrType, llvm.PointerType(b.getLLVMRuntimeType("_defer"), 0), b.uintptrType, b.i8ptrType}
valueTypes := []llvm.Type{c.uintptrType, llvm.PointerType(c.getLLVMRuntimeType("_defer"), 0), c.i8ptrType}
for _, arg := range callback.Args {
valueTypes = append(valueTypes, b.getLLVMType(arg.Type()))
valueTypes = append(valueTypes, c.getLLVMType(arg.Type()))
}
deferFrameType := b.ctx.StructType(valueTypes, false)
deferFramePtr := b.CreateBitCast(deferData, llvm.PointerType(deferFrameType, 0), "deferFrame")
deferFrameType := c.ctx.StructType(valueTypes, false)
deferFramePtr := c.builder.CreateBitCast(deferData, llvm.PointerType(deferFrameType, 0), "deferFrame")
// Extract the params from the struct (including receiver).
forwardParams := []llvm.Value{}
zero := llvm.ConstInt(b.ctx.Int32Type(), 0, false)
zero := llvm.ConstInt(c.ctx.Int32Type(), 0, false)
for i := 2; i < len(valueTypes); i++ {
gep := b.CreateInBoundsGEP(deferFramePtr, []llvm.Value{zero, llvm.ConstInt(b.ctx.Int32Type(), uint64(i), false)}, "gep")
forwardParam := b.CreateLoad(gep, "param")
gep := c.builder.CreateInBoundsGEP(deferFramePtr, []llvm.Value{zero, llvm.ConstInt(c.ctx.Int32Type(), uint64(i), false)}, "gep")
forwardParam := c.builder.CreateLoad(gep, "param")
forwardParams = append(forwardParams, forwardParam)
}
// Isolate the typecode.
typecode, forwardParams := forwardParams[0], forwardParams[1:]
// Add the context parameter. An interface call cannot also be a
// closure but we have to supply the parameter anyway for platforms
// with a strict calling convention.
forwardParams = append(forwardParams, llvm.Undef(b.i8ptrType))
forwardParams = append(forwardParams, llvm.Undef(c.i8ptrType))
// Parent coroutine handle.
forwardParams = append(forwardParams, llvm.Undef(b.i8ptrType))
forwardParams = append(forwardParams, llvm.Undef(c.i8ptrType))
fnPtr := b.getInvokePtr(callback, typecode)
b.createCall(fnPtr, forwardParams, "")
fnPtr, _ := c.getInvokeCall(frame, callback)
c.createCall(fnPtr, forwardParams, "")
case *ir.Function:
// Direct call.
// Get the real defer struct type and cast to it.
valueTypes := []llvm.Type{b.uintptrType, llvm.PointerType(b.getLLVMRuntimeType("_defer"), 0)}
valueTypes := []llvm.Type{c.uintptrType, llvm.PointerType(c.getLLVMRuntimeType("_defer"), 0)}
for _, param := range callback.Params {
valueTypes = append(valueTypes, b.getLLVMType(param.Type()))
valueTypes = append(valueTypes, c.getLLVMType(param.Type()))
}
deferFrameType := b.ctx.StructType(valueTypes, false)
deferFramePtr := b.CreateBitCast(deferData, llvm.PointerType(deferFrameType, 0), "deferFrame")
deferFrameType := c.ctx.StructType(valueTypes, false)
deferFramePtr := c.builder.CreateBitCast(deferData, llvm.PointerType(deferFrameType, 0), "deferFrame")
// Extract the params from the struct.
forwardParams := []llvm.Value{}
zero := llvm.ConstInt(b.ctx.Int32Type(), 0, false)
zero := llvm.ConstInt(c.ctx.Int32Type(), 0, false)
for i := range callback.Params {
gep := b.CreateInBoundsGEP(deferFramePtr, []llvm.Value{zero, llvm.ConstInt(b.ctx.Int32Type(), uint64(i+2), false)}, "gep")
forwardParam := b.CreateLoad(gep, "param")
gep := c.builder.CreateInBoundsGEP(deferFramePtr, []llvm.Value{zero, llvm.ConstInt(c.ctx.Int32Type(), uint64(i+2), false)}, "gep")
forwardParam := c.builder.CreateLoad(gep, "param")
forwardParams = append(forwardParams, forwardParam)
}
// Plain TinyGo functions add some extra parameters to implement async functionality and function recievers.
// These parameters should not be supplied when calling into an external C/ASM function.
if !callback.IsExported() {
// Add the context parameter. We know it is ignored by the receiving
// function, but we have to pass one anyway.
forwardParams = append(forwardParams, llvm.Undef(b.i8ptrType))
// Add the context parameter. We know it is ignored by the receiving
// function, but we have to pass one anyway.
forwardParams = append(forwardParams, llvm.Undef(c.i8ptrType))
// Parent coroutine handle.
forwardParams = append(forwardParams, llvm.Undef(b.i8ptrType))
}
// Parent coroutine handle.
forwardParams = append(forwardParams, llvm.Undef(c.i8ptrType))
// Call real function.
b.createCall(callback.LLVMFn, forwardParams, "")
c.createCall(callback.LLVMFn, forwardParams, "")
case *ssa.MakeClosure:
// Get the real defer struct type and cast to it.
fn := b.ir.GetFunction(callback.Fn.(*ssa.Function))
valueTypes := []llvm.Type{b.uintptrType, llvm.PointerType(b.getLLVMRuntimeType("_defer"), 0)}
fn := c.ir.GetFunction(callback.Fn.(*ssa.Function))
valueTypes := []llvm.Type{c.uintptrType, llvm.PointerType(c.getLLVMRuntimeType("_defer"), 0)}
params := fn.Signature.Params()
for i := 0; i < params.Len(); i++ {
valueTypes = append(valueTypes, b.getLLVMType(params.At(i).Type()))
valueTypes = append(valueTypes, c.getLLVMType(params.At(i).Type()))
}
valueTypes = append(valueTypes, b.i8ptrType) // closure
deferFrameType := b.ctx.StructType(valueTypes, false)
deferFramePtr := b.CreateBitCast(deferData, llvm.PointerType(deferFrameType, 0), "deferFrame")
valueTypes = append(valueTypes, c.i8ptrType) // closure
deferFrameType := c.ctx.StructType(valueTypes, false)
deferFramePtr := c.builder.CreateBitCast(deferData, llvm.PointerType(deferFrameType, 0), "deferFrame")
// Extract the params from the struct.
forwardParams := []llvm.Value{}
zero := llvm.ConstInt(b.ctx.Int32Type(), 0, false)
zero := llvm.ConstInt(c.ctx.Int32Type(), 0, false)
for i := 2; i < len(valueTypes); i++ {
gep := b.CreateInBoundsGEP(deferFramePtr, []llvm.Value{zero, llvm.ConstInt(b.ctx.Int32Type(), uint64(i), false)}, "")
forwardParam := b.CreateLoad(gep, "param")
gep := c.builder.CreateInBoundsGEP(deferFramePtr, []llvm.Value{zero, llvm.ConstInt(c.ctx.Int32Type(), uint64(i), false)}, "")
forwardParam := c.builder.CreateLoad(gep, "param")
forwardParams = append(forwardParams, forwardParam)
}
// Parent coroutine handle.
forwardParams = append(forwardParams, llvm.Undef(b.i8ptrType))
forwardParams = append(forwardParams, llvm.Undef(c.i8ptrType))
// Call deferred function.
b.createCall(fn.LLVMFn, forwardParams, "")
c.createCall(fn.LLVMFn, forwardParams, "")
default:
panic("unknown deferred function type")
}
// Branch back to the start of the loop.
b.CreateBr(loophead)
c.builder.CreateBr(loophead)
}
// Create default unreachable block:
// default:
// unreachable
// }
b.SetInsertPointAtEnd(unreachable)
b.CreateUnreachable()
c.builder.SetInsertPointAtEnd(unreachable)
c.builder.CreateUnreachable()
// End of loop.
b.SetInsertPointAtEnd(end)
c.builder.SetInsertPointAtEnd(end)
}
+2 -48
View File
@@ -1,18 +1,11 @@
package compiler
// This file contains some utility functions related to error handling.
import (
"go/scanner"
"go/token"
"go/types"
"path/filepath"
"tinygo.org/x/go-llvm"
)
// makeError makes it easy to create an error from a token.Pos with a message.
func (c *compilerContext) makeError(pos token.Pos, msg string) types.Error {
func (c *Compiler) makeError(pos token.Pos, msg string) types.Error {
return types.Error{
Fset: c.ir.Program.Fset,
Pos: pos,
@@ -20,45 +13,6 @@ func (c *compilerContext) makeError(pos token.Pos, msg string) types.Error {
}
}
func (c *compilerContext) addError(pos token.Pos, msg string) {
func (c *Compiler) addError(pos token.Pos, msg string) {
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
// it is available.
func getPosition(val llvm.Value) token.Position {
if !val.IsAInstruction().IsNil() {
loc := val.InstructionDebugLoc()
if loc.IsNil() {
return token.Position{}
}
file := loc.LocationScope().ScopeFile()
return token.Position{
Filename: filepath.Join(file.FileDirectory(), file.FileFilename()),
Line: int(loc.LocationLine()),
Column: int(loc.LocationColumn()),
}
} else if !val.IsAFunction().IsNil() {
loc := val.Subprogram()
if loc.IsNil() {
return token.Position{}
}
file := loc.ScopeFile()
return token.Position{
Filename: filepath.Join(file.FileDirectory(), file.FileFilename()),
Line: int(loc.SubprogramLine()),
}
} else {
return token.Position{}
}
}
+285
View File
@@ -0,0 +1,285 @@
package compiler
// This file lowers func values into their final form. This is necessary for
// funcValueSwitch, which needs full program analysis.
import (
"sort"
"strconv"
"tinygo.org/x/go-llvm"
)
// funcSignatureInfo keeps information about a single signature and its uses.
type funcSignatureInfo struct {
sig llvm.Value // *uint8 to identify the signature
funcValueWithSignatures []llvm.Value // slice of runtime.funcValueWithSignature
}
// funcWithUses keeps information about a single function used as func value and
// the assigned function ID. More commonly used functions are assigned a lower
// ID.
type funcWithUses struct {
funcPtr llvm.Value
useCount int // how often this function is used in a func value
id int // assigned ID
}
// Slice to sort functions by their use counts, or else their name if they're
// used equally often.
type funcWithUsesList []*funcWithUses
func (l funcWithUsesList) Len() int { return len(l) }
func (l funcWithUsesList) Less(i, j int) bool {
if l[i].useCount != l[j].useCount {
// return the reverse: we want the highest use counts sorted first
return l[i].useCount > l[j].useCount
}
iName := l[i].funcPtr.Name()
jName := l[j].funcPtr.Name()
return iName < jName
}
func (l funcWithUsesList) Swap(i, j int) {
l[i], l[j] = l[j], l[i]
}
// LowerFuncValue lowers the runtime.funcValueWithSignature type and
// runtime.getFuncPtr function to their final form.
func (c *Compiler) LowerFuncValues() {
if c.funcImplementation() != funcValueSwitch {
return
}
// Find all func values used in the program with their signatures.
funcValueWithSignaturePtr := llvm.PointerType(c.getLLVMRuntimeType("funcValueWithSignature"), 0)
signatures := map[string]*funcSignatureInfo{}
for global := c.mod.FirstGlobal(); !global.IsNil(); global = llvm.NextGlobal(global) {
if global.Type() != funcValueWithSignaturePtr {
continue
}
sig := llvm.ConstExtractValue(global.Initializer(), []uint32{1})
name := sig.Name()
if info, ok := signatures[name]; ok {
info.funcValueWithSignatures = append(info.funcValueWithSignatures, global)
} else {
signatures[name] = &funcSignatureInfo{
sig: sig,
funcValueWithSignatures: []llvm.Value{global},
}
}
}
// Sort the signatures, for deterministic execution.
names := make([]string, 0, len(signatures))
for name := range signatures {
names = append(names, name)
}
sort.Strings(names)
for _, name := range names {
info := signatures[name]
functions := make(funcWithUsesList, len(info.funcValueWithSignatures))
for i, use := range info.funcValueWithSignatures {
var useCount int
for _, use2 := range getUses(use) {
useCount += len(getUses(use2))
}
functions[i] = &funcWithUses{
funcPtr: llvm.ConstExtractValue(use.Initializer(), []uint32{0}).Operand(0),
useCount: useCount,
}
}
sort.Sort(functions)
for i, fn := range functions {
fn.id = i + 1
for _, ptrtoint := range getUses(fn.funcPtr) {
if ptrtoint.IsAConstantExpr().IsNil() || ptrtoint.Opcode() != llvm.PtrToInt {
continue
}
for _, funcValueWithSignatureConstant := range getUses(ptrtoint) {
for _, funcValueWithSignatureGlobal := range getUses(funcValueWithSignatureConstant) {
for _, use := range getUses(funcValueWithSignatureGlobal) {
if ptrtoint.IsAConstantExpr().IsNil() || ptrtoint.Opcode() != llvm.PtrToInt {
panic("expected const ptrtoint")
}
use.ReplaceAllUsesWith(llvm.ConstInt(c.uintptrType, uint64(fn.id), false))
}
}
}
}
}
for _, getFuncPtrCall := range getUses(info.sig) {
if getFuncPtrCall.IsACallInst().IsNil() {
continue
}
if getFuncPtrCall.CalledValue().Name() != "runtime.getFuncPtr" {
panic("expected all call uses to be runtime.getFuncPtr")
}
funcID := getFuncPtrCall.Operand(1)
switch len(functions) {
case 0:
// There are no functions used in a func value that implement
// this signature. The only possible value is a nil value.
for _, inttoptr := range getUses(getFuncPtrCall) {
if inttoptr.IsAIntToPtrInst().IsNil() {
panic("expected inttoptr")
}
nilptr := llvm.ConstPointerNull(inttoptr.Type())
inttoptr.ReplaceAllUsesWith(nilptr)
inttoptr.EraseFromParentAsInstruction()
}
getFuncPtrCall.EraseFromParentAsInstruction()
case 1:
// There is exactly one function with this signature that is
// used in a func value. The func value itself can be either nil
// or this one function.
c.builder.SetInsertPointBefore(getFuncPtrCall)
zero := llvm.ConstInt(c.uintptrType, 0, false)
isnil := c.builder.CreateICmp(llvm.IntEQ, funcID, zero, "")
funcPtrNil := llvm.ConstPointerNull(functions[0].funcPtr.Type())
funcPtr := c.builder.CreateSelect(isnil, funcPtrNil, functions[0].funcPtr, "")
for _, inttoptr := range getUses(getFuncPtrCall) {
if inttoptr.IsAIntToPtrInst().IsNil() {
panic("expected inttoptr")
}
inttoptr.ReplaceAllUsesWith(funcPtr)
inttoptr.EraseFromParentAsInstruction()
}
getFuncPtrCall.EraseFromParentAsInstruction()
default:
// There are multiple functions used in a func value that
// implement this signature.
// What we'll do is transform the following:
// rawPtr := runtime.getFuncPtr(func.ptr)
// if rawPtr == nil {
// runtime.nilPanic()
// }
// result := rawPtr(...args, func.context)
// into this:
// if false {
// runtime.nilPanic()
// }
// var result // Phi
// switch fn.id {
// case 0:
// runtime.nilPanic()
// case 1:
// result = call first implementation...
// case 2:
// result = call second implementation...
// default:
// unreachable
// }
// Remove some casts, checks, and the old call which we're going
// to replace.
for _, callIntPtr := range getUses(getFuncPtrCall) {
if !callIntPtr.IsACallInst().IsNil() && callIntPtr.CalledValue().Name() == "runtime.makeGoroutine" {
for _, inttoptr := range getUses(callIntPtr) {
if inttoptr.IsAIntToPtrInst().IsNil() {
panic("expected a inttoptr")
}
for _, use := range getUses(inttoptr) {
c.addFuncLoweringSwitch(funcID, use, c.emitStartGoroutine, functions)
use.EraseFromParentAsInstruction()
}
inttoptr.EraseFromParentAsInstruction()
}
callIntPtr.EraseFromParentAsInstruction()
continue
}
if callIntPtr.IsAIntToPtrInst().IsNil() {
panic("expected inttoptr")
}
for _, ptrUse := range getUses(callIntPtr) {
if !ptrUse.IsABitCastInst().IsNil() {
for _, bitcastUse := range getUses(ptrUse) {
if bitcastUse.IsACallInst().IsNil() || bitcastUse.CalledValue().IsAFunction().IsNil() {
panic("expected a call instruction")
}
switch bitcastUse.CalledValue().Name() {
case "runtime.isnil":
bitcastUse.ReplaceAllUsesWith(llvm.ConstInt(c.ctx.Int1Type(), 0, false))
bitcastUse.EraseFromParentAsInstruction()
default:
panic("expected a call to runtime.isnil")
}
}
} else if !ptrUse.IsACallInst().IsNil() && ptrUse.CalledValue() == callIntPtr {
c.addFuncLoweringSwitch(funcID, ptrUse, func(funcPtr llvm.Value, params []llvm.Value) llvm.Value {
return c.builder.CreateCall(funcPtr, params, "")
}, functions)
} else {
panic("unexpected getFuncPtrCall")
}
ptrUse.EraseFromParentAsInstruction()
}
callIntPtr.EraseFromParentAsInstruction()
}
getFuncPtrCall.EraseFromParentAsInstruction()
}
}
}
}
// addFuncLoweringSwitch creates a new switch on a function ID and inserts calls
// to the newly created direct calls. The funcID is the number to switch on,
// call is the call instruction to replace, and createCall is the callback that
// actually creates the new call. By changing createCall to something other than
// c.builder.CreateCall, instead of calling a function it can start a new
// goroutine for example.
func (c *Compiler) addFuncLoweringSwitch(funcID, call llvm.Value, createCall func(funcPtr llvm.Value, params []llvm.Value) llvm.Value, functions funcWithUsesList) {
// The block that cannot be reached with correct funcValues (to help the
// optimizer).
c.builder.SetInsertPointBefore(call)
defaultBlock := c.ctx.AddBasicBlock(call.InstructionParent().Parent(), "func.default")
c.builder.SetInsertPointAtEnd(defaultBlock)
c.builder.CreateUnreachable()
// Create the switch.
c.builder.SetInsertPointBefore(call)
sw := c.builder.CreateSwitch(funcID, defaultBlock, len(functions)+1)
// Split right after the switch. We will need to insert a few basic blocks
// in this gap.
nextBlock := c.splitBasicBlock(sw, llvm.NextBasicBlock(sw.InstructionParent()), "func.next")
// The 0 case, which is actually a nil check.
nilBlock := c.ctx.InsertBasicBlock(nextBlock, "func.nil")
c.builder.SetInsertPointAtEnd(nilBlock)
c.createRuntimeCall("nilPanic", nil, "")
c.builder.CreateUnreachable()
sw.AddCase(llvm.ConstInt(c.uintptrType, 0, false), nilBlock)
// Gather the list of parameters for every call we're going to make.
callParams := make([]llvm.Value, call.OperandsCount()-1)
for i := range callParams {
callParams[i] = call.Operand(i)
}
// If the call produces a value, we need to get it using a PHI
// node.
phiBlocks := make([]llvm.BasicBlock, len(functions))
phiValues := make([]llvm.Value, len(functions))
for i, fn := range functions {
// Insert a switch case.
bb := c.ctx.InsertBasicBlock(nextBlock, "func.call"+strconv.Itoa(fn.id))
c.builder.SetInsertPointAtEnd(bb)
result := createCall(fn.funcPtr, callParams)
c.builder.CreateBr(nextBlock)
sw.AddCase(llvm.ConstInt(c.uintptrType, uint64(fn.id), false), bb)
phiBlocks[i] = bb
phiValues[i] = result
}
// Create the PHI node so that the call result flows into the
// next block (after the split). This is only necessary when the
// call produced a value.
if call.Type().TypeKind() != llvm.VoidTypeKind {
c.builder.SetInsertPointBefore(nextBlock.FirstInstruction())
phi := c.builder.CreatePHI(call.Type(), "")
phi.AddIncoming(phiValues, phiBlocks)
call.ReplaceAllUsesWith(phi)
}
}
+64 -41
View File
@@ -6,26 +6,53 @@ package compiler
import (
"go/types"
"github.com/tinygo-org/tinygo/compileopts"
"golang.org/x/tools/go/ssa"
"tinygo.org/x/go-llvm"
)
// createFuncValue creates a function value from a raw function pointer with no
// context.
func (b *builder) createFuncValue(funcPtr, context llvm.Value, sig *types.Signature) llvm.Value {
return b.compilerContext.createFuncValue(b.Builder, funcPtr, context, sig)
type funcValueImplementation int
const (
funcValueNone funcValueImplementation = iota
// A func value is implemented as a pair of pointers:
// {context, function pointer}
// where the context may be a pointer to a heap-allocated struct containing
// the free variables, or it may be undef if the function being pointed to
// doesn't need a context. The function pointer is a regular function
// pointer.
funcValueDoubleword
// As funcValueDoubleword, but with the function pointer replaced by a
// unique ID per function signature. Function values are called by using a
// switch statement and choosing which function to call.
funcValueSwitch
)
// funcImplementation picks an appropriate func value implementation for the
// target.
func (c *Compiler) funcImplementation() funcValueImplementation {
// Always pick the switch implementation, as it allows the use of blocking
// inside a function that is used as a func value.
switch c.selectScheduler() {
case "coroutines":
return funcValueSwitch
case "tasks":
return funcValueDoubleword
default:
panic("unknown scheduler type")
}
}
// createFuncValue creates a function value from a raw function pointer with no
// context.
func (c *compilerContext) createFuncValue(builder llvm.Builder, funcPtr, context llvm.Value, sig *types.Signature) llvm.Value {
func (c *Compiler) createFuncValue(funcPtr, context llvm.Value, sig *types.Signature) llvm.Value {
var funcValueScalar llvm.Value
switch c.FuncImplementation() {
case compileopts.FuncValueDoubleword:
switch c.funcImplementation() {
case funcValueDoubleword:
// Closure is: {context, function pointer}
funcValueScalar = funcPtr
case compileopts.FuncValueSwitch:
case funcValueSwitch:
sigGlobal := c.getTypeCode(sig)
funcValueWithSignatureGlobalName := funcPtr.Name() + "$withSignature"
funcValueWithSignatureGlobal := c.mod.NamedGlobal(funcValueWithSignatureGlobalName)
@@ -46,35 +73,35 @@ func (c *compilerContext) createFuncValue(builder llvm.Builder, funcPtr, context
}
funcValueType := c.getFuncType(sig)
funcValue := llvm.Undef(funcValueType)
funcValue = builder.CreateInsertValue(funcValue, context, 0, "")
funcValue = builder.CreateInsertValue(funcValue, funcValueScalar, 1, "")
funcValue = c.builder.CreateInsertValue(funcValue, context, 0, "")
funcValue = c.builder.CreateInsertValue(funcValue, funcValueScalar, 1, "")
return funcValue
}
// extractFuncScalar returns some scalar that can be used in comparisons. It is
// a cheap operation.
func (b *builder) extractFuncScalar(funcValue llvm.Value) llvm.Value {
return b.CreateExtractValue(funcValue, 1, "")
func (c *Compiler) extractFuncScalar(funcValue llvm.Value) llvm.Value {
return c.builder.CreateExtractValue(funcValue, 1, "")
}
// extractFuncContext extracts the context pointer from this function value. It
// is a cheap operation.
func (b *builder) extractFuncContext(funcValue llvm.Value) llvm.Value {
return b.CreateExtractValue(funcValue, 0, "")
func (c *Compiler) extractFuncContext(funcValue llvm.Value) llvm.Value {
return c.builder.CreateExtractValue(funcValue, 0, "")
}
// decodeFuncValue extracts the context and the function pointer from this func
// value. This may be an expensive operation.
func (b *builder) decodeFuncValue(funcValue llvm.Value, sig *types.Signature) (funcPtr, context llvm.Value) {
context = b.CreateExtractValue(funcValue, 0, "")
switch b.FuncImplementation() {
case compileopts.FuncValueDoubleword:
funcPtr = b.CreateExtractValue(funcValue, 1, "")
case compileopts.FuncValueSwitch:
llvmSig := b.getRawFuncType(sig)
sigGlobal := b.getTypeCode(sig)
funcPtr = b.createRuntimeCall("getFuncPtr", []llvm.Value{funcValue, sigGlobal}, "")
funcPtr = b.CreateIntToPtr(funcPtr, llvmSig, "")
func (c *Compiler) decodeFuncValue(funcValue llvm.Value, sig *types.Signature) (funcPtr, context llvm.Value) {
context = c.builder.CreateExtractValue(funcValue, 0, "")
switch c.funcImplementation() {
case funcValueDoubleword:
funcPtr = c.builder.CreateExtractValue(funcValue, 1, "")
case funcValueSwitch:
llvmSig := c.getRawFuncType(sig)
sigGlobal := c.getTypeCode(sig)
funcPtr = c.createRuntimeCall("getFuncPtr", []llvm.Value{funcValue, sigGlobal}, "")
funcPtr = c.builder.CreateIntToPtr(funcPtr, llvmSig, "")
default:
panic("unimplemented func value variant")
}
@@ -82,12 +109,12 @@ func (b *builder) decodeFuncValue(funcValue llvm.Value, sig *types.Signature) (f
}
// getFuncType returns the type of a func value given a signature.
func (c *compilerContext) getFuncType(typ *types.Signature) llvm.Type {
switch c.FuncImplementation() {
case compileopts.FuncValueDoubleword:
func (c *Compiler) getFuncType(typ *types.Signature) llvm.Type {
switch c.funcImplementation() {
case funcValueDoubleword:
rawPtr := c.getRawFuncType(typ)
return c.ctx.StructType([]llvm.Type{c.i8ptrType, rawPtr}, false)
case compileopts.FuncValueSwitch:
case funcValueSwitch:
return c.getLLVMRuntimeType("funcValue")
default:
panic("unimplemented func value variant")
@@ -95,7 +122,7 @@ func (c *compilerContext) getFuncType(typ *types.Signature) llvm.Type {
}
// getRawFuncType returns a LLVM function pointer type for a given signature.
func (c *compilerContext) getRawFuncType(typ *types.Signature) llvm.Type {
func (c *Compiler) getRawFuncType(typ *types.Signature) llvm.Type {
// Get the return type.
var returnType llvm.Type
switch typ.Results().Len() {
@@ -125,15 +152,11 @@ func (c *compilerContext) getRawFuncType(typ *types.Signature) llvm.Type {
// The receiver is not an interface, but a i8* type.
recv = c.i8ptrType
}
for _, info := range expandFormalParamType(recv, "", nil) {
paramTypes = append(paramTypes, info.llvmType)
}
paramTypes = append(paramTypes, c.expandFormalParamType(recv)...)
}
for i := 0; i < typ.Params().Len(); i++ {
subType := c.getLLVMType(typ.Params().At(i).Type())
for _, info := range expandFormalParamType(subType, "", nil) {
paramTypes = append(paramTypes, info.llvmType)
}
paramTypes = append(paramTypes, c.expandFormalParamType(subType)...)
}
// All functions take these parameters at the end.
paramTypes = append(paramTypes, c.i8ptrType) // context
@@ -145,24 +168,24 @@ func (c *compilerContext) getRawFuncType(typ *types.Signature) llvm.Type {
// parseMakeClosure makes a function value (with context) from the given
// closure expression.
func (b *builder) parseMakeClosure(expr *ssa.MakeClosure) (llvm.Value, error) {
func (c *Compiler) parseMakeClosure(frame *Frame, expr *ssa.MakeClosure) (llvm.Value, error) {
if len(expr.Bindings) == 0 {
panic("unexpected: MakeClosure without bound variables")
}
f := b.ir.GetFunction(expr.Fn.(*ssa.Function))
f := c.ir.GetFunction(expr.Fn.(*ssa.Function))
// Collect all bound variables.
boundVars := make([]llvm.Value, len(expr.Bindings))
for i, binding := range expr.Bindings {
// The context stores the bound variables.
llvmBoundVar := b.getValue(binding)
llvmBoundVar := c.getValue(frame, binding)
boundVars[i] = llvmBoundVar
}
// Store the bound variables in a single object, allocating it on the heap
// if necessary.
context := b.emitPointerPack(boundVars)
context := c.emitPointerPack(boundVars)
// Create the closure.
return b.createFuncValue(f.LLVMFn, context, f.Signature), nil
return c.createFuncValue(f.LLVMFn, context, f.Signature), nil
}
+393 -23
View File
@@ -5,66 +5,76 @@ package compiler
import (
"go/token"
"math/big"
"golang.org/x/tools/go/ssa"
"tinygo.org/x/go-llvm"
)
// needsStackObjects returns true if the compiler should insert stack objects
// that can be traced by the garbage collector.
func (c *Compiler) needsStackObjects() bool {
if c.selectGC() != "conservative" {
return false
}
for _, tag := range c.BuildTags {
if tag == "baremetal" {
return false
}
}
return true
}
// trackExpr inserts pointer tracking intrinsics for the GC if the expression is
// one of the expressions that need this.
func (b *builder) trackExpr(expr ssa.Value, value llvm.Value) {
func (c *Compiler) trackExpr(frame *Frame, expr ssa.Value, value llvm.Value) {
// There are uses of this expression, Make sure the pointers
// are tracked during GC.
switch expr := expr.(type) {
case *ssa.Alloc, *ssa.MakeChan, *ssa.MakeMap:
// These values are always of pointer type in IR.
b.trackPointer(value)
c.trackPointer(value)
case *ssa.Call, *ssa.Convert, *ssa.MakeClosure, *ssa.MakeInterface, *ssa.MakeSlice, *ssa.Next:
if !value.IsNil() {
b.trackValue(value)
c.trackValue(value)
}
case *ssa.Select:
if alloca, ok := b.selectRecvBuf[expr]; ok {
if alloca, ok := frame.selectRecvBuf[expr]; ok {
if alloca.IsAUndefValue().IsNil() {
b.trackPointer(alloca)
c.trackPointer(alloca)
}
}
case *ssa.UnOp:
switch expr.Op {
case token.MUL:
// Pointer dereference.
b.trackValue(value)
c.trackValue(value)
case token.ARROW:
// Channel receive operator.
// It's not necessary to look at commaOk here, because in that
// case it's just an aggregate and trackValue will extract the
// pointer in there (if there is one).
b.trackValue(value)
}
case *ssa.BinOp:
switch expr.Op {
case token.ADD:
// String concatenation.
b.trackValue(value)
c.trackValue(value)
}
}
}
// trackValue locates pointers in a value (possibly an aggregate) and tracks the
// individual pointers
func (b *builder) trackValue(value llvm.Value) {
func (c *Compiler) trackValue(value llvm.Value) {
typ := value.Type()
switch typ.TypeKind() {
case llvm.PointerTypeKind:
b.trackPointer(value)
c.trackPointer(value)
case llvm.StructTypeKind:
if !typeHasPointers(typ) {
return
}
numElements := typ.StructElementTypesCount()
for i := 0; i < numElements; i++ {
subValue := b.CreateExtractValue(value, i, "")
b.trackValue(subValue)
subValue := c.builder.CreateExtractValue(value, i, "")
c.trackValue(subValue)
}
case llvm.ArrayTypeKind:
if !typeHasPointers(typ) {
@@ -72,19 +82,19 @@ func (b *builder) trackValue(value llvm.Value) {
}
numElements := typ.ArrayLength()
for i := 0; i < numElements; i++ {
subValue := b.CreateExtractValue(value, i, "")
b.trackValue(subValue)
subValue := c.builder.CreateExtractValue(value, i, "")
c.trackValue(subValue)
}
}
}
// trackPointer creates a call to runtime.trackPointer, bitcasting the poitner
// first if needed. The input value must be of LLVM pointer type.
func (b *builder) trackPointer(value llvm.Value) {
if value.Type() != b.i8ptrType {
value = b.CreateBitCast(value, b.i8ptrType, "")
func (c *Compiler) trackPointer(value llvm.Value) {
if value.Type() != c.i8ptrType {
value = c.builder.CreateBitCast(value, c.i8ptrType, "")
}
b.createRuntimeCall("trackPointer", []llvm.Value{value}, "")
c.createRuntimeCall("trackPointer", []llvm.Value{value}, "")
}
// typeHasPointers returns whether this type is a pointer or contains pointers.
@@ -110,3 +120,363 @@ func typeHasPointers(t llvm.Type) bool {
return false
}
}
// makeGCStackSlots converts all calls to runtime.trackPointer to explicit
// stores to stack slots that are scannable by the GC.
func (c *Compiler) makeGCStackSlots() bool {
// Check whether there are allocations at all.
alloc := c.mod.NamedFunction("runtime.alloc")
if alloc.IsNil() {
// Nothing to. Make sure all remaining bits and pieces for stack
// chains are neutralized.
for _, call := range getUses(c.mod.NamedFunction("runtime.trackPointer")) {
call.EraseFromParentAsInstruction()
}
stackChainStart := c.mod.NamedGlobal("runtime.stackChainStart")
if !stackChainStart.IsNil() {
stackChainStart.SetInitializer(c.getZeroValue(stackChainStart.Type().ElementType()))
stackChainStart.SetGlobalConstant(true)
}
}
trackPointer := c.mod.NamedFunction("runtime.trackPointer")
if trackPointer.IsNil() || trackPointer.FirstUse().IsNil() {
return false // nothing to do
}
// Look at *all* functions to see whether they are free of function pointer
// calls.
// This takes less than 5ms for ~100kB of WebAssembly but would perhaps be
// faster when written in C++ (to avoid the CGo overhead).
funcsWithFPCall := map[llvm.Value]struct{}{}
n := 0
for fn := c.mod.FirstFunction(); !fn.IsNil(); fn = llvm.NextFunction(fn) {
n++
if _, ok := funcsWithFPCall[fn]; ok {
continue // already found
}
done := false
for bb := fn.FirstBasicBlock(); !bb.IsNil() && !done; bb = llvm.NextBasicBlock(bb) {
for call := bb.FirstInstruction(); !call.IsNil() && !done; call = llvm.NextInstruction(call) {
if call.IsACallInst().IsNil() {
continue // only looking at calls
}
called := call.CalledValue()
if !called.IsAFunction().IsNil() {
continue // only looking for function pointers
}
funcsWithFPCall[fn] = struct{}{}
markParentFunctions(funcsWithFPCall, fn)
done = true
}
}
}
// Determine which functions need stack objects. Many leaf functions don't
// need it: it only causes overhead for them.
// Actually, in one test it was only able to eliminate stack object from 12%
// of functions that had a call to runtime.trackPointer (8 out of 68
// functions), so this optimization is not as big as it may seem.
allocatingFunctions := map[llvm.Value]struct{}{} // set of allocating functions
// Work from runtime.alloc and trace all parents to check which functions do
// a heap allocation (and thus which functions do not).
markParentFunctions(allocatingFunctions, alloc)
// Also trace all functions that call a function pointer.
for fn := range funcsWithFPCall {
// Assume that functions that call a function pointer do a heap
// allocation as a conservative guess because the called function might
// do a heap allocation.
allocatingFunctions[fn] = struct{}{}
markParentFunctions(allocatingFunctions, fn)
}
// Collect some variables used below in the loop.
stackChainStart := c.mod.NamedGlobal("runtime.stackChainStart")
if stackChainStart.IsNil() {
panic("stack chain start not found!")
}
stackChainStartType := stackChainStart.Type().ElementType()
stackChainStart.SetInitializer(c.getZeroValue(stackChainStartType))
// Iterate until runtime.trackPointer has no uses left.
for use := trackPointer.FirstUse(); !use.IsNil(); use = trackPointer.FirstUse() {
// Pick the first use of runtime.trackPointer.
call := use.User()
if call.IsACallInst().IsNil() {
panic("expected runtime.trackPointer use to be a call")
}
// Pick the parent function.
fn := call.InstructionParent().Parent()
if _, ok := allocatingFunctions[fn]; !ok {
// This function nor any of the functions it calls (recursively)
// allocate anything from the heap, so it will not trigger a garbage
// collection cycle. Thus, it does not need to track local pointer
// values.
// This is a useful optimization but not as big as you might guess,
// as described above (it avoids stack objects for ~12% of
// functions).
call.EraseFromParentAsInstruction()
continue
}
// Find all calls to runtime.trackPointer in this function.
var calls []llvm.Value
var returns []llvm.Value
for bb := fn.FirstBasicBlock(); !bb.IsNil(); bb = llvm.NextBasicBlock(bb) {
for inst := bb.FirstInstruction(); !inst.IsNil(); inst = llvm.NextInstruction(inst) {
switch inst.InstructionOpcode() {
case llvm.Call:
if inst.CalledValue() == trackPointer {
calls = append(calls, inst)
}
case llvm.Ret:
returns = append(returns, inst)
}
}
}
// Determine what to do with each call.
var allocas, pointers []llvm.Value
for _, call := range calls {
ptr := call.Operand(0)
call.EraseFromParentAsInstruction()
if ptr.IsAInstruction().IsNil() {
continue
}
// Some trivial optimizations.
if ptr.IsAInstruction().IsNil() {
continue
}
switch ptr.InstructionOpcode() {
case llvm.PHI, llvm.GetElementPtr:
// These values do not create new values: the values already
// existed locally in this function so must have been tracked
// already.
continue
case llvm.ExtractValue, llvm.BitCast:
// These instructions do not create new values, but their
// original value may not be tracked. So keep tracking them for
// now.
// With more analysis, it should be possible to optimize a
// significant chunk of these away.
case llvm.Call, llvm.Load, llvm.IntToPtr:
// These create new values so must be stored locally. But
// perhaps some of these can be fused when they actually refer
// to the same value.
default:
// Ambiguous. These instructions are uncommon, but perhaps could
// be optimized if needed.
}
if !ptr.IsAAllocaInst().IsNil() {
if typeHasPointers(ptr.Type().ElementType()) {
allocas = append(allocas, ptr)
}
} else {
pointers = append(pointers, ptr)
}
}
if len(allocas) == 0 && len(pointers) == 0 {
// This function does not need to keep track of stack pointers.
continue
}
// Determine the type of the required stack slot.
fields := []llvm.Type{
stackChainStartType, // Pointer to parent frame.
c.uintptrType, // Number of elements in this frame.
}
for _, alloca := range allocas {
fields = append(fields, alloca.Type().ElementType())
}
for _, ptr := range pointers {
fields = append(fields, ptr.Type())
}
stackObjectType := c.ctx.StructType(fields, false)
// Create the stack object at the function entry.
c.builder.SetInsertPointBefore(fn.EntryBasicBlock().FirstInstruction())
stackObject := c.builder.CreateAlloca(stackObjectType, "gc.stackobject")
initialStackObject := c.getZeroValue(stackObjectType)
numSlots := (c.targetData.TypeAllocSize(stackObjectType) - c.targetData.TypeAllocSize(c.i8ptrType)*2) / uint64(c.targetData.ABITypeAlignment(c.uintptrType))
numSlotsValue := llvm.ConstInt(c.uintptrType, numSlots, false)
initialStackObject = llvm.ConstInsertValue(initialStackObject, numSlotsValue, []uint32{1})
c.builder.CreateStore(initialStackObject, stackObject)
// Update stack start.
parent := c.builder.CreateLoad(stackChainStart, "")
gep := c.builder.CreateGEP(stackObject, []llvm.Value{
llvm.ConstInt(c.ctx.Int32Type(), 0, false),
llvm.ConstInt(c.ctx.Int32Type(), 0, false),
}, "")
c.builder.CreateStore(parent, gep)
stackObjectCast := c.builder.CreateBitCast(stackObject, stackChainStartType, "")
c.builder.CreateStore(stackObjectCast, stackChainStart)
// Replace all independent allocas with GEPs in the stack object.
for i, alloca := range allocas {
gep := c.builder.CreateGEP(stackObject, []llvm.Value{
llvm.ConstInt(c.ctx.Int32Type(), 0, false),
llvm.ConstInt(c.ctx.Int32Type(), uint64(2+i), false),
}, "")
alloca.ReplaceAllUsesWith(gep)
alloca.EraseFromParentAsInstruction()
}
// Do a store to the stack object after each new pointer that is created.
for i, ptr := range pointers {
c.builder.SetInsertPointBefore(llvm.NextInstruction(ptr))
gep := c.builder.CreateGEP(stackObject, []llvm.Value{
llvm.ConstInt(c.ctx.Int32Type(), 0, false),
llvm.ConstInt(c.ctx.Int32Type(), uint64(2+len(allocas)+i), false),
}, "")
c.builder.CreateStore(ptr, gep)
}
// Make sure this stack object is popped from the linked list of stack
// objects at return.
for _, ret := range returns {
c.builder.SetInsertPointBefore(ret)
c.builder.CreateStore(parent, stackChainStart)
}
}
return true
}
func (c *Compiler) addGlobalsBitmap() bool {
if c.mod.NamedGlobal("runtime.trackedGlobalsStart").IsNil() {
return false // nothing to do: no GC in use
}
var trackedGlobals []llvm.Value
var trackedGlobalTypes []llvm.Type
for global := c.mod.FirstGlobal(); !global.IsNil(); global = llvm.NextGlobal(global) {
if global.IsDeclaration() {
continue
}
typ := global.Type().ElementType()
ptrs := c.getPointerBitmap(typ, global.Name())
if ptrs.BitLen() == 0 {
continue
}
trackedGlobals = append(trackedGlobals, global)
trackedGlobalTypes = append(trackedGlobalTypes, typ)
}
globalsBundleType := c.ctx.StructType(trackedGlobalTypes, false)
globalsBundle := llvm.AddGlobal(c.mod, globalsBundleType, "tinygo.trackedGlobals")
globalsBundle.SetLinkage(llvm.InternalLinkage)
globalsBundle.SetUnnamedAddr(true)
initializer := llvm.Undef(globalsBundleType)
for i, global := range trackedGlobals {
initializer = llvm.ConstInsertValue(initializer, global.Initializer(), []uint32{uint32(i)})
gep := llvm.ConstGEP(globalsBundle, []llvm.Value{
llvm.ConstInt(c.ctx.Int32Type(), 0, false),
llvm.ConstInt(c.ctx.Int32Type(), uint64(i), false),
})
global.ReplaceAllUsesWith(gep)
global.EraseFromParentAsGlobal()
}
globalsBundle.SetInitializer(initializer)
trackedGlobalsStart := llvm.ConstPtrToInt(globalsBundle, c.uintptrType)
c.mod.NamedGlobal("runtime.trackedGlobalsStart").SetInitializer(trackedGlobalsStart)
alignment := c.targetData.PrefTypeAlignment(c.i8ptrType)
trackedGlobalsLength := llvm.ConstInt(c.uintptrType, c.targetData.TypeAllocSize(globalsBundleType)/uint64(alignment), false)
c.mod.NamedGlobal("runtime.trackedGlobalsLength").SetInitializer(trackedGlobalsLength)
bitmapBytes := c.getPointerBitmap(globalsBundleType, "globals bundle").Bytes()
bitmapValues := make([]llvm.Value, len(bitmapBytes))
for i, b := range bitmapBytes {
bitmapValues[len(bitmapBytes)-i-1] = llvm.ConstInt(c.ctx.Int8Type(), uint64(b), false)
}
bitmapArray := llvm.ConstArray(c.ctx.Int8Type(), bitmapValues)
bitmapNew := llvm.AddGlobal(c.mod, bitmapArray.Type(), "runtime.trackedGlobalsBitmap.tmp")
bitmapOld := c.mod.NamedGlobal("runtime.trackedGlobalsBitmap")
for _, inst := range getUses(bitmapOld) {
c.builder.SetInsertPointBefore(inst)
bc := c.builder.CreateBitCast(bitmapNew, bitmapOld.Type(), "runtime.trackedGlobalsBitmap.cast")
for i := 0; i < inst.OperandsCount(); i++ {
if inst.Operand(i) == bitmapOld {
inst.SetOperand(i, bc)
}
}
}
if len(getUses(bitmapOld)) > 0 {
panic("failed to eliminate tracked globals bitmap representation")
}
bitmapNew.SetInitializer(bitmapArray)
bitmapNew.SetName("runtime.trackedGlobalsBitmap")
return true // the IR was changed
}
func (c *Compiler) getPointerBitmap(typ llvm.Type, name string) *big.Int {
alignment := c.targetData.PrefTypeAlignment(c.i8ptrType)
switch typ.TypeKind() {
case llvm.IntegerTypeKind, llvm.FloatTypeKind, llvm.DoubleTypeKind:
return big.NewInt(0)
case llvm.PointerTypeKind:
return big.NewInt(1)
case llvm.StructTypeKind:
ptrs := big.NewInt(0)
for i, subtyp := range typ.StructElementTypes() {
subptrs := c.getPointerBitmap(subtyp, name)
if subptrs.BitLen() == 0 {
continue
}
offset := c.targetData.ElementOffset(typ, i)
if offset%uint64(alignment) != 0 {
panic("precise GC: global contains unaligned pointer: " + name)
}
subptrs.Lsh(subptrs, uint(offset)/uint(alignment))
ptrs.Or(ptrs, subptrs)
}
return ptrs
case llvm.ArrayTypeKind:
subtyp := typ.ElementType()
subptrs := c.getPointerBitmap(subtyp, name)
ptrs := big.NewInt(0)
if subptrs.BitLen() == 0 {
return ptrs
}
elementSize := c.targetData.TypeAllocSize(subtyp)
for i := 0; i < typ.ArrayLength(); i++ {
ptrs.Lsh(ptrs, uint(elementSize)/uint(alignment))
ptrs.Or(ptrs, subptrs)
}
return ptrs
default:
panic("unknown type kind of global: " + name)
}
}
// markParentFunctions traverses all parent function calls (recursively) and
// adds them to the set of marked functions. It only considers function calls:
// any other uses of such a function is ignored.
func markParentFunctions(marked map[llvm.Value]struct{}, fn llvm.Value) {
worklist := []llvm.Value{fn}
for len(worklist) != 0 {
fn := worklist[len(worklist)-1]
worklist = worklist[:len(worklist)-1]
for _, use := range getUses(fn) {
if use.IsACallInst().IsNil() || use.CalledValue() != fn {
// Not the parent function.
continue
}
parent := use.InstructionParent().Parent()
if _, ok := marked[parent]; !ok {
marked[parent] = struct{}{}
worklist = append(worklist, parent)
}
}
}
}
+673
View File
@@ -0,0 +1,673 @@
package compiler
// This file implements lowering for the goroutine scheduler. There are two
// scheduler implementations, one based on tasks (like RTOSes and the main Go
// runtime) and one based on a coroutine compiler transformation. The task based
// implementation requires very little work from the compiler but is not very
// portable (in particular, it is very hard if not impossible to support on
// WebAssembly). The coroutine based one requires a lot of work by the compiler
// to implement, but can run virtually anywhere with a single scheduler
// implementation.
//
// The below description is for the coroutine based scheduler.
//
// This file lowers goroutine pseudo-functions into coroutines scheduled by a
// scheduler at runtime. It uses coroutine support in LLVM for this
// transformation: https://llvm.org/docs/Coroutines.html
//
// For example, take the following code:
//
// func main() {
// go foo()
// time.Sleep(2 * time.Second)
// println("some other operation")
// i := bar()
// println("done", *i)
// }
//
// func foo() {
// for {
// println("foo!")
// time.Sleep(time.Second)
// }
// }
//
// func bar() *int {
// time.Sleep(time.Second)
// println("blocking operation completed)
// return new(int)
// }
//
// It is transformed by the IR generator in compiler.go into the following
// pseudo-Go code:
//
// func main() {
// fn := runtime.makeGoroutine(foo)
// fn()
// time.Sleep(2 * time.Second)
// println("some other operation")
// i := bar() // imagine an 'await' keyword in front of this call
// println("done", *i)
// }
//
// func foo() {
// for {
// println("foo!")
// time.Sleep(time.Second)
// }
// }
//
// func bar() *int {
// time.Sleep(time.Second)
// println("blocking operation completed)
// return new(int)
// }
//
// The pass in this file transforms this code even further, to the following
// async/await style pseudocode:
//
// func main(parent) {
// hdl := llvm.makeCoroutine()
// foo(nil) // do not pass the parent coroutine: this is an independent goroutine
// runtime.sleepTask(hdl, 2 * time.Second) // ask the scheduler to re-activate this coroutine at the right time
// llvm.suspend(hdl) // suspend point
// println("some other operation")
// var i *int // allocate space on the stack for the return value
// runtime.setTaskStatePtr(hdl, &i) // store return value alloca in our coroutine promise
// bar(hdl) // await, pass a continuation (hdl) to bar
// llvm.suspend(hdl) // suspend point, wait for the callee to re-activate
// println("done", *i)
// runtime.activateTask(parent) // re-activate the parent (nop, there is no parent)
// }
//
// func foo(parent) {
// hdl := llvm.makeCoroutine()
// for {
// println("foo!")
// runtime.sleepTask(hdl, time.Second) // ask the scheduler to re-activate this coroutine at the right time
// llvm.suspend(hdl) // suspend point
// }
// }
//
// func bar(parent) {
// hdl := llvm.makeCoroutine()
// runtime.sleepTask(hdl, time.Second) // ask the scheduler to re-activate this coroutine at the right time
// llvm.suspend(hdl) // suspend point
// println("blocking operation completed)
// runtime.activateTask(parent) // re-activate the parent coroutine before returning
// }
//
// The real LLVM code is more complicated, but this is the general idea.
//
// The LLVM coroutine passes will then process this file further transforming
// these three functions into coroutines. Most of the actual work is done by the
// scheduler, which runs in the background scheduling all coroutines.
import (
"errors"
"strings"
"tinygo.org/x/go-llvm"
)
type asyncFunc struct {
taskHandle llvm.Value
cleanupBlock llvm.BasicBlock
suspendBlock llvm.BasicBlock
unreachableBlock llvm.BasicBlock
}
// LowerGoroutines performs some IR transformations necessary to support
// goroutines. It does something different based on whether it uses the
// coroutine or the tasks implementation of goroutines, and whether goroutines
// are necessary at all.
func (c *Compiler) LowerGoroutines() error {
switch c.selectScheduler() {
case "coroutines":
return c.lowerCoroutines()
case "tasks":
return c.lowerTasks()
default:
panic("unknown scheduler type")
}
}
// lowerTasks starts the main goroutine and then runs the scheduler.
// This is enough compiler-level transformation for the task-based scheduler.
func (c *Compiler) lowerTasks() error {
uses := getUses(c.mod.NamedFunction("runtime.callMain"))
if len(uses) != 1 || uses[0].IsACallInst().IsNil() {
panic("expected exactly 1 call of runtime.callMain, check the entry point")
}
mainCall := uses[0]
realMain := c.mod.NamedFunction(c.ir.MainPkg().Pkg.Path() + ".main")
if len(getUses(c.mod.NamedFunction("runtime.startGoroutine"))) != 0 {
// Program needs a scheduler. Start main.main as a goroutine and start
// the scheduler.
realMainWrapper := c.createGoroutineStartWrapper(realMain)
c.builder.SetInsertPointBefore(mainCall)
zero := llvm.ConstInt(c.uintptrType, 0, false)
c.createRuntimeCall("startGoroutine", []llvm.Value{realMainWrapper, zero}, "")
c.createRuntimeCall("scheduler", nil, "")
sleep := c.mod.NamedFunction("time.Sleep")
if !sleep.IsNil() {
sleep.ReplaceAllUsesWith(c.mod.NamedFunction("runtime.sleepCurrentTask"))
}
} else {
// Program doesn't need a scheduler. Call main.main directly.
c.builder.SetInsertPointBefore(mainCall)
params := []llvm.Value{
llvm.Undef(c.i8ptrType), // unused context parameter
llvm.Undef(c.i8ptrType), // unused coroutine handle
}
c.createCall(realMain, params, "")
// runtime.Goexit isn't needed so let it be optimized away by
// globalopt.
c.mod.NamedFunction("runtime.Goexit").SetLinkage(llvm.InternalLinkage)
}
mainCall.EraseFromParentAsInstruction()
// main.main was set to external linkage during IR construction. Set it to
// internal linkage to enable interprocedural optimizations.
realMain.SetLinkage(llvm.InternalLinkage)
return nil
}
// lowerCoroutines transforms the IR into one where all blocking functions are
// turned into goroutines and blocking calls into await calls. It also makes
// sure that the first coroutine is started and the coroutine scheduler will be
// run.
func (c *Compiler) lowerCoroutines() error {
needsScheduler, err := c.markAsyncFunctions()
if err != nil {
return err
}
uses := getUses(c.mod.NamedFunction("runtime.callMain"))
if len(uses) != 1 || uses[0].IsACallInst().IsNil() {
panic("expected exactly 1 call of runtime.callMain, check the entry point")
}
mainCall := uses[0]
// Replace call of runtime.callMain() with a real call to main.main(),
// optionally followed by a call to runtime.scheduler().
c.builder.SetInsertPointBefore(mainCall)
realMain := c.mod.NamedFunction(c.ir.MainPkg().Pkg.Path() + ".main")
c.builder.CreateCall(realMain, []llvm.Value{llvm.Undef(c.i8ptrType), llvm.ConstPointerNull(c.i8ptrType)}, "")
if needsScheduler {
c.createRuntimeCall("scheduler", nil, "")
}
mainCall.EraseFromParentAsInstruction()
if !needsScheduler {
go_scheduler := c.mod.NamedFunction("go_scheduler")
if !go_scheduler.IsNil() {
// This is the WebAssembly backend.
// There is no need to export the go_scheduler function, but it is
// still exported. Make sure it is optimized away.
go_scheduler.SetLinkage(llvm.InternalLinkage)
}
}
// main.main was set to external linkage during IR construction. Set it to
// internal linkage to enable interprocedural optimizations.
realMain.SetLinkage(llvm.InternalLinkage)
return nil
}
// markAsyncFunctions does the bulk of the work of lowering goroutines. It
// determines whether a scheduler is needed, and if it is, it transforms
// blocking operations into goroutines and blocking calls into await calls.
//
// It does the following operations:
// * Find all blocking functions.
// * Determine whether a scheduler is necessary. If not, it skips the
// following operations.
// * Transform call instructions into await calls.
// * Transform return instructions into final suspends.
// * Set up the coroutine frames for async functions.
// * Transform blocking calls into their async equivalents.
func (c *Compiler) markAsyncFunctions() (needsScheduler bool, err error) {
var worklist []llvm.Value
sleep := c.mod.NamedFunction("time.Sleep")
if !sleep.IsNil() {
worklist = append(worklist, sleep)
}
deadlock := c.mod.NamedFunction("runtime.deadlock")
if !deadlock.IsNil() {
worklist = append(worklist, deadlock)
}
chanSend := c.mod.NamedFunction("runtime.chanSend")
if !chanSend.IsNil() {
worklist = append(worklist, chanSend)
}
chanRecv := c.mod.NamedFunction("runtime.chanRecv")
if !chanRecv.IsNil() {
worklist = append(worklist, chanRecv)
}
if len(worklist) == 0 {
// There are no blocking operations, so no need to transform anything.
return false, c.lowerMakeGoroutineCalls()
}
// Find all async functions.
// Keep reducing this worklist by marking a function as recursively async
// from the worklist and pushing all its parents that are non-async.
// This is somewhat similar to a worklist in a mark-sweep garbage collector:
// the work items are then grey objects.
asyncFuncs := make(map[llvm.Value]*asyncFunc)
asyncList := make([]llvm.Value, 0, 4)
for len(worklist) != 0 {
// Pick the topmost.
f := worklist[len(worklist)-1]
worklist = worklist[:len(worklist)-1]
if _, ok := asyncFuncs[f]; ok {
continue // already processed
}
// Add to set of async functions.
asyncFuncs[f] = &asyncFunc{}
asyncList = append(asyncList, f)
// Add all callees to the worklist.
for _, use := range getUses(f) {
if use.IsConstant() && use.Opcode() == llvm.PtrToInt {
for _, call := range getUses(use) {
if call.IsACallInst().IsNil() || call.CalledValue().Name() != "runtime.makeGoroutine" {
return false, errors.New("async function " + f.Name() + " incorrectly used in ptrtoint, expected runtime.makeGoroutine")
}
}
// This is a go statement. Do not mark the parent as async, as
// starting a goroutine is not a blocking operation.
continue
}
if use.IsConstant() && use.Opcode() == llvm.BitCast {
// Not sure why this const bitcast is here but as long as it
// has no uses it can be ignored, I guess?
// I think it was created for the runtime.isnil check but
// somehow wasn't removed when all these checks are removed.
if len(getUses(use)) == 0 {
continue
}
}
if use.IsACallInst().IsNil() {
// Not a call instruction. Maybe a store to a global? In any
// case, this requires support for async calls across function
// pointers which is not yet supported.
return false, errors.New("async function " + f.Name() + " used as function pointer")
}
parent := use.InstructionParent().Parent()
for i := 0; i < use.OperandsCount()-1; i++ {
if use.Operand(i) == f {
return false, errors.New("async function " + f.Name() + " used as function pointer in " + parent.Name())
}
}
worklist = append(worklist, parent)
}
}
// Check whether a scheduler is needed.
makeGoroutine := c.mod.NamedFunction("runtime.makeGoroutine")
if c.GOOS == "js" && strings.HasPrefix(c.Triple, "wasm") {
// JavaScript always needs a scheduler, as in general no blocking
// operations are possible. Blocking operations block the browser UI,
// which is very bad.
needsScheduler = true
} else {
// Only use a scheduler when an async goroutine is started. When the
// goroutine is not async (does not do any blocking operation), no
// scheduler is necessary as it can be called directly.
for _, use := range getUses(makeGoroutine) {
// Input param must be const ptrtoint of function.
ptrtoint := use.Operand(0)
if !ptrtoint.IsConstant() || ptrtoint.Opcode() != llvm.PtrToInt {
panic("expected const ptrtoint operand of runtime.makeGoroutine")
}
goroutine := ptrtoint.Operand(0)
if _, ok := asyncFuncs[goroutine]; ok {
needsScheduler = true
break
}
}
}
if !needsScheduler {
// No scheduler is needed. Do not transform all functions here.
// However, make sure that all go calls (which are all non-async) are
// transformed into regular calls.
return false, c.lowerMakeGoroutineCalls()
}
// Create a few LLVM intrinsics for coroutine support.
coroIdType := llvm.FunctionType(c.ctx.TokenType(), []llvm.Type{c.ctx.Int32Type(), c.i8ptrType, c.i8ptrType, c.i8ptrType}, false)
coroIdFunc := llvm.AddFunction(c.mod, "llvm.coro.id", coroIdType)
coroSizeType := llvm.FunctionType(c.ctx.Int32Type(), nil, false)
coroSizeFunc := llvm.AddFunction(c.mod, "llvm.coro.size.i32", coroSizeType)
coroBeginType := llvm.FunctionType(c.i8ptrType, []llvm.Type{c.ctx.TokenType(), c.i8ptrType}, false)
coroBeginFunc := llvm.AddFunction(c.mod, "llvm.coro.begin", coroBeginType)
coroSuspendType := llvm.FunctionType(c.ctx.Int8Type(), []llvm.Type{c.ctx.TokenType(), c.ctx.Int1Type()}, false)
coroSuspendFunc := llvm.AddFunction(c.mod, "llvm.coro.suspend", coroSuspendType)
coroEndType := llvm.FunctionType(c.ctx.Int1Type(), []llvm.Type{c.i8ptrType, c.ctx.Int1Type()}, false)
coroEndFunc := llvm.AddFunction(c.mod, "llvm.coro.end", coroEndType)
coroFreeType := llvm.FunctionType(c.i8ptrType, []llvm.Type{c.ctx.TokenType(), c.i8ptrType}, false)
coroFreeFunc := llvm.AddFunction(c.mod, "llvm.coro.free", coroFreeType)
// Transform all async functions into coroutines.
for _, f := range asyncList {
if f == sleep || f == deadlock || f == chanSend || f == chanRecv {
continue
}
frame := asyncFuncs[f]
frame.cleanupBlock = c.ctx.AddBasicBlock(f, "task.cleanup")
frame.suspendBlock = c.ctx.AddBasicBlock(f, "task.suspend")
frame.unreachableBlock = c.ctx.AddBasicBlock(f, "task.unreachable")
// Scan for async calls and return instructions that need to have
// suspend points inserted.
var asyncCalls []llvm.Value
var returns []llvm.Value
for bb := f.EntryBasicBlock(); !bb.IsNil(); bb = llvm.NextBasicBlock(bb) {
for inst := bb.FirstInstruction(); !inst.IsNil(); inst = llvm.NextInstruction(inst) {
if !inst.IsACallInst().IsNil() {
callee := inst.CalledValue()
if _, ok := asyncFuncs[callee]; !ok || callee == sleep || callee == deadlock || callee == chanSend || callee == chanRecv {
continue
}
asyncCalls = append(asyncCalls, inst)
} else if !inst.IsAReturnInst().IsNil() {
returns = append(returns, inst)
}
}
}
// Coroutine setup.
c.builder.SetInsertPointBefore(f.EntryBasicBlock().FirstInstruction())
taskState := c.builder.CreateAlloca(c.getLLVMRuntimeType("taskState"), "task.state")
stateI8 := c.builder.CreateBitCast(taskState, c.i8ptrType, "task.state.i8")
id := c.builder.CreateCall(coroIdFunc, []llvm.Value{
llvm.ConstInt(c.ctx.Int32Type(), 0, false),
stateI8,
llvm.ConstNull(c.i8ptrType),
llvm.ConstNull(c.i8ptrType),
}, "task.token")
size := c.builder.CreateCall(coroSizeFunc, nil, "task.size")
if c.targetData.TypeAllocSize(size.Type()) > c.targetData.TypeAllocSize(c.uintptrType) {
size = c.builder.CreateTrunc(size, c.uintptrType, "task.size.uintptr")
} else if c.targetData.TypeAllocSize(size.Type()) < c.targetData.TypeAllocSize(c.uintptrType) {
size = c.builder.CreateZExt(size, c.uintptrType, "task.size.uintptr")
}
data := c.createRuntimeCall("alloc", []llvm.Value{size}, "task.data")
if c.needsStackObjects() {
c.trackPointer(data)
}
frame.taskHandle = c.builder.CreateCall(coroBeginFunc, []llvm.Value{id, data}, "task.handle")
// Modify async calls so this function suspends right after the child
// returns, because the child is probably not finished yet. Wait until
// the child reactivates the parent.
for _, inst := range asyncCalls {
inst.SetOperand(inst.OperandsCount()-2, frame.taskHandle)
// Split this basic block.
await := c.splitBasicBlock(inst, llvm.NextBasicBlock(c.builder.GetInsertBlock()), "task.await")
// Allocate space for the return value.
var retvalAlloca llvm.Value
if inst.Type().TypeKind() != llvm.VoidTypeKind {
c.builder.SetInsertPointBefore(inst.InstructionParent().Parent().EntryBasicBlock().FirstInstruction())
retvalAlloca = c.builder.CreateAlloca(inst.Type(), "coro.retvalAlloca")
c.builder.SetInsertPointBefore(inst)
data := c.builder.CreateBitCast(retvalAlloca, c.i8ptrType, "")
c.createRuntimeCall("setTaskStatePtr", []llvm.Value{frame.taskHandle, data}, "")
}
// Suspend.
c.builder.SetInsertPointAtEnd(inst.InstructionParent())
continuePoint := c.builder.CreateCall(coroSuspendFunc, []llvm.Value{
llvm.ConstNull(c.ctx.TokenType()),
llvm.ConstInt(c.ctx.Int1Type(), 0, false),
}, "")
sw := c.builder.CreateSwitch(continuePoint, frame.suspendBlock, 2)
sw.AddCase(llvm.ConstInt(c.ctx.Int8Type(), 0, false), await)
sw.AddCase(llvm.ConstInt(c.ctx.Int8Type(), 1, false), frame.cleanupBlock)
if inst.Type().TypeKind() != llvm.VoidTypeKind {
// Load the return value from the alloca. The callee has
// written the return value to it.
c.builder.SetInsertPointBefore(await.FirstInstruction())
retval := c.builder.CreateLoad(retvalAlloca, "coro.retval")
inst.ReplaceAllUsesWith(retval)
}
}
// Replace return instructions with suspend points that should
// reactivate the parent coroutine.
for _, inst := range returns {
// These properties were added by the functionattrs pass. Remove
// them, because now we start using the parameter.
// https://llvm.org/docs/Passes.html#functionattrs-deduce-function-attributes
for _, kind := range []string{"nocapture", "readnone"} {
kindID := llvm.AttributeKindID(kind)
f.RemoveEnumAttributeAtIndex(f.ParamsCount(), kindID)
}
c.builder.SetInsertPointBefore(inst)
var parentHandle llvm.Value
if f.Linkage() == llvm.ExternalLinkage {
// Exported function.
// Note that getTaskStatePtr will panic if it is called with
// a nil pointer, so blocking exported functions that try to
// return anything will not work.
parentHandle = llvm.ConstPointerNull(c.i8ptrType)
} else {
parentHandle = f.LastParam()
if parentHandle.IsNil() || parentHandle.Name() != "parentHandle" {
// sanity check
panic("trying to make exported function async: " + f.Name())
}
}
// Store return values.
switch inst.OperandsCount() {
case 0:
// Nothing to return.
case 1:
// Return this value by writing to the pointer stored in the
// parent handle. The parent coroutine has made an alloca that
// we can write to to store our return value.
returnValuePtr := c.createRuntimeCall("getTaskStatePtr", []llvm.Value{parentHandle}, "coro.parentData")
alloca := c.builder.CreateBitCast(returnValuePtr, llvm.PointerType(inst.Operand(0).Type(), 0), "coro.parentAlloca")
c.builder.CreateStore(inst.Operand(0), alloca)
default:
panic("unreachable")
}
// Reactivate the parent coroutine. This adds it back to the run
// queue, so it is started again by the scheduler when possible
// (possibly right after the following suspend).
c.createRuntimeCall("activateTask", []llvm.Value{parentHandle}, "")
// Suspend this coroutine.
// It would look like this is unnecessary, but if this
// suspend point is left out, it leads to undefined
// behavior somehow (with the unreachable instruction).
continuePoint := c.builder.CreateCall(coroSuspendFunc, []llvm.Value{
llvm.ConstNull(c.ctx.TokenType()),
llvm.ConstInt(c.ctx.Int1Type(), 0, false),
}, "ret")
sw := c.builder.CreateSwitch(continuePoint, frame.suspendBlock, 2)
sw.AddCase(llvm.ConstInt(c.ctx.Int8Type(), 0, false), frame.unreachableBlock)
sw.AddCase(llvm.ConstInt(c.ctx.Int8Type(), 1, false), frame.cleanupBlock)
inst.EraseFromParentAsInstruction()
}
// Coroutine cleanup. Free resources associated with this coroutine.
c.builder.SetInsertPointAtEnd(frame.cleanupBlock)
mem := c.builder.CreateCall(coroFreeFunc, []llvm.Value{id, frame.taskHandle}, "task.data.free")
c.createRuntimeCall("free", []llvm.Value{mem}, "")
c.builder.CreateBr(frame.suspendBlock)
// Coroutine suspend. A call to llvm.coro.suspend() will branch here.
c.builder.SetInsertPointAtEnd(frame.suspendBlock)
c.builder.CreateCall(coroEndFunc, []llvm.Value{frame.taskHandle, llvm.ConstInt(c.ctx.Int1Type(), 0, false)}, "unused")
returnType := f.Type().ElementType().ReturnType()
if returnType.TypeKind() == llvm.VoidTypeKind {
c.builder.CreateRetVoid()
} else {
c.builder.CreateRet(llvm.Undef(returnType))
}
// Coroutine exit. All final suspends (return instructions) will branch
// here.
c.builder.SetInsertPointAtEnd(frame.unreachableBlock)
c.builder.CreateUnreachable()
}
// Replace calls to runtime.getCoroutineCall with the coroutine of this
// frame.
for _, getCoroutineCall := range getUses(c.mod.NamedFunction("runtime.getCoroutine")) {
frame := asyncFuncs[getCoroutineCall.InstructionParent().Parent()]
getCoroutineCall.ReplaceAllUsesWith(frame.taskHandle)
getCoroutineCall.EraseFromParentAsInstruction()
}
// Transform calls to time.Sleep() into coroutine suspend points.
for _, sleepCall := range getUses(sleep) {
// sleepCall must be a call instruction.
frame := asyncFuncs[sleepCall.InstructionParent().Parent()]
duration := sleepCall.Operand(0)
// Set task state to TASK_STATE_SLEEP and set the duration.
c.builder.SetInsertPointBefore(sleepCall)
c.createRuntimeCall("sleepTask", []llvm.Value{frame.taskHandle, duration}, "")
// Yield to scheduler.
continuePoint := c.builder.CreateCall(coroSuspendFunc, []llvm.Value{
llvm.ConstNull(c.ctx.TokenType()),
llvm.ConstInt(c.ctx.Int1Type(), 0, false),
}, "")
wakeup := c.splitBasicBlock(sleepCall, llvm.NextBasicBlock(c.builder.GetInsertBlock()), "task.wakeup")
c.builder.SetInsertPointBefore(sleepCall)
sw := c.builder.CreateSwitch(continuePoint, frame.suspendBlock, 2)
sw.AddCase(llvm.ConstInt(c.ctx.Int8Type(), 0, false), wakeup)
sw.AddCase(llvm.ConstInt(c.ctx.Int8Type(), 1, false), frame.cleanupBlock)
sleepCall.EraseFromParentAsInstruction()
}
// Transform calls to runtime.deadlock into coroutine suspends (without
// resume).
for _, deadlockCall := range getUses(deadlock) {
// deadlockCall must be a call instruction.
frame := asyncFuncs[deadlockCall.InstructionParent().Parent()]
// Exit coroutine.
c.builder.SetInsertPointBefore(deadlockCall)
continuePoint := c.builder.CreateCall(coroSuspendFunc, []llvm.Value{
llvm.ConstNull(c.ctx.TokenType()),
llvm.ConstInt(c.ctx.Int1Type(), 0, false),
}, "")
c.splitBasicBlock(deadlockCall, llvm.NextBasicBlock(c.builder.GetInsertBlock()), "task.wakeup.dead")
c.builder.SetInsertPointBefore(deadlockCall)
sw := c.builder.CreateSwitch(continuePoint, frame.suspendBlock, 2)
sw.AddCase(llvm.ConstInt(c.ctx.Int8Type(), 0, false), frame.unreachableBlock)
sw.AddCase(llvm.ConstInt(c.ctx.Int8Type(), 1, false), frame.cleanupBlock)
deadlockCall.EraseFromParentAsInstruction()
}
// Transform calls to runtime.chanSend into channel send operations.
for _, sendOp := range getUses(chanSend) {
// sendOp must be a call instruction.
frame := asyncFuncs[sendOp.InstructionParent().Parent()]
// Yield to scheduler.
c.builder.SetInsertPointBefore(llvm.NextInstruction(sendOp))
continuePoint := c.builder.CreateCall(coroSuspendFunc, []llvm.Value{
llvm.ConstNull(c.ctx.TokenType()),
llvm.ConstInt(c.ctx.Int1Type(), 0, false),
}, "")
sw := c.builder.CreateSwitch(continuePoint, frame.suspendBlock, 2)
wakeup := c.splitBasicBlock(sw, llvm.NextBasicBlock(c.builder.GetInsertBlock()), "task.sent")
sw.AddCase(llvm.ConstInt(c.ctx.Int8Type(), 0, false), wakeup)
sw.AddCase(llvm.ConstInt(c.ctx.Int8Type(), 1, false), frame.cleanupBlock)
}
// Transform calls to runtime.chanRecv into channel receive operations.
for _, recvOp := range getUses(chanRecv) {
// recvOp must be a call instruction.
frame := asyncFuncs[recvOp.InstructionParent().Parent()]
// Yield to scheduler.
c.builder.SetInsertPointBefore(llvm.NextInstruction(recvOp))
continuePoint := c.builder.CreateCall(coroSuspendFunc, []llvm.Value{
llvm.ConstNull(c.ctx.TokenType()),
llvm.ConstInt(c.ctx.Int1Type(), 0, false),
}, "")
sw := c.builder.CreateSwitch(continuePoint, frame.suspendBlock, 2)
wakeup := c.splitBasicBlock(sw, llvm.NextBasicBlock(c.builder.GetInsertBlock()), "task.received")
c.builder.SetInsertPointAtEnd(recvOp.InstructionParent())
sw.AddCase(llvm.ConstInt(c.ctx.Int8Type(), 0, false), wakeup)
sw.AddCase(llvm.ConstInt(c.ctx.Int8Type(), 1, false), frame.cleanupBlock)
}
return true, c.lowerMakeGoroutineCalls()
}
// Lower runtime.makeGoroutine calls to regular call instructions. This is done
// after the regular goroutine transformations. The started goroutines are
// either non-blocking (in which case they can be called directly) or blocking,
// in which case they will ask the scheduler themselves to be rescheduled.
func (c *Compiler) lowerMakeGoroutineCalls() error {
// The following Go code:
// go startedGoroutine()
//
// Is translated to the following during IR construction, to preserve the
// fact that this function should be called as a new goroutine.
// %0 = call i8* @runtime.makeGoroutine(i8* bitcast (void (i8*, i8*)* @main.startedGoroutine to i8*), i8* undef, i8* null)
// %1 = bitcast i8* %0 to void (i8*, i8*)*
// call void %1(i8* undef, i8* undef)
//
// This function rewrites it to a direct call:
// call void @main.startedGoroutine(i8* undef, i8* null)
makeGoroutine := c.mod.NamedFunction("runtime.makeGoroutine")
for _, goroutine := range getUses(makeGoroutine) {
ptrtointIn := goroutine.Operand(0)
origFunc := ptrtointIn.Operand(0)
uses := getUses(goroutine)
if len(uses) != 1 || uses[0].IsAIntToPtrInst().IsNil() {
return errors.New("expected exactly 1 inttoptr use of runtime.makeGoroutine")
}
inttoptrOut := uses[0]
uses = getUses(inttoptrOut)
if len(uses) != 1 || uses[0].IsACallInst().IsNil() {
return errors.New("expected exactly 1 call use of runtime.makeGoroutine bitcast")
}
realCall := uses[0]
// Create call instruction.
var params []llvm.Value
for i := 0; i < realCall.OperandsCount()-1; i++ {
params = append(params, realCall.Operand(i))
}
params[len(params)-1] = llvm.ConstPointerNull(c.i8ptrType) // parent coroutine handle (must be nil)
c.builder.SetInsertPointBefore(realCall)
c.builder.CreateCall(origFunc, params, "")
realCall.EraseFromParentAsInstruction()
inttoptrOut.EraseFromParentAsInstruction()
goroutine.EraseFromParentAsInstruction()
}
return nil
}
+37 -77
View File
@@ -3,32 +3,31 @@ package compiler
// This file implements the 'go' keyword to start a new goroutine. See
// goroutine-lowering.go for more details.
import (
"go/token"
import "tinygo.org/x/go-llvm"
"github.com/tinygo-org/tinygo/compiler/llvmutil"
"tinygo.org/x/go-llvm"
)
// createGoInstruction starts a new goroutine with the provided function pointer
// emitStartGoroutine starts a new goroutine with the provided function pointer
// and parameters.
// In general, you should pass all regular parameters plus the context parameter.
// There is one exception: the task-based scheduler needs to have the function
// pointer passed in as a parameter too in addition to the context.
//
// Because a go statement doesn't return anything, return undef.
func (b *builder) createGoInstruction(funcPtr llvm.Value, params []llvm.Value, prefix string, pos token.Pos) llvm.Value {
paramBundle := b.emitPointerPack(params)
var callee llvm.Value
switch b.Scheduler() {
case "none", "tasks":
callee = b.createGoroutineStartWrapper(funcPtr, prefix, pos)
func (c *Compiler) emitStartGoroutine(funcPtr llvm.Value, params []llvm.Value) llvm.Value {
switch c.selectScheduler() {
case "tasks":
paramBundle := c.emitPointerPack(params)
paramBundle = c.builder.CreatePtrToInt(paramBundle, c.uintptrType, "")
calleeValue := c.createGoroutineStartWrapper(funcPtr)
c.createRuntimeCall("startGoroutine", []llvm.Value{calleeValue, paramBundle}, "")
case "coroutines":
callee = b.CreatePtrToInt(funcPtr, b.uintptrType, "")
// We roundtrip through runtime.makeGoroutine as a signal (to find these
// calls) and to break any optimizations LLVM will try to do: they are
// invalid if we called this as a regular function to be updated later.
calleeValue := c.builder.CreatePtrToInt(funcPtr, c.uintptrType, "")
calleeValue = c.createRuntimeCall("makeGoroutine", []llvm.Value{calleeValue}, "")
calleeValue = c.builder.CreateIntToPtr(calleeValue, funcPtr.Type(), "")
c.createCall(calleeValue, params, "")
default:
panic("unreachable")
}
b.createCall(b.mod.NamedFunction("internal/task.start"), []llvm.Value{callee, paramBundle, llvm.Undef(b.i8ptrType), llvm.ConstPointerNull(b.i8ptrType)}, "")
return llvm.Undef(funcPtr.Type().ElementType().ReturnType())
}
@@ -50,57 +49,36 @@ func (b *builder) createGoInstruction(funcPtr llvm.Value, params []llvm.Value, p
// allows a single (pointer) argument to the newly started goroutine. Also, it
// ignores the return value because newly started goroutines do not have a
// return value.
func (c *compilerContext) createGoroutineStartWrapper(fn llvm.Value, prefix string, pos token.Pos) llvm.Value {
func (c *Compiler) createGoroutineStartWrapper(fn llvm.Value) llvm.Value {
var wrapper llvm.Value
builder := c.ctx.NewBuilder()
defer builder.Dispose()
if !fn.IsAFunction().IsNil() {
// See whether this wrapper has already been created. If so, return it.
name := fn.Name()
wrapper = c.mod.NamedFunction(name + "$gowrapper")
if !wrapper.IsNil() {
return llvm.ConstPtrToInt(wrapper, c.uintptrType)
return c.builder.CreatePtrToInt(wrapper, c.uintptrType, "")
}
// Save the current position in the IR builder.
currentBlock := c.builder.GetInsertBlock()
defer c.builder.SetInsertPointAtEnd(currentBlock)
// Create the wrapper.
wrapperType := llvm.FunctionType(c.ctx.VoidType(), []llvm.Type{c.i8ptrType}, false)
wrapper = llvm.AddFunction(c.mod, name+"$gowrapper", wrapperType)
wrapper.SetLinkage(llvm.PrivateLinkage)
wrapper.SetUnnamedAddr(true)
entry := c.ctx.AddBasicBlock(wrapper, "entry")
builder.SetInsertPointAtEnd(entry)
if c.Debug() {
pos := c.ir.Program.Fset.Position(pos)
diFuncType := c.dibuilder.CreateSubroutineType(llvm.DISubroutineType{
File: c.getDIFile(pos.Filename),
Parameters: nil, // do not show parameters in debugger
Flags: 0, // ?
})
difunc := c.dibuilder.CreateFunction(c.getDIFile(pos.Filename), llvm.DIFunction{
Name: "<goroutine wrapper>",
File: c.getDIFile(pos.Filename),
Line: pos.Line,
Type: diFuncType,
LocalToUnit: true,
IsDefinition: true,
ScopeLine: 0,
Flags: llvm.FlagPrototyped,
Optimized: true,
})
wrapper.SetSubprogram(difunc)
builder.SetCurrentDebugLocation(uint(pos.Line), uint(pos.Column), difunc, llvm.Metadata{})
}
c.builder.SetInsertPointAtEnd(entry)
// Create the list of params for the call.
paramTypes := fn.Type().ElementType().ParamTypes()
params := llvmutil.EmitPointerUnpack(builder, c.mod, wrapper.Param(0), paramTypes[:len(paramTypes)-1])
params = append(params, llvm.Undef(c.i8ptrType))
params := c.emitPointerUnpack(wrapper.Param(0), paramTypes[:len(paramTypes)-2])
params = append(params, llvm.Undef(c.i8ptrType), llvm.ConstPointerNull(c.i8ptrType))
// Create the call.
builder.CreateCall(fn, params, "")
c.builder.CreateCall(fn, params, "")
} else {
// For a function pointer like this:
@@ -120,40 +98,22 @@ func (c *compilerContext) createGoroutineStartWrapper(fn llvm.Value, prefix stri
// With a bit of luck, identical wrapper functions like these can be
// merged into one.
// Save the current position in the IR builder.
currentBlock := c.builder.GetInsertBlock()
defer c.builder.SetInsertPointAtEnd(currentBlock)
// Create the wrapper.
wrapperType := llvm.FunctionType(c.ctx.VoidType(), []llvm.Type{c.i8ptrType}, false)
wrapper = llvm.AddFunction(c.mod, prefix+".gowrapper", wrapperType)
wrapper = llvm.AddFunction(c.mod, ".gowrapper", wrapperType)
wrapper.SetLinkage(llvm.InternalLinkage)
wrapper.SetUnnamedAddr(true)
entry := c.ctx.AddBasicBlock(wrapper, "entry")
builder.SetInsertPointAtEnd(entry)
if c.Debug() {
pos := c.ir.Program.Fset.Position(pos)
diFuncType := c.dibuilder.CreateSubroutineType(llvm.DISubroutineType{
File: c.getDIFile(pos.Filename),
Parameters: nil, // do not show parameters in debugger
Flags: 0, // ?
})
difunc := c.dibuilder.CreateFunction(c.getDIFile(pos.Filename), llvm.DIFunction{
Name: "<goroutine wrapper>",
File: c.getDIFile(pos.Filename),
Line: pos.Line,
Type: diFuncType,
LocalToUnit: true,
IsDefinition: true,
ScopeLine: 0,
Flags: llvm.FlagPrototyped,
Optimized: true,
})
wrapper.SetSubprogram(difunc)
builder.SetCurrentDebugLocation(uint(pos.Line), uint(pos.Column), difunc, llvm.Metadata{})
}
c.builder.SetInsertPointAtEnd(entry)
// Get the list of parameters, with the extra parameters at the end.
paramTypes := fn.Type().ElementType().ParamTypes()
paramTypes[len(paramTypes)-1] = fn.Type() // the last element is the function pointer
params := llvmutil.EmitPointerUnpack(builder, c.mod, wrapper.Param(0), paramTypes)
params := c.emitPointerUnpack(wrapper.Param(0), paramTypes)
// Get the function pointer.
fnPtr := params[len(params)-1]
@@ -163,13 +123,13 @@ func (c *compilerContext) createGoroutineStartWrapper(fn llvm.Value, prefix stri
params[len(params)-1] = llvm.Undef(c.i8ptrType)
// Create the call.
builder.CreateCall(fnPtr, params, "")
c.builder.CreateCall(fnPtr, params, "")
}
// Finish the function. Every basic block must end in a terminator, and
// because goroutines never return a value we can simply return void.
builder.CreateRetVoid()
c.builder.CreateRetVoid()
// Return a ptrtoint of the wrapper, not the function itself.
return builder.CreatePtrToInt(wrapper, c.uintptrType, "")
return c.builder.CreatePtrToInt(wrapper, c.uintptrType, "")
}
+51 -96
View File
@@ -13,6 +13,27 @@ import (
"tinygo.org/x/go-llvm"
)
// This is a compiler builtin, which reads the given register by name:
//
// func ReadRegister(name string) uintptr
//
// The register name must be a constant, for example "sp".
func (c *Compiler) emitReadRegister(name string, args []ssa.Value) (llvm.Value, error) {
fnType := llvm.FunctionType(c.uintptrType, []llvm.Type{}, false)
regname := constant.StringVal(args[0].(*ssa.Const).Value)
var asm string
switch name {
case "device/arm.ReadRegister":
asm = "mov $0, " + regname
case "device/riscv.ReadRegister":
asm = "mv $0, " + regname
default:
panic("unknown architecture")
}
target := llvm.InlineAsm(fnType, asm, "=r", false, false, 0)
return c.builder.CreateCall(target, nil, ""), nil
}
// This is a compiler builtin, which emits a piece of inline assembly with no
// operands or return values. It is useful for trivial instructions, like wfi in
// ARM or sleep in AVR.
@@ -20,18 +41,18 @@ import (
// func Asm(asm string)
//
// The provided assembly must be a constant.
func (b *builder) createInlineAsm(args []ssa.Value) (llvm.Value, error) {
func (c *Compiler) emitAsm(args []ssa.Value) (llvm.Value, error) {
// Magic function: insert inline assembly instead of calling it.
fnType := llvm.FunctionType(b.ctx.VoidType(), []llvm.Type{}, false)
fnType := llvm.FunctionType(c.ctx.VoidType(), []llvm.Type{}, false)
asm := constant.StringVal(args[0].(*ssa.Const).Value)
target := llvm.InlineAsm(fnType, asm, "", true, false, 0)
return b.CreateCall(target, nil, ""), nil
return c.builder.CreateCall(target, nil, ""), nil
}
// This is a compiler builtin, which allows assembly to be called in a flexible
// way.
//
// func AsmFull(asm string, regs map[string]interface{}) uintptr
// func AsmFull(asm string, regs map[string]interface{})
//
// The asm parameter must be a constant string. The regs parameter must be
// provided immediately. For example:
@@ -42,27 +63,27 @@ func (b *builder) createInlineAsm(args []ssa.Value) (llvm.Value, error) {
// "value": 1
// "result": &dest,
// })
func (b *builder) createInlineAsmFull(instr *ssa.CallCommon) (llvm.Value, error) {
func (c *Compiler) emitAsmFull(frame *Frame, instr *ssa.CallCommon) (llvm.Value, error) {
asmString := constant.StringVal(instr.Args[0].(*ssa.Const).Value)
registers := map[string]llvm.Value{}
if registerMap, ok := instr.Args[1].(*ssa.MakeMap); ok {
for _, r := range *registerMap.Referrers() {
switch r := r.(type) {
case *ssa.DebugRef:
// ignore
case *ssa.MapUpdate:
if r.Block() != registerMap.Block() {
return llvm.Value{}, b.makeError(instr.Pos(), "register value map must be created in the same basic block")
}
key := constant.StringVal(r.Key.(*ssa.Const).Value)
registers[key] = b.getValue(r.Value.(*ssa.MakeInterface).X)
case *ssa.Call:
if r.Common() == instr {
break
}
default:
return llvm.Value{}, b.makeError(instr.Pos(), "don't know how to handle argument to inline assembly: "+r.String())
registerMap := instr.Args[1].(*ssa.MakeMap)
for _, r := range *registerMap.Referrers() {
switch r := r.(type) {
case *ssa.DebugRef:
// ignore
case *ssa.MapUpdate:
if r.Block() != registerMap.Block() {
return llvm.Value{}, c.makeError(instr.Pos(), "register value map must be created in the same basic block")
}
key := constant.StringVal(r.Key.(*ssa.Const).Value)
//println("value:", r.Value.(*ssa.MakeInterface).X.String())
registers[key] = c.getValue(frame, r.Value.(*ssa.MakeInterface).X)
case *ssa.Call:
if r.Common() == instr {
break
}
default:
return llvm.Value{}, c.makeError(instr.Pos(), "don't know how to handle argument to inline assembly: "+r.String())
}
}
// TODO: handle dollar signs in asm string
@@ -71,22 +92,13 @@ func (b *builder) createInlineAsmFull(instr *ssa.CallCommon) (llvm.Value, error)
argTypes := []llvm.Type{}
args := []llvm.Value{}
constraints := []string{}
hasOutput := false
asmString = regexp.MustCompile("\\{\\}").ReplaceAllStringFunc(asmString, func(s string) string {
hasOutput = true
return "$0"
})
if hasOutput {
constraints = append(constraints, "=&r")
registerNumbers[""] = 0
}
asmString = regexp.MustCompile("\\{[a-zA-Z]+\\}").ReplaceAllStringFunc(asmString, func(s string) string {
// TODO: skip strings like {r4} etc. that look like ARM push/pop
// instructions.
name := s[1 : len(s)-1]
if _, ok := registers[name]; !ok {
if err == nil {
err = b.makeError(instr.Pos(), "unknown register name: "+name)
err = c.makeError(instr.Pos(), "unknown register name: "+name)
}
return s
}
@@ -100,7 +112,7 @@ func (b *builder) createInlineAsmFull(instr *ssa.CallCommon) (llvm.Value, error)
case llvm.PointerTypeKind:
constraints = append(constraints, "*m")
default:
err = b.makeError(instr.Pos(), "unknown type in inline assembly for value: "+name)
err = c.makeError(instr.Pos(), "unknown type in inline assembly for value: "+name)
return s
}
}
@@ -109,21 +121,9 @@ func (b *builder) createInlineAsmFull(instr *ssa.CallCommon) (llvm.Value, error)
if err != nil {
return llvm.Value{}, err
}
var outputType llvm.Type
if hasOutput {
outputType = b.uintptrType
} else {
outputType = b.ctx.VoidType()
}
fnType := llvm.FunctionType(outputType, argTypes, false)
fnType := llvm.FunctionType(c.ctx.VoidType(), argTypes, false)
target := llvm.InlineAsm(fnType, asmString, strings.Join(constraints, ","), true, false, 0)
result := b.CreateCall(target, args, "")
if hasOutput {
return result, nil
} else {
// Make sure we return something valid.
return llvm.ConstInt(b.uintptrType, 0, false), nil
}
return c.builder.CreateCall(target, args, ""), nil
}
// This is a compiler builtin which emits an inline SVCall instruction. It can
@@ -137,7 +137,7 @@ func (b *builder) createInlineAsmFull(instr *ssa.CallCommon) (llvm.Value, error)
//
// The num parameter must be a constant. All other parameters may be any scalar
// value supported by LLVM inline assembly.
func (b *builder) emitSVCall(args []ssa.Value) (llvm.Value, error) {
func (c *Compiler) emitSVCall(frame *Frame, args []ssa.Value) (llvm.Value, error) {
num, _ := constant.Uint64Val(args[0].(*ssa.Const).Value)
llvmArgs := []llvm.Value{}
argTypes := []llvm.Type{}
@@ -150,7 +150,7 @@ func (b *builder) emitSVCall(args []ssa.Value) (llvm.Value, error) {
} else {
constraints += ",{r" + strconv.Itoa(i) + "}"
}
llvmValue := b.getValue(arg)
llvmValue := c.getValue(frame, arg)
llvmArgs = append(llvmArgs, llvmValue)
argTypes = append(argTypes, llvmValue.Type())
}
@@ -158,52 +158,7 @@ func (b *builder) emitSVCall(args []ssa.Value) (llvm.Value, error) {
// clobbered. r0 is used as an output register so doesn't have to be
// marked as clobbered.
constraints += ",~{r1},~{r2},~{r3}"
fnType := llvm.FunctionType(b.uintptrType, argTypes, false)
fnType := llvm.FunctionType(c.uintptrType, argTypes, false)
target := llvm.InlineAsm(fnType, asm, constraints, true, false, 0)
return b.CreateCall(target, llvmArgs, ""), nil
}
// This is a compiler builtin which emits CSR instructions. It can be one of:
//
// func (csr CSR) Get() uintptr
// func (csr CSR) Set(uintptr)
// func (csr CSR) SetBits(uintptr) uintptr
// func (csr CSR) ClearBits(uintptr) uintptr
//
// The csr parameter (method receiver) must be a constant. Other parameter can
// be any value.
func (b *builder) emitCSROperation(call *ssa.CallCommon) (llvm.Value, error) {
csrConst, ok := call.Args[0].(*ssa.Const)
if !ok {
return llvm.Value{}, b.makeError(call.Pos(), "CSR must be constant")
}
csr := csrConst.Uint64()
switch name := call.StaticCallee().Name(); name {
case "Get":
// Note that this instruction may have side effects, and thus must be
// marked as such.
fnType := llvm.FunctionType(b.uintptrType, nil, false)
asm := fmt.Sprintf("csrr $0, %d", csr)
target := llvm.InlineAsm(fnType, asm, "=r", true, false, 0)
return b.CreateCall(target, nil, ""), nil
case "Set":
fnType := llvm.FunctionType(b.ctx.VoidType(), []llvm.Type{b.uintptrType}, false)
asm := fmt.Sprintf("csrw %d, $0", csr)
target := llvm.InlineAsm(fnType, asm, "r", true, false, 0)
return b.CreateCall(target, []llvm.Value{b.getValue(call.Args[1])}, ""), nil
case "SetBits":
// Note: it may be possible to optimize this to csrrsi in many cases.
fnType := llvm.FunctionType(b.uintptrType, []llvm.Type{b.uintptrType}, false)
asm := fmt.Sprintf("csrrs $0, %d, $1", csr)
target := llvm.InlineAsm(fnType, asm, "=r,r", true, false, 0)
return b.CreateCall(target, []llvm.Value{b.getValue(call.Args[1])}, ""), nil
case "ClearBits":
// Note: it may be possible to optimize this to csrrci in many cases.
fnType := llvm.FunctionType(b.uintptrType, []llvm.Type{b.uintptrType}, false)
asm := fmt.Sprintf("csrrc $0, %d, $1", csr)
target := llvm.InlineAsm(fnType, asm, "=r,r", true, false, 0)
return b.CreateCall(target, []llvm.Value{b.getValue(call.Args[1])}, ""), nil
default:
return llvm.Value{}, b.makeError(call.Pos(), "unknown CSR operation: "+name)
}
return c.builder.CreateCall(target, llvmArgs, ""), nil
}
@@ -1,4 +1,4 @@
package transform
package compiler
// This file provides function to lower interface intrinsics to their final LLVM
// form, optimizing them in the process.
@@ -17,10 +17,17 @@ package transform
// Replaced with an icmp instruction so it can be directly used in a type
// switch. This is very easy to optimize for LLVM: it will often translate a
// type switch into a regular switch statement.
// When this type assert is not possible (the type is never used in an
// interface), this call is replaced with a constant false to optimize the
// type assert away completely.
//
// interfaceImplements:
// This call is translated into a call that checks whether the underlying
// type is one of the types implementing this interface.
// When there is only one type implementing this interface, the check is
// replaced with a simple icmp instruction, just like a type assert.
// When there is no type at all that implements this interface, it is
// replaced with a constant false to optimize it completely.
//
// interfaceMethod:
// This call is replaced with a call to a function that calls the
@@ -38,7 +45,6 @@ import (
"sort"
"strings"
"github.com/tinygo-org/tinygo/compiler/llvmutil"
"tinygo.org/x/go-llvm"
)
@@ -134,37 +140,31 @@ func (itf *interfaceInfo) id() string {
// pass has been implemented as an object type because of its complexity, but
// should be seen as a regular function call (see LowerInterfaces).
type lowerInterfacesPass struct {
mod llvm.Module
builder llvm.Builder
ctx llvm.Context
uintptrType llvm.Type
types map[string]*typeInfo
signatures map[string]*signatureInfo
interfaces map[string]*interfaceInfo
*Compiler
types map[string]*typeInfo
signatures map[string]*signatureInfo
interfaces map[string]*interfaceInfo
}
// LowerInterfaces lowers all intermediate interface calls and globals that are
// emitted by the compiler as higher-level intrinsics. They need some lowering
// before LLVM can work on them. This is done so that a few cleanup passes can
// run before assigning the final type codes.
func LowerInterfaces(mod llvm.Module) error {
// Lower all interface functions. They are emitted by the compiler as
// higher-level intrinsics that need some lowering before LLVM can work on them.
// This is done so that a few cleanup passes can run before assigning the final
// type codes.
func (c *Compiler) LowerInterfaces() {
p := &lowerInterfacesPass{
mod: mod,
builder: mod.Context().NewBuilder(),
ctx: mod.Context(),
uintptrType: mod.Context().IntType(llvm.NewTargetData(mod.DataLayout()).PointerSize() * 8),
types: make(map[string]*typeInfo),
signatures: make(map[string]*signatureInfo),
interfaces: make(map[string]*interfaceInfo),
Compiler: c,
types: make(map[string]*typeInfo),
signatures: make(map[string]*signatureInfo),
interfaces: make(map[string]*interfaceInfo),
}
return p.run()
p.run()
}
// run runs the pass itself.
func (p *lowerInterfacesPass) run() error {
func (p *lowerInterfacesPass) run() {
// Collect all type codes.
typecodeIDPtr := llvm.PointerType(p.mod.GetTypeByName("runtime.typecodeID"), 0)
typeInInterfacePtr := llvm.PointerType(p.mod.GetTypeByName("runtime.typeInInterface"), 0)
typecodeIDPtr := llvm.PointerType(p.getLLVMRuntimeType("typecodeID"), 0)
typeInInterfacePtr := llvm.PointerType(p.getLLVMRuntimeType("typeInInterface"), 0)
var typesInInterfaces []llvm.Value
for global := p.mod.FirstGlobal(); !global.IsNil(); global = llvm.NextGlobal(global) {
switch global.Type() {
@@ -303,44 +303,44 @@ func (p *lowerInterfacesPass) run() error {
} else if len(itf.types) == 1 {
// There is only one implementation of the given type.
// Call that function directly.
err := p.replaceInvokeWithCall(use, itf.types[0], signature)
if err != nil {
return err
}
p.replaceInvokeWithCall(use, itf.types[0], signature)
} else {
// There are multiple types implementing this interface, thus there
// are multiple possible functions to call. Delegate calling the
// right function to a special wrapper function.
inttoptrs := getUses(use)
if len(inttoptrs) != 1 || inttoptrs[0].IsAIntToPtrInst().IsNil() {
return errorAt(use, "internal error: expected exactly one inttoptr use of runtime.interfaceMethod")
panic("expected exactly one inttoptr use of runtime.interfaceMethod")
}
inttoptr := inttoptrs[0]
calls := getUses(inttoptr)
for _, call := range calls {
// Set up parameters for the call. First copy the regular params...
params := make([]llvm.Value, call.OperandsCount())
paramTypes := make([]llvm.Type, len(params))
for i := 0; i < len(params)-1; i++ {
params[i] = call.Operand(i)
paramTypes[i] = params[i].Type()
}
// then add the typecode to the end of the list.
params[len(params)-1] = typecode
paramTypes[len(params)-1] = p.uintptrType
// Create a function that redirects the call to the destination
// call, after selecting the right concrete type.
redirector := p.getInterfaceMethodFunc(itf, signature, call.Type(), paramTypes)
// Replace the old lookup/inttoptr/call with the new call.
p.builder.SetInsertPointBefore(call)
retval := p.builder.CreateCall(redirector, append(params, llvm.ConstNull(llvm.PointerType(p.ctx.Int8Type(), 0))), "")
if retval.Type().TypeKind() != llvm.VoidTypeKind {
call.ReplaceAllUsesWith(retval)
}
call.EraseFromParentAsInstruction()
if len(calls) != 1 || calls[0].IsACallInst().IsNil() {
panic("expected exactly one call use of runtime.interfaceMethod")
}
call := calls[0]
// Set up parameters for the call. First copy the regular params...
params := make([]llvm.Value, call.OperandsCount())
paramTypes := make([]llvm.Type, len(params))
for i := 0; i < len(params)-1; i++ {
params[i] = call.Operand(i)
paramTypes[i] = params[i].Type()
}
// then add the typecode to the end of the list.
params[len(params)-1] = typecode
paramTypes[len(params)-1] = p.uintptrType
// Create a function that redirects the call to the destination
// call, after selecting the right concrete type.
redirector := p.getInterfaceMethodFunc(itf, signature, call.Type(), paramTypes)
// Replace the old lookup/inttoptr/call with the new call.
p.builder.SetInsertPointBefore(call)
retval := p.builder.CreateCall(redirector, params, "")
if retval.Type().TypeKind() != llvm.VoidTypeKind {
call.ReplaceAllUsesWith(retval)
}
call.EraseFromParentAsInstruction()
inttoptr.EraseFromParentAsInstruction()
use.EraseFromParentAsInstruction()
}
@@ -353,13 +353,32 @@ func (p *lowerInterfacesPass) run() error {
methodSet := use.Operand(1).Operand(0) // global variable
itf := p.interfaces[methodSet.Name()]
// Create a function that does a type switch on all available types
// that implement this interface.
fn := p.getInterfaceImplementsFunc(itf)
p.builder.SetInsertPointBefore(use)
commaOk := p.builder.CreateCall(fn, []llvm.Value{actualType}, "typeassert.ok")
use.ReplaceAllUsesWith(commaOk)
use.EraseFromParentAsInstruction()
if len(itf.types) == 0 {
// There are no types implementing this interface, so this assert
// can never succeed.
// Signal this to the optimizer by branching on constant false. It
// should remove the "then" block.
use.ReplaceAllUsesWith(llvm.ConstInt(p.ctx.Int1Type(), 0, false))
use.EraseFromParentAsInstruction()
} else if len(itf.types) == 1 {
// There is only one type implementing this interface.
// Transform this interface assert into comparison against a
// constant.
p.builder.SetInsertPointBefore(use)
assertedType := p.builder.CreatePtrToInt(itf.types[0].typecode, p.uintptrType, "typeassert.typecode")
commaOk := p.builder.CreateICmp(llvm.IntEQ, assertedType, actualType, "typeassert.ok")
use.ReplaceAllUsesWith(commaOk)
use.EraseFromParentAsInstruction()
} else {
// There are multiple possible types implementing this interface.
// Create a function that does a type switch on all available types
// that implement this interface.
fn := p.getInterfaceImplementsFunc(itf)
p.builder.SetInsertPointBefore(use)
commaOk := p.builder.CreateCall(fn, []llvm.Value{actualType}, "typeassert.ok")
use.ReplaceAllUsesWith(commaOk)
use.EraseFromParentAsInstruction()
}
}
// Make a slice of types sorted by frequency of use.
@@ -369,8 +388,13 @@ func (p *lowerInterfacesPass) run() error {
}
sort.Sort(sort.Reverse(typeSlice))
// A type code must fit in 16 bits.
if len(typeSlice) >= 1<<16 {
panic("typecode does not fit in a uint16: too many types in this program")
}
// Assign a type code for each type.
assignTypeCodes(p.mod, typeSlice)
p.assignTypeCodes(typeSlice)
// Replace each use of a runtime.typeInInterface with the constant type
// code.
@@ -387,8 +411,16 @@ func (p *lowerInterfacesPass) run() error {
for _, use := range typeAssertUses {
actualType := use.Operand(0)
assertedTypeGlobal := use.Operand(1)
p.builder.SetInsertPointBefore(use)
commaOk := p.builder.CreateICmp(llvm.IntEQ, llvm.ConstPtrToInt(assertedTypeGlobal, p.uintptrType), actualType, "typeassert.ok")
t := p.types[assertedTypeGlobal.Name()]
var commaOk llvm.Value
if t.countMakeInterfaces == 0 {
// impossible type assert: optimize accordingly
commaOk = llvm.ConstInt(p.ctx.Int1Type(), 0, false)
} else {
// regular type assert
p.builder.SetInsertPointBefore(use)
commaOk = p.builder.CreateICmp(llvm.IntEQ, llvm.ConstPtrToInt(assertedTypeGlobal, p.uintptrType), actualType, "typeassert.ok")
}
use.ReplaceAllUsesWith(commaOk)
use.EraseFromParentAsInstruction()
}
@@ -428,7 +460,6 @@ func (p *lowerInterfacesPass) run() error {
typ.methodSet = llvm.Value{}
}
}
return nil
}
// addTypeMethods reads the method set of the given type info struct. It
@@ -489,10 +520,10 @@ func (p *lowerInterfacesPass) getSignature(name string) *signatureInfo {
// replaceInvokeWithCall replaces a runtime.interfaceMethod + inttoptr with a
// concrete method. This can be done when only one type implements the
// interface.
func (p *lowerInterfacesPass) replaceInvokeWithCall(use llvm.Value, typ *typeInfo, signature *signatureInfo) error {
func (p *lowerInterfacesPass) replaceInvokeWithCall(use llvm.Value, typ *typeInfo, signature *signatureInfo) {
inttoptrs := getUses(use)
if len(inttoptrs) != 1 || inttoptrs[0].IsAIntToPtrInst().IsNil() {
return errorAt(use, "internal error: expected exactly one inttoptr use of runtime.interfaceMethod")
panic("expected exactly one inttoptr use of runtime.interfaceMethod")
}
inttoptr := inttoptrs[0]
function := typ.getMethod(signature).function
@@ -507,7 +538,7 @@ func (p *lowerInterfacesPass) replaceInvokeWithCall(use llvm.Value, typ *typeInf
// function.
for _, call := range getUses(inttoptr) {
if call.IsACallInst().IsNil() || call.CalledValue() != inttoptr {
return errorAt(call, "internal error: expected the inttoptr to be called as a method, this is not a method call")
panic("expected the inttoptr to be called as a method, this is not a method call")
}
operands := make([]llvm.Value, call.OperandsCount()-1)
for i := range operands {
@@ -518,11 +549,11 @@ func (p *lowerInterfacesPass) replaceInvokeWithCall(use llvm.Value, typ *typeInf
methodParamTypes := paramTypes[len(paramTypes)-(len(operands)-1):]
for i, methodParamType := range methodParamTypes {
if methodParamType != operands[i+1].Type() {
return errorAt(call, "internal error: expected method call param type and function param type to be the same")
panic("expected method call param type and function param type to be the same")
}
}
p.builder.SetInsertPointBefore(call)
receiverParams := llvmutil.EmitPointerUnpack(p.builder, p.mod, operands[0], receiverParamTypes)
receiverParams := p.emitPointerUnpack(operands[0], receiverParamTypes)
result := p.builder.CreateCall(function, append(receiverParams, operands[1:]...), "")
if result.Type().TypeKind() != llvm.VoidTypeKind {
call.ReplaceAllUsesWith(result)
@@ -532,7 +563,6 @@ func (p *lowerInterfacesPass) replaceInvokeWithCall(use llvm.Value, typ *typeInf
}
inttoptr.EraseFromParentAsInstruction()
use.EraseFromParentAsInstruction()
return nil
}
// getInterfaceImplementsFunc returns a function that checks whether a given
@@ -610,10 +640,9 @@ func (p *lowerInterfacesPass) getInterfaceMethodFunc(itf *interfaceInfo, signatu
// Construct the function name, which is of the form:
// (main.Stringer).String
fnName := "(" + itf.id() + ")." + signature.methodName()
fnType := llvm.FunctionType(returnType, append(params, llvm.PointerType(p.ctx.Int8Type(), 0)), false)
fnType := llvm.FunctionType(returnType, params, false)
fn := llvm.AddFunction(p.mod, fnName, fnType)
llvm.PrevParam(fn.LastParam()).SetName("actualType")
fn.LastParam().SetName("parentHandle")
fn.LastParam().SetName("actualType")
itf.methodFuncs[signature] = fn
return fn
}
@@ -642,13 +671,13 @@ func (p *lowerInterfacesPass) createInterfaceMethodFunc(itf *interfaceInfo, sign
// Create type switch in entry block.
p.builder.SetInsertPointAtEnd(entry)
actualType := llvm.PrevParam(fn.LastParam())
actualType := fn.LastParam()
sw := p.builder.CreateSwitch(actualType, defaultBlock, len(itf.types))
// Collect the params that will be passed to the functions to call.
// These params exclude the receiver (which may actually consist of multiple
// parts).
params := make([]llvm.Value, fn.ParamsCount()-3)
params := make([]llvm.Value, fn.ParamsCount()-2)
for i := range params {
params[i] = fn.Param(i + 1)
}
+113 -112
View File
@@ -16,35 +16,35 @@ import (
"tinygo.org/x/go-llvm"
)
// createMakeInterface emits the LLVM IR for the *ssa.MakeInterface instruction.
// parseMakeInterface emits the LLVM IR for the *ssa.MakeInterface instruction.
// It tries to put the type in the interface value, but if that's not possible,
// it will do an allocation of the right size and put that in the interface
// value field.
//
// An interface value is a {typecode, value} tuple named runtime._interface.
func (b *builder) createMakeInterface(val llvm.Value, typ types.Type, pos token.Pos) llvm.Value {
itfValue := b.emitPointerPack([]llvm.Value{val})
itfTypeCodeGlobal := b.getTypeCode(typ)
itfMethodSetGlobal := b.getTypeMethodSet(typ)
itfConcreteTypeGlobal := b.mod.NamedGlobal("typeInInterface:" + itfTypeCodeGlobal.Name())
// An interface value is a {typecode, value} tuple, or {i16, i8*} to be exact.
func (c *Compiler) parseMakeInterface(val llvm.Value, typ types.Type, pos token.Pos) llvm.Value {
itfValue := c.emitPointerPack([]llvm.Value{val})
itfTypeCodeGlobal := c.getTypeCode(typ)
itfMethodSetGlobal := c.getTypeMethodSet(typ)
itfConcreteTypeGlobal := c.mod.NamedGlobal("typeInInterface:" + itfTypeCodeGlobal.Name())
if itfConcreteTypeGlobal.IsNil() {
typeInInterface := b.getLLVMRuntimeType("typeInInterface")
itfConcreteTypeGlobal = llvm.AddGlobal(b.mod, typeInInterface, "typeInInterface:"+itfTypeCodeGlobal.Name())
typeInInterface := c.getLLVMRuntimeType("typeInInterface")
itfConcreteTypeGlobal = llvm.AddGlobal(c.mod, typeInInterface, "typeInInterface:"+itfTypeCodeGlobal.Name())
itfConcreteTypeGlobal.SetInitializer(llvm.ConstNamedStruct(typeInInterface, []llvm.Value{itfTypeCodeGlobal, itfMethodSetGlobal}))
itfConcreteTypeGlobal.SetGlobalConstant(true)
itfConcreteTypeGlobal.SetLinkage(llvm.PrivateLinkage)
}
itfTypeCode := b.CreatePtrToInt(itfConcreteTypeGlobal, b.uintptrType, "")
itf := llvm.Undef(b.getLLVMRuntimeType("_interface"))
itf = b.CreateInsertValue(itf, itfTypeCode, 0, "")
itf = b.CreateInsertValue(itf, itfValue, 1, "")
itfTypeCode := c.builder.CreatePtrToInt(itfConcreteTypeGlobal, c.uintptrType, "")
itf := llvm.Undef(c.getLLVMRuntimeType("_interface"))
itf = c.builder.CreateInsertValue(itf, itfTypeCode, 0, "")
itf = c.builder.CreateInsertValue(itf, itfValue, 1, "")
return itf
}
// getTypeCode returns a reference to a type code.
// It returns a pointer to an external global which should be replaced with the
// real type in the interface lowering pass.
func (c *compilerContext) getTypeCode(typ types.Type) llvm.Value {
func (c *Compiler) getTypeCode(typ types.Type) llvm.Value {
globalName := "reflect/types.type:" + getTypeCodeName(typ)
global := c.mod.NamedGlobal(globalName)
if global.IsNil() {
@@ -74,7 +74,7 @@ func (c *compilerContext) getTypeCode(typ types.Type) llvm.Value {
}
if !references.IsNil() {
// Set the 'references' field of the runtime.typecodeID struct.
globalValue := llvm.ConstNull(global.Type().ElementType())
globalValue := c.getZeroValue(global.Type().ElementType())
globalValue = llvm.ConstInsertValue(globalValue, references, []uint32{0})
if length != 0 {
lengthValue := llvm.ConstInt(c.uintptrType, uint64(length), false)
@@ -91,14 +91,14 @@ func (c *compilerContext) getTypeCode(typ types.Type) llvm.Value {
// makeStructTypeFields creates a new global that stores all type information
// related to this struct type, and returns the resulting global. This global is
// actually an array of all the fields in the structs.
func (c *compilerContext) makeStructTypeFields(typ *types.Struct) llvm.Value {
func (c *Compiler) makeStructTypeFields(typ *types.Struct) llvm.Value {
// The global is an array of runtime.structField structs.
runtimeStructField := c.getLLVMRuntimeType("structField")
structGlobalType := llvm.ArrayType(runtimeStructField, typ.NumFields())
structGlobal := llvm.AddGlobal(c.mod, structGlobalType, "reflect/types.structFields")
structGlobalValue := llvm.ConstNull(structGlobalType)
structGlobalValue := c.getZeroValue(structGlobalType)
for i := 0; i < typ.NumFields(); i++ {
fieldGlobalValue := llvm.ConstNull(runtimeStructField)
fieldGlobalValue := c.getZeroValue(runtimeStructField)
fieldGlobalValue = llvm.ConstInsertValue(fieldGlobalValue, c.getTypeCode(typ.Field(i).Type()), []uint32{0})
fieldName := c.makeGlobalArray([]byte(typ.Field(i).Name()), "reflect/types.structFieldName", c.ctx.Int8Type())
fieldName.SetLinkage(llvm.PrivateLinkage)
@@ -210,6 +210,10 @@ func getTypeCodeName(t types.Type) string {
return "slice:" + getTypeCodeName(t.Elem())
case *types.Struct:
elems := make([]string, t.NumFields())
if t.NumFields() > 2 && t.Field(0).Name() == "C union" {
// TODO: report this as a normal error instead of panicking.
panic("cgo unions are not allowed in interfaces")
}
for i := 0; i < t.NumFields(); i++ {
embedded := ""
if t.Field(i).Embedded() {
@@ -228,7 +232,7 @@ func getTypeCodeName(t types.Type) string {
// getTypeMethodSet returns a reference (GEP) to a global method set. This
// method set should be unreferenced after the interface lowering pass.
func (c *compilerContext) getTypeMethodSet(typ types.Type) llvm.Value {
func (c *Compiler) getTypeMethodSet(typ types.Type) llvm.Value {
global := c.mod.NamedGlobal(typ.String() + "$methodset")
zero := llvm.ConstInt(c.ctx.Int32Type(), 0, false)
if !global.IsNil() {
@@ -271,28 +275,23 @@ func (c *compilerContext) getTypeMethodSet(typ types.Type) llvm.Value {
// getInterfaceMethodSet returns a global variable with the method set of the
// given named interface type. This method set is used by the interface lowering
// pass.
func (c *compilerContext) getInterfaceMethodSet(typ types.Type) llvm.Value {
name := typ.String()
if _, ok := typ.(*types.Named); !ok {
// Anonymous interface.
name = "reflect/types.interface:" + name
}
global := c.mod.NamedGlobal(name + "$interface")
func (c *Compiler) getInterfaceMethodSet(typ *types.Named) llvm.Value {
global := c.mod.NamedGlobal(typ.String() + "$interface")
zero := llvm.ConstInt(c.ctx.Int32Type(), 0, false)
if !global.IsNil() {
// method set already exist, return it
return llvm.ConstGEP(global, []llvm.Value{zero, zero})
}
// Every method is a *i8 reference indicating the signature of this method.
// Every method is a *i16 reference indicating the signature of this method.
methods := make([]llvm.Value, typ.Underlying().(*types.Interface).NumMethods())
for i := range methods {
method := typ.Underlying().(*types.Interface).Method(i)
methods[i] = c.getMethodSignature(method)
}
value := llvm.ConstArray(c.i8ptrType, methods)
global = llvm.AddGlobal(c.mod, value.Type(), name+"$interface")
value := llvm.ConstArray(methods[0].Type(), methods)
global = llvm.AddGlobal(c.mod, value.Type(), typ.String()+"$interface")
global.SetInitializer(value)
global.SetGlobalConstant(true)
global.SetLinkage(llvm.PrivateLinkage)
@@ -300,9 +299,9 @@ func (c *compilerContext) getInterfaceMethodSet(typ types.Type) llvm.Value {
}
// getMethodSignature returns a global variable which is a reference to an
// external *i8 indicating the indicating the signature of this method. It is
// external *i16 indicating the indicating the signature of this method. It is
// used during the interface lowering pass.
func (c *compilerContext) getMethodSignature(method *types.Func) llvm.Value {
func (c *Compiler) getMethodSignature(method *types.Func) llvm.Value {
signature := ir.MethodSignature(method)
signatureGlobal := c.mod.NamedGlobal("func " + signature)
if signatureGlobal.IsNil() {
@@ -312,18 +311,18 @@ func (c *compilerContext) getMethodSignature(method *types.Func) llvm.Value {
return signatureGlobal
}
// createTypeAssert will emit the code for a typeassert, used in if statements
// parseTypeAssert will emit the code for a typeassert, used in if statements
// and in type switches (Go SSA does not have type switches, only if/else
// chains). Note that even though the Go SSA does not contain type switches,
// LLVM will recognize the pattern and make it a real switch in many cases.
//
// Type asserts on concrete types are trivial: just compare type numbers. Type
// asserts on interfaces are more difficult, see the comments in the function.
func (b *builder) createTypeAssert(expr *ssa.TypeAssert) llvm.Value {
itf := b.getValue(expr.X)
assertedType := b.getLLVMType(expr.AssertedType)
func (c *Compiler) parseTypeAssert(frame *Frame, expr *ssa.TypeAssert) llvm.Value {
itf := c.getValue(frame, expr.X)
assertedType := c.getLLVMType(expr.AssertedType)
actualTypeNum := b.CreateExtractValue(itf, 0, "interface.type")
actualTypeNum := c.builder.CreateExtractValue(itf, 0, "interface.type")
commaOk := llvm.Value{}
if _, ok := expr.AssertedType.Underlying().(*types.Interface); ok {
// Type assert on interface type.
@@ -334,15 +333,15 @@ func (b *builder) createTypeAssert(expr *ssa.TypeAssert) llvm.Value {
// the main Go compiler, where the runtime checks whether the type
// implements each method of the interface. See:
// https://research.swtch.com/interfaces
methodSet := b.getInterfaceMethodSet(expr.AssertedType)
commaOk = b.createRuntimeCall("interfaceImplements", []llvm.Value{actualTypeNum, methodSet}, "")
methodSet := c.getInterfaceMethodSet(expr.AssertedType.(*types.Named))
commaOk = c.createRuntimeCall("interfaceImplements", []llvm.Value{actualTypeNum, methodSet}, "")
} else {
// Type assert on concrete type.
// Call runtime.typeAssert, which will be lowered to a simple icmp or
// const false in the interface lowering pass.
assertedTypeCodeGlobal := b.getTypeCode(expr.AssertedType)
commaOk = b.createRuntimeCall("typeAssert", []llvm.Value{actualTypeNum, assertedTypeCodeGlobal}, "typecode")
assertedTypeCodeGlobal := c.getTypeCode(expr.AssertedType)
commaOk = c.createRuntimeCall("typeAssert", []llvm.Value{actualTypeNum, assertedTypeCodeGlobal}, "typecode")
}
// Add 2 new basic blocks (that should get optimized away): one for the
@@ -356,15 +355,15 @@ func (b *builder) createTypeAssert(expr *ssa.TypeAssert) llvm.Value {
// typeassert should return a zero value, not an incorrectly casted
// value.
prevBlock := b.GetInsertBlock()
okBlock := b.ctx.AddBasicBlock(b.fn.LLVMFn, "typeassert.ok")
nextBlock := b.ctx.AddBasicBlock(b.fn.LLVMFn, "typeassert.next")
b.blockExits[b.currentBlock] = nextBlock // adjust outgoing block for phi nodes
b.CreateCondBr(commaOk, okBlock, nextBlock)
prevBlock := c.builder.GetInsertBlock()
okBlock := c.ctx.AddBasicBlock(frame.fn.LLVMFn, "typeassert.ok")
nextBlock := c.ctx.AddBasicBlock(frame.fn.LLVMFn, "typeassert.next")
frame.blockExits[frame.currentBlock] = nextBlock // adjust outgoing block for phi nodes
c.builder.CreateCondBr(commaOk, okBlock, nextBlock)
// Retrieve the value from the interface if the type assert was
// successful.
b.SetInsertPointAtEnd(okBlock)
c.builder.SetInsertPointAtEnd(okBlock)
var valueOk llvm.Value
if _, ok := expr.AssertedType.Underlying().(*types.Interface); ok {
// Type assert on interface type. Easy: just return the same
@@ -373,70 +372,74 @@ func (b *builder) createTypeAssert(expr *ssa.TypeAssert) llvm.Value {
} else {
// Type assert on concrete type. Extract the underlying type from
// the interface (but only after checking it matches).
valuePtr := b.CreateExtractValue(itf, 1, "typeassert.value.ptr")
valueOk = b.emitPointerUnpack(valuePtr, []llvm.Type{assertedType})[0]
valuePtr := c.builder.CreateExtractValue(itf, 1, "typeassert.value.ptr")
valueOk = c.emitPointerUnpack(valuePtr, []llvm.Type{assertedType})[0]
}
b.CreateBr(nextBlock)
c.builder.CreateBr(nextBlock)
// Continue after the if statement.
b.SetInsertPointAtEnd(nextBlock)
phi := b.CreatePHI(assertedType, "typeassert.value")
phi.AddIncoming([]llvm.Value{llvm.ConstNull(assertedType), valueOk}, []llvm.BasicBlock{prevBlock, okBlock})
c.builder.SetInsertPointAtEnd(nextBlock)
phi := c.builder.CreatePHI(assertedType, "typeassert.value")
phi.AddIncoming([]llvm.Value{c.getZeroValue(assertedType), valueOk}, []llvm.BasicBlock{prevBlock, okBlock})
if expr.CommaOk {
tuple := b.ctx.ConstStruct([]llvm.Value{llvm.Undef(assertedType), llvm.Undef(b.ctx.Int1Type())}, false) // create empty tuple
tuple = b.CreateInsertValue(tuple, phi, 0, "") // insert value
tuple = b.CreateInsertValue(tuple, commaOk, 1, "") // insert 'comma ok' boolean
tuple := c.ctx.ConstStruct([]llvm.Value{llvm.Undef(assertedType), llvm.Undef(c.ctx.Int1Type())}, false) // create empty tuple
tuple = c.builder.CreateInsertValue(tuple, phi, 0, "") // insert value
tuple = c.builder.CreateInsertValue(tuple, commaOk, 1, "") // insert 'comma ok' boolean
return tuple
} else {
// This is kind of dirty as the branch above becomes mostly useless,
// but hopefully this gets optimized away.
b.createRuntimeCall("interfaceTypeAssert", []llvm.Value{commaOk}, "")
c.createRuntimeCall("interfaceTypeAssert", []llvm.Value{commaOk}, "")
return phi
}
}
// getInvokePtr creates an interface function pointer lookup for the specified invoke instruction, using a specified typecode.
func (b *builder) getInvokePtr(instr *ssa.CallCommon, typecode llvm.Value) llvm.Value {
llvmFnType := b.getRawFuncType(instr.Method.Type().(*types.Signature))
// getInvokeCall creates and returns the function pointer and parameters of an
// interface call. It can be used in a call or defer instruction.
func (c *Compiler) getInvokeCall(frame *Frame, instr *ssa.CallCommon) (llvm.Value, []llvm.Value) {
// Call an interface method with dynamic dispatch.
itf := c.getValue(frame, instr.Value) // interface
llvmFnType := c.getRawFuncType(instr.Method.Type().(*types.Signature))
typecode := c.builder.CreateExtractValue(itf, 0, "invoke.typecode")
values := []llvm.Value{
typecode,
b.getInterfaceMethodSet(instr.Value.Type()),
b.getMethodSignature(instr.Method),
c.getInterfaceMethodSet(instr.Value.Type().(*types.Named)),
c.getMethodSignature(instr.Method),
}
fn := b.createRuntimeCall("interfaceMethod", values, "invoke.func")
return b.CreateIntToPtr(fn, llvmFnType, "invoke.func.cast")
}
// getInvokeCall creates and returns the function pointer and parameters of an
// interface call.
func (b *builder) getInvokeCall(instr *ssa.CallCommon) (llvm.Value, []llvm.Value) {
// Call an interface method with dynamic dispatch.
itf := b.getValue(instr.Value) // interface
typecode := b.CreateExtractValue(itf, 0, "invoke.typecode")
fnCast := b.getInvokePtr(instr, typecode)
receiverValue := b.CreateExtractValue(itf, 1, "invoke.func.receiver")
fn := c.createRuntimeCall("interfaceMethod", values, "invoke.func")
fnCast := c.builder.CreateIntToPtr(fn, llvmFnType, "invoke.func.cast")
receiverValue := c.builder.CreateExtractValue(itf, 1, "invoke.func.receiver")
args := []llvm.Value{receiverValue}
for _, arg := range instr.Args {
args = append(args, b.getValue(arg))
args = append(args, c.getValue(frame, arg))
}
// 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))
args = append(args, llvm.Undef(c.i8ptrType))
// Add the parent goroutine handle.
args = append(args, llvm.Undef(b.i8ptrType))
args = append(args, llvm.Undef(c.i8ptrType))
return fnCast, args
}
// getInterfaceInvokeWrapper returns a wrapper for the given method so it can be
// invoked from an interface. The wrapper takes in a pointer to the underlying
// value, dereferences or unpacks it if necessary, and calls the real method.
// If the method to wrap has a pointer receiver, no wrapping is necessary and
// the function is returned directly.
func (c *compilerContext) getInterfaceInvokeWrapper(f *ir.Function) llvm.Value {
// interfaceInvokeWrapper keeps some state between getInterfaceInvokeWrapper and
// createInterfaceInvokeWrapper. The former is called during IR construction
// itself and the latter is called when finishing up the IR.
type interfaceInvokeWrapper struct {
fn *ir.Function
wrapper llvm.Value
receiverType llvm.Type
}
// Wrap an interface method function pointer. The wrapper takes in a pointer to
// the underlying value, dereferences it, and calls the real method. This
// wrapper is only needed when the interface value actually doesn't fit in a
// pointer and a pointer to the value must be created.
func (c *Compiler) getInterfaceInvokeWrapper(f *ir.Function) llvm.Value {
wrapperName := f.LinkName() + "$invoke"
wrapper := c.mod.NamedFunction(wrapperName)
if !wrapper.IsNil() {
@@ -446,10 +449,7 @@ func (c *compilerContext) getInterfaceInvokeWrapper(f *ir.Function) llvm.Value {
// Get the expanded receiver type.
receiverType := c.getLLVMType(f.Params[0].Type())
var expandedReceiverType []llvm.Type
for _, info := range expandFormalParamType(receiverType, "", nil) {
expandedReceiverType = append(expandedReceiverType, info.llvmType)
}
expandedReceiverType := c.expandFormalParamType(receiverType)
// Does this method even need any wrapping?
if len(expandedReceiverType) == 1 && receiverType.TypeKind() == llvm.PointerTypeKind {
@@ -465,40 +465,41 @@ func (c *compilerContext) getInterfaceInvokeWrapper(f *ir.Function) llvm.Value {
paramTypes := append([]llvm.Type{c.i8ptrType}, fnType.ParamTypes()[len(expandedReceiverType):]...)
wrapFnType := llvm.FunctionType(fnType.ReturnType(), paramTypes, false)
wrapper = llvm.AddFunction(c.mod, wrapperName, wrapFnType)
if f.LLVMFn.LastParam().Name() == "parentHandle" {
wrapper.LastParam().SetName("parentHandle")
}
c.interfaceInvokeWrappers = append(c.interfaceInvokeWrappers, interfaceInvokeWrapper{
fn: f,
wrapper: wrapper,
receiverType: receiverType,
})
return wrapper
}
// createInterfaceInvokeWrapper finishes the work of getInterfaceInvokeWrapper,
// see that function for details.
func (c *Compiler) createInterfaceInvokeWrapper(state interfaceInvokeWrapper) {
wrapper := state.wrapper
fn := state.fn
receiverType := state.receiverType
wrapper.SetLinkage(llvm.InternalLinkage)
wrapper.SetUnnamedAddr(true)
// Create a new builder just to create this wrapper.
b := builder{
compilerContext: c,
Builder: c.ctx.NewBuilder(),
}
defer b.Builder.Dispose()
// add debug info if needed
if c.Debug() {
pos := c.ir.Program.Fset.Position(f.Pos())
difunc := c.attachDebugInfoRaw(f, wrapper, "$invoke", pos.Filename, pos.Line)
b.SetCurrentDebugLocation(uint(pos.Line), uint(pos.Column), difunc, llvm.Metadata{})
if c.Debug {
pos := c.ir.Program.Fset.Position(fn.Pos())
difunc := c.attachDebugInfoRaw(fn, wrapper, "$invoke", pos.Filename, pos.Line)
c.builder.SetCurrentDebugLocation(uint(pos.Line), uint(pos.Column), difunc, llvm.Metadata{})
}
// set up IR builder
block := b.ctx.AddBasicBlock(wrapper, "entry")
b.SetInsertPointAtEnd(block)
block := c.ctx.AddBasicBlock(wrapper, "entry")
c.builder.SetInsertPointAtEnd(block)
receiverValue := b.emitPointerUnpack(wrapper.Param(0), []llvm.Type{receiverType})[0]
params := append(b.expandFormalParam(receiverValue), wrapper.Params()[1:]...)
if f.LLVMFn.Type().ElementType().ReturnType().TypeKind() == llvm.VoidTypeKind {
b.CreateCall(f.LLVMFn, params, "")
b.CreateRetVoid()
receiverValue := c.emitPointerUnpack(wrapper.Param(0), []llvm.Type{receiverType})[0]
params := append(c.expandFormalParam(receiverValue), wrapper.Params()[1:]...)
if fn.LLVMFn.Type().ElementType().ReturnType().TypeKind() == llvm.VoidTypeKind {
c.builder.CreateCall(fn.LLVMFn, params, "")
c.builder.CreateRetVoid()
} else {
ret := b.CreateCall(f.LLVMFn, params, "ret")
b.CreateRet(ret)
ret := c.builder.CreateCall(fn.LLVMFn, params, "ret")
c.builder.CreateRet(ret)
}
return wrapper
}
-92
View File
@@ -1,92 +0,0 @@
package compiler
import (
"strconv"
"strings"
"golang.org/x/tools/go/ssa"
"tinygo.org/x/go-llvm"
)
// createInterruptGlobal creates a new runtime/interrupt.Interrupt struct that
// will be lowered to a real interrupt during interrupt lowering.
//
// This two-stage approach allows unused interrupts to be optimized away if
// necessary.
func (b *builder) createInterruptGlobal(instr *ssa.CallCommon) (llvm.Value, error) {
// Get the interrupt number, which must be a compile-time constant.
id, ok := instr.Args[0].(*ssa.Const)
if !ok {
return llvm.Value{}, b.makeError(instr.Pos(), "interrupt ID is not a constant")
}
// Get the func value, which also must be a compile time constant.
// Note that bound functions are allowed if the function has a pointer
// receiver and is a global. This is rather strict but still allows for
// idiomatic Go code.
funcValue := b.getValue(instr.Args[1])
if funcValue.IsAConstant().IsNil() {
// Try to determine the cause of the non-constantness for a nice error
// message.
switch instr.Args[1].(type) {
case *ssa.MakeClosure:
// This may also be a bound method.
return llvm.Value{}, b.makeError(instr.Pos(), "closures are not supported in interrupt.New")
}
// Fall back to a generic error.
return llvm.Value{}, b.makeError(instr.Pos(), "interrupt function must be constant")
}
// Create a new global of type runtime/interrupt.handle. Globals of this
// type are lowered in the interrupt lowering pass.
globalType := b.ir.Program.ImportedPackage("runtime/interrupt").Type("handle").Type()
globalLLVMType := b.getLLVMType(globalType)
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.SetLinkage(llvm.PrivateLinkage)
global.SetGlobalConstant(true)
global.SetUnnamedAddr(true)
initializer := llvm.ConstNull(globalLLVMType)
initializer = llvm.ConstInsertValue(initializer, funcValue, []uint32{0})
initializer = llvm.ConstInsertValue(initializer, llvm.ConstInt(b.intType, uint64(id.Int64()), true), []uint32{1, 0})
global.SetInitializer(initializer)
// Add debug info to the interrupt global.
if b.Debug() {
pos := b.ir.Program.Fset.Position(instr.Pos())
diglobal := b.dibuilder.CreateGlobalVariableExpression(b.getDIFile(pos.Filename), llvm.DIGlobalVariableExpression{
Name: "interrupt" + strconv.FormatInt(id.Int64(), 10),
LinkageName: globalName,
File: b.getDIFile(pos.Filename),
Line: pos.Line,
Type: b.getDIType(globalType),
Expr: b.dibuilder.CreateExpression(nil),
LocalToUnit: false,
})
global.AddMetadata(0, diglobal)
}
// Create the runtime/interrupt.Interrupt type. It is a struct with a single
// member of type int.
num := llvm.ConstPtrToInt(global, b.intType)
interrupt := llvm.ConstNamedStruct(b.mod.GetTypeByName("runtime/interrupt.Interrupt"), []llvm.Value{num})
// Add dummy "use" call for AVR, because interrupts may be used even though
// they are never referenced again. This is unlike Cortex-M or the RISC-V
// PLIC where each interrupt must be enabled using the interrupt number, and
// thus keeps the Interrupt object alive.
// This call is removed during interrupt lowering.
if strings.HasPrefix(b.Triple(), "avr") {
useFn := b.mod.NamedFunction("runtime/interrupt.use")
if useFn.IsNil() {
useFnType := llvm.FunctionType(b.ctx.VoidType(), []llvm.Type{interrupt.Type()}, false)
useFn = llvm.AddFunction(b.mod, "runtime/interrupt.use", useFnType)
}
b.CreateCall(useFn, []llvm.Value{interrupt}, "")
}
return interrupt, nil
}
-50
View File
@@ -1,50 +0,0 @@
package compiler
// This file contains helper functions to create calls to LLVM intrinsics.
import (
"strconv"
"golang.org/x/tools/go/ssa"
"tinygo.org/x/go-llvm"
)
// createMemoryCopyCall creates a call to a builtin LLVM memcpy or memmove
// function, declaring this function if needed. These calls are treated
// specially by optimization passes possibly resulting in better generated code,
// and will otherwise be lowered to regular libc memcpy/memmove calls.
func (b *builder) createMemoryCopyCall(fn *ssa.Function, args []ssa.Value) (llvm.Value, error) {
fnName := "llvm." + fn.Name() + ".p0i8.p0i8.i" + strconv.Itoa(b.uintptrType.IntTypeWidth())
llvmFn := b.mod.NamedFunction(fnName)
if llvmFn.IsNil() {
fnType := llvm.FunctionType(b.ctx.VoidType(), []llvm.Type{b.i8ptrType, b.i8ptrType, b.uintptrType, b.ctx.Int1Type()}, false)
llvmFn = llvm.AddFunction(b.mod, fnName, fnType)
}
var params []llvm.Value
for _, param := range args {
params = append(params, b.getValue(param))
}
params = append(params, llvm.ConstInt(b.ctx.Int1Type(), 0, false))
b.CreateCall(llvmFn, params, "")
return llvm.Value{}, nil
}
// createMemoryZeroCall creates calls to llvm.memset.* to zero a block of
// memory, declaring the function if needed. These calls will be lowered to
// regular libc memset calls if they aren't optimized out in a different way.
func (b *builder) createMemoryZeroCall(args []ssa.Value) (llvm.Value, error) {
fnName := "llvm.memset.p0i8.i" + strconv.Itoa(b.uintptrType.IntTypeWidth())
llvmFn := b.mod.NamedFunction(fnName)
if llvmFn.IsNil() {
fnType := llvm.FunctionType(b.ctx.VoidType(), []llvm.Type{b.i8ptrType, b.ctx.Int8Type(), b.uintptrType, b.ctx.Int1Type()}, false)
llvmFn = llvm.AddFunction(b.mod, fnName, fnType)
}
params := []llvm.Value{
b.getValue(args[0]),
llvm.ConstInt(b.ctx.Int8Type(), 0, false),
b.getValue(args[1]),
llvm.ConstInt(b.ctx.Int1Type(), 0, false),
}
b.CreateCall(llvmFn, params, "")
return llvm.Value{}, nil
}
-194
View File
@@ -1,194 +0,0 @@
// Package ircheck implements a checker for LLVM IR, that goes a bit further
// than the regular LLVM IR verifier. Note that it checks different things, so
// this is not a replacement for the LLVM verifier but does catch things that
// the LLVM verifier doesn't catch.
package ircheck
import (
"errors"
"fmt"
"tinygo.org/x/go-llvm"
)
type checker struct {
ctx llvm.Context
}
func (c *checker) checkType(t llvm.Type, checked map[llvm.Type]struct{}, specials map[llvm.TypeKind]llvm.Type) error {
// prevent infinite recursion for self-referential types
if _, ok := checked[t]; ok {
return nil
}
checked[t] = struct{}{}
// check for any context mismatches
switch {
case t.Context() == c.ctx:
// this is correct
case t.Context() == llvm.GlobalContext():
// somewhere we accidentally used the global context instead of a real context
return fmt.Errorf("type %q uses global context", t.String())
default:
// we used some other context by accident
return fmt.Errorf("type %q uses context %v instead of the main context %v", t.Context(), c.ctx)
}
// if this is a composite type, check the components of the type
switch t.TypeKind() {
case llvm.VoidTypeKind, llvm.LabelTypeKind, llvm.TokenTypeKind, llvm.MetadataTypeKind:
// there should only be one of any of these
if s, ok := specials[t.TypeKind()]; !ok {
specials[t.TypeKind()] = t
} else if s != t {
return fmt.Errorf("duplicate special type %q: %v and %v", t.TypeKind().String(), t, s)
}
case llvm.FloatTypeKind, llvm.DoubleTypeKind, llvm.X86_FP80TypeKind, llvm.FP128TypeKind, llvm.PPC_FP128TypeKind:
// floating point numbers are primitives - nothing to recurse
case llvm.IntegerTypeKind:
// integers are primitives - nothing to recurse
case llvm.FunctionTypeKind:
// check arguments and return(s)
for i, v := range t.ParamTypes() {
if err := c.checkType(v, checked, specials); err != nil {
return fmt.Errorf("failed to verify argument %d of type %s: %s", i, t.String(), err.Error())
}
}
if err := c.checkType(t.ReturnType(), checked, specials); err != nil {
return fmt.Errorf("failed to verify return type of type %s: %s", t.String(), err.Error())
}
case llvm.StructTypeKind:
// check all elements
for i, v := range t.StructElementTypes() {
if err := c.checkType(v, checked, specials); err != nil {
return fmt.Errorf("failed to verify type of field %d of struct type %s: %s", i, t.String(), err.Error())
}
}
case llvm.ArrayTypeKind:
// check element type
if err := c.checkType(t.ElementType(), checked, specials); err != nil {
return fmt.Errorf("failed to verify element type of array type %s: %s", t.String(), err.Error())
}
case llvm.PointerTypeKind:
// check underlying type
if err := c.checkType(t.ElementType(), checked, specials); err != nil {
return fmt.Errorf("failed to verify underlying type of pointer type %s: %s", t.String(), err.Error())
}
case llvm.VectorTypeKind:
// check element type
if err := c.checkType(t.ElementType(), checked, specials); err != nil {
return fmt.Errorf("failed to verify element type of vector type %s: %s", t.String(), err.Error())
}
default:
return fmt.Errorf("unrecognized kind %q of type %s", t.TypeKind(), t.String())
}
return nil
}
func (c *checker) checkValue(v llvm.Value, types map[llvm.Type]struct{}, specials map[llvm.TypeKind]llvm.Type) error {
// check type
if err := c.checkType(v.Type(), types, specials); err != nil {
return fmt.Errorf("failed to verify type of value: %s", err.Error())
}
// check if this is an undefined void
if v.IsUndef() && v.Type().TypeKind() == llvm.VoidTypeKind {
return errors.New("encountered undefined void value")
}
return nil
}
func (c *checker) checkInstruction(inst llvm.Value, types map[llvm.Type]struct{}, specials map[llvm.TypeKind]llvm.Type) error {
// check value properties
if err := c.checkValue(inst, types, specials); err != nil {
return errorAt(inst, err.Error())
}
// The alloca instruction can be present in every basic block. However,
// allocas in basic blocks other than the entry basic block have a number of
// problems:
// * They are hard to optimize, leading to potential missed optimizations.
// * They may cause stack overflows in loops that would otherwise be
// innocent.
// * They cause extra code to be generated, because it requires the use of
// a frame pointer.
// * Perhaps most importantly, the coroutine lowering pass of LLVM (as of
// LLVM 9) cannot deal with these allocas:
// https://llvm.org/docs/Coroutines.html
// Therefore, alloca instructions should be limited to the entry block.
if !inst.IsAAllocaInst().IsNil() {
if inst.InstructionParent() != inst.InstructionParent().Parent().EntryBasicBlock() {
return errorAt(inst, "internal error: non-static alloca")
}
}
// check operands
for i := 0; i < inst.OperandsCount(); i++ {
if err := c.checkValue(inst.Operand(i), types, specials); err != nil {
return errorAt(inst, fmt.Sprintf("failed to validate operand %d of instruction %q: %s", i, inst.Name(), err.Error()))
}
}
return nil
}
func (c *checker) checkBasicBlock(bb llvm.BasicBlock, types map[llvm.Type]struct{}, specials map[llvm.TypeKind]llvm.Type) []error {
// check basic block value and type
var errs []error
if err := c.checkValue(bb.AsValue(), types, specials); err != nil {
errs = append(errs, errorAt(bb.Parent(), fmt.Sprintf("failed to validate value of basic block %s: %v", bb.AsValue().Name(), err)))
}
// check instructions
for inst := bb.FirstInstruction(); !inst.IsNil(); inst = llvm.NextInstruction(inst) {
if err := c.checkInstruction(inst, types, specials); err != nil {
errs = append(errs, err)
}
}
return errs
}
func (c *checker) checkFunction(fn llvm.Value, types map[llvm.Type]struct{}, specials map[llvm.TypeKind]llvm.Type) []error {
// check function value and type
var errs []error
if err := c.checkValue(fn, types, specials); err != nil {
errs = append(errs, fmt.Errorf("failed to validate value of function %s: %s", fn.Name(), err.Error()))
}
// check basic blocks
for bb := fn.FirstBasicBlock(); !bb.IsNil(); bb = llvm.NextBasicBlock(bb) {
errs = append(errs, c.checkBasicBlock(bb, types, specials)...)
}
return errs
}
// Module checks the given module and returns a slice of error, if there are
// any.
func Module(mod llvm.Module) []error {
// check for any context mismatches
var errs []error
c := checker{
ctx: mod.Context(),
}
if c.ctx == llvm.GlobalContext() {
// somewhere we accidentally used the global context instead of a real context
errs = append(errs, errors.New("module uses global context"))
}
types := map[llvm.Type]struct{}{}
specials := map[llvm.TypeKind]llvm.Type{}
for fn := mod.FirstFunction(); !fn.IsNil(); fn = llvm.NextFunction(fn) {
errs = append(errs, c.checkFunction(fn, types, specials)...)
}
for g := mod.FirstGlobal(); !g.IsNil(); g = llvm.NextGlobal(g) {
if err := c.checkValue(g, types, specials); err != nil {
errs = append(errs, fmt.Errorf("failed to verify global %s of module: %s", g.Name(), err.Error()))
}
}
return errs
}
-48
View File
@@ -1,48 +0,0 @@
package ircheck
import (
"go/scanner"
"go/token"
"path/filepath"
"tinygo.org/x/go-llvm"
)
// 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
// it is available.
func getPosition(val llvm.Value) token.Position {
if !val.IsAInstruction().IsNil() {
loc := val.InstructionDebugLoc()
if loc.IsNil() {
return token.Position{}
}
file := loc.LocationScope().ScopeFile()
return token.Position{
Filename: filepath.Join(file.FileDirectory(), file.FileFilename()),
Line: int(loc.LocationLine()),
Column: int(loc.LocationColumn()),
}
} else if !val.IsAFunction().IsNil() {
loc := val.Subprogram()
if loc.IsNil() {
return token.Position{}
}
file := loc.ScopeFile()
return token.Position{
Filename: filepath.Join(file.FileDirectory(), file.FileFilename()),
Line: int(loc.SubprogramLine()),
}
} else {
return token.Position{}
}
}
+150 -18
View File
@@ -1,7 +1,8 @@
package compiler
import (
"github.com/tinygo-org/tinygo/compiler/llvmutil"
"reflect"
"tinygo.org/x/go-llvm"
)
@@ -23,47 +24,178 @@ func getUses(value llvm.Value) []llvm.Value {
return uses
}
// createEntryBlockAlloca creates a new alloca in the entry block, even though
// the IR builder is located elsewhere. It assumes that the insert point is
// at the end of the current block.
func (c *Compiler) createEntryBlockAlloca(t llvm.Type, name string) llvm.Value {
currentBlock := c.builder.GetInsertBlock()
entryBlock := currentBlock.Parent().EntryBasicBlock()
if entryBlock.FirstInstruction().IsNil() {
c.builder.SetInsertPointAtEnd(entryBlock)
} else {
c.builder.SetInsertPointBefore(entryBlock.FirstInstruction())
}
alloca := c.builder.CreateAlloca(t, name)
c.builder.SetInsertPointAtEnd(currentBlock)
return alloca
}
// createTemporaryAlloca creates a new alloca in the entry block and adds
// lifetime start information in the IR signalling that the alloca won't be used
// lifetime start infromation in the IR signalling that the alloca won't be used
// before this point.
//
// This is useful for creating temporary allocas for intrinsics. Don't forget to
// end the lifetime using emitLifetimeEnd after you're done with it.
func (b *builder) createTemporaryAlloca(t llvm.Type, name string) (alloca, bitcast, size llvm.Value) {
return llvmutil.CreateTemporaryAlloca(b.Builder, b.mod, t, name)
func (c *Compiler) createTemporaryAlloca(t llvm.Type, name string) (alloca, bitcast, size llvm.Value) {
alloca = c.createEntryBlockAlloca(t, name)
bitcast = c.builder.CreateBitCast(alloca, c.i8ptrType, name+".bitcast")
size = llvm.ConstInt(c.ctx.Int64Type(), c.targetData.TypeAllocSize(t), false)
c.builder.CreateCall(c.getLifetimeStartFunc(), []llvm.Value{size, bitcast}, "")
return
}
// emitLifetimeEnd signals the end of an (alloca) lifetime by calling the
// llvm.lifetime.end intrinsic. It is commonly used together with
// createTemporaryAlloca.
func (b *builder) emitLifetimeEnd(ptr, size llvm.Value) {
llvmutil.EmitLifetimeEnd(b.Builder, b.mod, ptr, size)
func (c *Compiler) emitLifetimeEnd(ptr, size llvm.Value) {
c.builder.CreateCall(c.getLifetimeEndFunc(), []llvm.Value{size, ptr}, "")
}
// emitPointerPack packs the list of values into a single pointer value using
// bitcasts, or else allocates a value on the heap if it cannot be packed in the
// pointer value directly. It returns the pointer with the packed data.
func (b *builder) emitPointerPack(values []llvm.Value) llvm.Value {
return llvmutil.EmitPointerPack(b.Builder, b.mod, b.Config, values)
// getLifetimeStartFunc returns the llvm.lifetime.start intrinsic and creates it
// first if it doesn't exist yet.
func (c *Compiler) getLifetimeStartFunc() llvm.Value {
fn := c.mod.NamedFunction("llvm.lifetime.start.p0i8")
if fn.IsNil() {
fnType := llvm.FunctionType(c.ctx.VoidType(), []llvm.Type{c.ctx.Int64Type(), c.i8ptrType}, false)
fn = llvm.AddFunction(c.mod, "llvm.lifetime.start.p0i8", fnType)
}
return fn
}
// emitPointerUnpack extracts a list of values packed using emitPointerPack.
func (b *builder) emitPointerUnpack(ptr llvm.Value, valueTypes []llvm.Type) []llvm.Value {
return llvmutil.EmitPointerUnpack(b.Builder, b.mod, ptr, valueTypes)
// getLifetimeEndFunc returns the llvm.lifetime.end intrinsic and creates it
// first if it doesn't exist yet.
func (c *Compiler) getLifetimeEndFunc() llvm.Value {
fn := c.mod.NamedFunction("llvm.lifetime.end.p0i8")
if fn.IsNil() {
fnType := llvm.FunctionType(c.ctx.VoidType(), []llvm.Type{c.ctx.Int64Type(), c.i8ptrType}, false)
fn = llvm.AddFunction(c.mod, "llvm.lifetime.end.p0i8", fnType)
}
return fn
}
// splitBasicBlock splits a LLVM basic block into two parts. All instructions
// after afterInst are moved into a new basic block (created right after the
// current one) with the given name.
func (c *Compiler) splitBasicBlock(afterInst llvm.Value, insertAfter llvm.BasicBlock, name string) llvm.BasicBlock {
oldBlock := afterInst.InstructionParent()
newBlock := c.ctx.InsertBasicBlock(insertAfter, name)
var nextInstructions []llvm.Value // values to move
// Collect to-be-moved instructions.
inst := afterInst
for {
inst = llvm.NextInstruction(inst)
if inst.IsNil() {
break
}
nextInstructions = append(nextInstructions, inst)
}
// Move instructions.
c.builder.SetInsertPointAtEnd(newBlock)
for _, inst := range nextInstructions {
inst.RemoveFromParentAsInstruction()
c.builder.Insert(inst)
}
// Find PHI nodes to update.
var phiNodes []llvm.Value // PHI nodes to update
for bb := insertAfter.Parent().FirstBasicBlock(); !bb.IsNil(); bb = llvm.NextBasicBlock(bb) {
for inst := bb.FirstInstruction(); !inst.IsNil(); inst = llvm.NextInstruction(inst) {
if inst.IsAPHINode().IsNil() {
continue
}
needsUpdate := false
incomingCount := inst.IncomingCount()
for i := 0; i < incomingCount; i++ {
if inst.IncomingBlock(i) == oldBlock {
needsUpdate = true
break
}
}
if !needsUpdate {
// PHI node has no incoming edge from the old block.
continue
}
phiNodes = append(phiNodes, inst)
}
}
// Update PHI nodes.
for _, phi := range phiNodes {
c.builder.SetInsertPointBefore(phi)
newPhi := c.builder.CreatePHI(phi.Type(), "")
incomingCount := phi.IncomingCount()
incomingVals := make([]llvm.Value, incomingCount)
incomingBlocks := make([]llvm.BasicBlock, incomingCount)
for i := 0; i < incomingCount; i++ {
value := phi.IncomingValue(i)
block := phi.IncomingBlock(i)
if block == oldBlock {
block = newBlock
}
incomingVals[i] = value
incomingBlocks[i] = block
}
newPhi.AddIncoming(incomingVals, incomingBlocks)
phi.ReplaceAllUsesWith(newPhi)
phi.EraseFromParentAsInstruction()
}
return newBlock
}
// makeGlobalArray creates a new LLVM global with the given name and integers as
// contents, and returns the global.
// Note that it is left with the default linkage etc., you should set
// linkage/constant/etc properties yourself.
func (c *compilerContext) makeGlobalArray(buf []byte, name string, elementType llvm.Type) llvm.Value {
globalType := llvm.ArrayType(elementType, len(buf))
func (c *Compiler) makeGlobalArray(bufItf interface{}, name string, elementType llvm.Type) llvm.Value {
buf := reflect.ValueOf(bufItf)
globalType := llvm.ArrayType(elementType, buf.Len())
global := llvm.AddGlobal(c.mod, globalType, name)
value := llvm.Undef(globalType)
for i := 0; i < len(buf); i++ {
ch := uint64(buf[i])
for i := 0; i < buf.Len(); i++ {
ch := buf.Index(i).Uint()
value = llvm.ConstInsertValue(value, llvm.ConstInt(elementType, ch, false), []uint32{uint32(i)})
}
global.SetInitializer(value)
return global
}
// getGlobalBytes returns the slice contained in the array of the provided
// global. It can recover the bytes originally created using makeGlobalArray, if
// makeGlobalArray was given a byte slice.
func getGlobalBytes(global llvm.Value) []byte {
value := global.Initializer()
buf := make([]byte, value.Type().ArrayLength())
for i := range buf {
buf[i] = byte(llvm.ConstExtractValue(value, []uint32{uint32(i)}).ZExtValue())
}
return buf
}
// replaceGlobalByteWithArray replaces a global integer type in the module with
// an integer array, using a GEP to make the types match. It is a convenience
// function used for creating reflection sidetables, for example.
func (c *Compiler) replaceGlobalIntWithArray(name string, buf interface{}) llvm.Value {
oldGlobal := c.mod.NamedGlobal(name)
global := c.makeGlobalArray(buf, name+".tmp", oldGlobal.Type().ElementType())
gep := llvm.ConstGEP(global, []llvm.Value{
llvm.ConstInt(c.ctx.Int32Type(), 0, false),
llvm.ConstInt(c.ctx.Int32Type(), 0, false),
})
oldGlobal.ReplaceAllUsesWith(gep)
oldGlobal.EraseFromParentAsGlobal()
global.SetName(name)
return global
}
-168
View File
@@ -1,168 +0,0 @@
// Package llvmutil contains utility functions used across multiple compiler
// packages. For example, they may be used by both the compiler pacakge and
// transformation packages.
//
// Normally, utility packages are avoided. However, in this case, the utility
// functions are non-trivial and hard to get right. Copying them to multiple
// places would be a big risk if only one of them is updated.
package llvmutil
import "tinygo.org/x/go-llvm"
// CreateEntryBlockAlloca creates a new alloca in the entry block, even though
// the IR builder is located elsewhere. It assumes that the insert point is
// at the end of the current block.
func CreateEntryBlockAlloca(builder llvm.Builder, t llvm.Type, name string) llvm.Value {
currentBlock := builder.GetInsertBlock()
entryBlock := currentBlock.Parent().EntryBasicBlock()
if entryBlock.FirstInstruction().IsNil() {
builder.SetInsertPointAtEnd(entryBlock)
} else {
builder.SetInsertPointBefore(entryBlock.FirstInstruction())
}
alloca := builder.CreateAlloca(t, name)
builder.SetInsertPointAtEnd(currentBlock)
return alloca
}
// CreateTemporaryAlloca creates a new alloca in the entry block and adds
// lifetime start infromation in the IR signalling that the alloca won't be used
// before this point.
//
// This is useful for creating temporary allocas for intrinsics. Don't forget to
// end the lifetime using emitLifetimeEnd after you're done with it.
func CreateTemporaryAlloca(builder llvm.Builder, mod llvm.Module, t llvm.Type, name string) (alloca, bitcast, size llvm.Value) {
ctx := t.Context()
targetData := llvm.NewTargetData(mod.DataLayout())
i8ptrType := llvm.PointerType(ctx.Int8Type(), 0)
alloca = CreateEntryBlockAlloca(builder, t, name)
bitcast = builder.CreateBitCast(alloca, i8ptrType, name+".bitcast")
size = llvm.ConstInt(ctx.Int64Type(), targetData.TypeAllocSize(t), false)
builder.CreateCall(getLifetimeStartFunc(mod), []llvm.Value{size, bitcast}, "")
return
}
// CreateInstructionAlloca creates an alloca in the entry block, and places lifetime control intrinsics around the instruction
func CreateInstructionAlloca(builder llvm.Builder, mod llvm.Module, t llvm.Type, inst llvm.Value, name string) llvm.Value {
ctx := mod.Context()
targetData := llvm.NewTargetData(mod.DataLayout())
i8ptrType := llvm.PointerType(ctx.Int8Type(), 0)
alloca := CreateEntryBlockAlloca(builder, t, name)
builder.SetInsertPointBefore(inst)
bitcast := builder.CreateBitCast(alloca, i8ptrType, name+".bitcast")
size := llvm.ConstInt(ctx.Int64Type(), targetData.TypeAllocSize(t), false)
builder.CreateCall(getLifetimeStartFunc(mod), []llvm.Value{size, bitcast}, "")
if next := llvm.NextInstruction(inst); !next.IsNil() {
builder.SetInsertPointBefore(next)
} else {
builder.SetInsertPointAtEnd(inst.InstructionParent())
}
builder.CreateCall(getLifetimeEndFunc(mod), []llvm.Value{size, bitcast}, "")
return alloca
}
// EmitLifetimeEnd signals the end of an (alloca) lifetime by calling the
// llvm.lifetime.end intrinsic. It is commonly used together with
// createTemporaryAlloca.
func EmitLifetimeEnd(builder llvm.Builder, mod llvm.Module, ptr, size llvm.Value) {
builder.CreateCall(getLifetimeEndFunc(mod), []llvm.Value{size, ptr}, "")
}
// getLifetimeStartFunc returns the llvm.lifetime.start intrinsic and creates it
// first if it doesn't exist yet.
func getLifetimeStartFunc(mod llvm.Module) llvm.Value {
fn := mod.NamedFunction("llvm.lifetime.start.p0i8")
ctx := mod.Context()
i8ptrType := llvm.PointerType(ctx.Int8Type(), 0)
if fn.IsNil() {
fnType := llvm.FunctionType(ctx.VoidType(), []llvm.Type{ctx.Int64Type(), i8ptrType}, false)
fn = llvm.AddFunction(mod, "llvm.lifetime.start.p0i8", fnType)
}
return fn
}
// getLifetimeEndFunc returns the llvm.lifetime.end intrinsic and creates it
// first if it doesn't exist yet.
func getLifetimeEndFunc(mod llvm.Module) llvm.Value {
fn := mod.NamedFunction("llvm.lifetime.end.p0i8")
ctx := mod.Context()
i8ptrType := llvm.PointerType(ctx.Int8Type(), 0)
if fn.IsNil() {
fnType := llvm.FunctionType(ctx.VoidType(), []llvm.Type{ctx.Int64Type(), i8ptrType}, false)
fn = llvm.AddFunction(mod, "llvm.lifetime.end.p0i8", fnType)
}
return fn
}
// SplitBasicBlock splits a LLVM basic block into two parts. All instructions
// after afterInst are moved into a new basic block (created right after the
// current one) with the given name.
func SplitBasicBlock(builder llvm.Builder, afterInst llvm.Value, insertAfter llvm.BasicBlock, name string) llvm.BasicBlock {
oldBlock := afterInst.InstructionParent()
newBlock := afterInst.Type().Context().InsertBasicBlock(insertAfter, name)
var nextInstructions []llvm.Value // values to move
// Collect to-be-moved instructions.
inst := afterInst
for {
inst = llvm.NextInstruction(inst)
if inst.IsNil() {
break
}
nextInstructions = append(nextInstructions, inst)
}
// Move instructions.
builder.SetInsertPointAtEnd(newBlock)
for _, inst := range nextInstructions {
inst.RemoveFromParentAsInstruction()
builder.Insert(inst)
}
// Find PHI nodes to update.
var phiNodes []llvm.Value // PHI nodes to update
for bb := insertAfter.Parent().FirstBasicBlock(); !bb.IsNil(); bb = llvm.NextBasicBlock(bb) {
for inst := bb.FirstInstruction(); !inst.IsNil(); inst = llvm.NextInstruction(inst) {
if inst.IsAPHINode().IsNil() {
continue
}
needsUpdate := false
incomingCount := inst.IncomingCount()
for i := 0; i < incomingCount; i++ {
if inst.IncomingBlock(i) == oldBlock {
needsUpdate = true
break
}
}
if !needsUpdate {
// PHI node has no incoming edge from the old block.
continue
}
phiNodes = append(phiNodes, inst)
}
}
// Update PHI nodes.
for _, phi := range phiNodes {
builder.SetInsertPointBefore(phi)
newPhi := builder.CreatePHI(phi.Type(), "")
incomingCount := phi.IncomingCount()
incomingVals := make([]llvm.Value, incomingCount)
incomingBlocks := make([]llvm.BasicBlock, incomingCount)
for i := 0; i < incomingCount; i++ {
value := phi.IncomingValue(i)
block := phi.IncomingBlock(i)
if block == oldBlock {
block = newBlock
}
incomingVals[i] = value
incomingBlocks[i] = block
}
newPhi.AddIncoming(incomingVals, incomingBlocks)
phi.ReplaceAllUsesWith(newPhi)
phi.EraseFromParentAsInstruction()
}
return newBlock
}
-163
View File
@@ -1,163 +0,0 @@
package llvmutil
// This file contains utility functions to pack and unpack sets of values. It
// can take in a list of values and tries to store it efficiently in the pointer
// itself if possible and legal.
import (
"github.com/tinygo-org/tinygo/compileopts"
"tinygo.org/x/go-llvm"
)
// EmitPointerPack packs the list of values into a single pointer value using
// bitcasts, or else allocates a value on the heap if it cannot be packed in the
// pointer value directly. It returns the pointer with the packed data.
// If the values are all constants, they are be stored in a constant global and deduplicated.
func EmitPointerPack(builder llvm.Builder, mod llvm.Module, config *compileopts.Config, values []llvm.Value) llvm.Value {
ctx := mod.Context()
targetData := llvm.NewTargetData(mod.DataLayout())
i8ptrType := llvm.PointerType(mod.Context().Int8Type(), 0)
uintptrType := ctx.IntType(llvm.NewTargetData(mod.DataLayout()).PointerSize() * 8)
valueTypes := make([]llvm.Type, len(values))
for i, value := range values {
valueTypes[i] = value.Type()
}
packedType := ctx.StructType(valueTypes, false)
// Allocate memory for the packed data.
var packedAlloc, packedHeapAlloc llvm.Value
size := targetData.TypeAllocSize(packedType)
if size == 0 {
return llvm.ConstPointerNull(i8ptrType)
} else if len(values) == 1 && values[0].Type().TypeKind() == llvm.PointerTypeKind {
return builder.CreateBitCast(values[0], i8ptrType, "pack.ptr")
} else if size <= targetData.TypeAllocSize(i8ptrType) {
// Packed data fits in a pointer, so store it directly inside the
// pointer.
if len(values) == 1 && values[0].Type().TypeKind() == llvm.IntegerTypeKind {
// Try to keep this cast in SSA form.
return builder.CreateIntToPtr(values[0], i8ptrType, "pack.int")
}
// Because packedType is a struct and we have to cast it to a *i8, store
// it in an alloca first for bitcasting (store+bitcast+load).
packedAlloc, _, _ = CreateTemporaryAlloca(builder, mod, packedType, "")
} else {
// Check if the values are all constants.
constant := true
for _, v := range values {
if !v.IsConstant() {
constant = false
break
}
}
if constant {
// 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.
funcName := builder.GetInsertBlock().Parent().Name()
global := llvm.AddGlobal(mod, packedType, funcName+"$pack")
global.SetInitializer(ctx.ConstStruct(values, false))
global.SetGlobalConstant(true)
global.SetUnnamedAddr(true)
global.SetLinkage(llvm.PrivateLinkage)
return llvm.ConstBitCast(global, i8ptrType)
}
// Packed data is bigger than a pointer, so allocate it on the heap.
sizeValue := llvm.ConstInt(uintptrType, size, false)
alloc := mod.NamedFunction("runtime.alloc")
packedHeapAlloc = builder.CreateCall(alloc, []llvm.Value{
sizeValue,
llvm.Undef(i8ptrType), // unused context parameter
llvm.ConstPointerNull(i8ptrType), // coroutine handle
}, "")
if config.NeedsStackObjects() {
trackPointer := mod.NamedFunction("runtime.trackPointer")
builder.CreateCall(trackPointer, []llvm.Value{
packedHeapAlloc,
llvm.Undef(i8ptrType), // unused context parameter
llvm.ConstPointerNull(i8ptrType), // coroutine handle
}, "")
}
packedAlloc = builder.CreateBitCast(packedHeapAlloc, llvm.PointerType(packedType, 0), "")
}
// Store all values in the alloca or heap pointer.
for i, value := range values {
indices := []llvm.Value{
llvm.ConstInt(ctx.Int32Type(), 0, false),
llvm.ConstInt(ctx.Int32Type(), uint64(i), false),
}
gep := builder.CreateInBoundsGEP(packedAlloc, indices, "")
builder.CreateStore(value, gep)
}
if packedHeapAlloc.IsNil() {
// Load value (as *i8) from the alloca.
packedAlloc = builder.CreateBitCast(packedAlloc, llvm.PointerType(i8ptrType, 0), "")
result := builder.CreateLoad(packedAlloc, "")
packedPtr := builder.CreateBitCast(packedAlloc, i8ptrType, "")
packedSize := llvm.ConstInt(ctx.Int64Type(), targetData.TypeAllocSize(packedAlloc.Type()), false)
EmitLifetimeEnd(builder, mod, packedPtr, packedSize)
return result
} else {
// Get the original heap allocation pointer, which already is an *i8.
return packedHeapAlloc
}
}
// EmitPointerUnpack extracts a list of values packed using EmitPointerPack.
func EmitPointerUnpack(builder llvm.Builder, mod llvm.Module, ptr llvm.Value, valueTypes []llvm.Type) []llvm.Value {
ctx := mod.Context()
targetData := llvm.NewTargetData(mod.DataLayout())
i8ptrType := llvm.PointerType(mod.Context().Int8Type(), 0)
uintptrType := ctx.IntType(llvm.NewTargetData(mod.DataLayout()).PointerSize() * 8)
packedType := ctx.StructType(valueTypes, false)
// Get a correctly-typed pointer to the packed data.
var packedAlloc, packedRawAlloc llvm.Value
size := targetData.TypeAllocSize(packedType)
if size == 0 {
// No data to unpack.
} else if len(valueTypes) == 1 && valueTypes[0].TypeKind() == llvm.PointerTypeKind {
// A single pointer is always stored directly.
return []llvm.Value{builder.CreateBitCast(ptr, valueTypes[0], "unpack.ptr")}
} else if size <= targetData.TypeAllocSize(i8ptrType) {
// Packed data stored directly in pointer.
if len(valueTypes) == 1 && valueTypes[0].TypeKind() == llvm.IntegerTypeKind {
// Keep this cast in SSA form.
return []llvm.Value{builder.CreatePtrToInt(ptr, valueTypes[0], "unpack.int")}
}
// Fallback: load it using an alloca.
packedRawAlloc, _, _ = CreateTemporaryAlloca(builder, mod, llvm.PointerType(i8ptrType, 0), "unpack.raw.alloc")
packedRawValue := builder.CreateBitCast(ptr, llvm.PointerType(i8ptrType, 0), "unpack.raw.value")
builder.CreateStore(packedRawValue, packedRawAlloc)
packedAlloc = builder.CreateBitCast(packedRawAlloc, llvm.PointerType(packedType, 0), "unpack.alloc")
} else {
// Packed data stored on the heap. Bitcast the passed-in pointer to the
// correct pointer type.
packedAlloc = builder.CreateBitCast(ptr, llvm.PointerType(packedType, 0), "unpack.raw.ptr")
}
// Load each value from the packed data.
values := make([]llvm.Value, len(valueTypes))
for i, valueType := range valueTypes {
if targetData.TypeAllocSize(valueType) == 0 {
// This value has length zero, so there's nothing to load.
values[i] = llvm.ConstNull(valueType)
continue
}
indices := []llvm.Value{
llvm.ConstInt(ctx.Int32Type(), 0, false),
llvm.ConstInt(ctx.Int32Type(), uint64(i), false),
}
gep := builder.CreateInBoundsGEP(packedAlloc, indices, "")
values[i] = builder.CreateLoad(gep, "")
}
if !packedRawAlloc.IsNil() {
allocPtr := builder.CreateBitCast(packedRawAlloc, i8ptrType, "")
allocSize := llvm.ConstInt(ctx.Int64Type(), targetData.TypeAllocSize(uintptrType), false)
EmitLifetimeEnd(builder, mod, allocPtr, allocSize)
}
return values
}
+56 -108
View File
@@ -6,167 +6,115 @@ import (
"go/token"
"go/types"
"golang.org/x/tools/go/ssa"
"tinygo.org/x/go-llvm"
)
// createMakeMap creates a new map object (runtime.hashmap) by allocating and
// initializing an appropriately sized object.
func (b *builder) createMakeMap(expr *ssa.MakeMap) (llvm.Value, error) {
mapType := expr.Type().Underlying().(*types.Map)
keyType := mapType.Key().Underlying()
llvmValueType := b.getLLVMType(mapType.Elem().Underlying())
var llvmKeyType llvm.Type
if t, ok := keyType.(*types.Basic); ok && t.Info()&types.IsString != 0 {
// String keys.
llvmKeyType = b.getLLVMType(keyType)
} else if hashmapIsBinaryKey(keyType) {
// Trivially comparable keys.
llvmKeyType = b.getLLVMType(keyType)
} else {
// All other keys. Implemented as map[interface{}]valueType for ease of
// implementation.
llvmKeyType = b.getLLVMRuntimeType("_interface")
}
keySize := b.targetData.TypeAllocSize(llvmKeyType)
valueSize := b.targetData.TypeAllocSize(llvmValueType)
llvmKeySize := llvm.ConstInt(b.ctx.Int8Type(), keySize, false)
llvmValueSize := llvm.ConstInt(b.ctx.Int8Type(), valueSize, false)
sizeHint := llvm.ConstInt(b.uintptrType, 8, false)
if expr.Reserve != nil {
sizeHint = b.getValue(expr.Reserve)
var err error
sizeHint, err = b.createConvert(expr.Reserve.Type(), types.Typ[types.Uintptr], sizeHint, expr.Pos())
if err != nil {
return llvm.Value{}, err
}
}
hashmap := b.createRuntimeCall("hashmapMake", []llvm.Value{llvmKeySize, llvmValueSize, sizeHint}, "")
return hashmap, nil
}
// createMapLookup returns the value in a map. It calls a runtime function
// depending on the map key type to load the map value and its comma-ok value.
func (b *builder) createMapLookup(keyType, valueType types.Type, m, key llvm.Value, commaOk bool, pos token.Pos) (llvm.Value, error) {
llvmValueType := b.getLLVMType(valueType)
func (c *Compiler) emitMapLookup(keyType, valueType types.Type, m, key llvm.Value, commaOk bool, pos token.Pos) (llvm.Value, error) {
llvmValueType := c.getLLVMType(valueType)
// Allocate the memory for the resulting type. Do not zero this memory: it
// will be zeroed by the hashmap get implementation if the key is not
// present in the map.
mapValueAlloca, mapValuePtr, mapValueAllocaSize := b.createTemporaryAlloca(llvmValueType, "hashmap.value")
// We need the map size (with type uintptr) to pass to the hashmap*Get
// functions. This is necessary because those *Get functions are valid on
// nil maps, and they'll need to zero the value pointer by that number of
// bytes.
mapValueSize := mapValueAllocaSize
if mapValueSize.Type().IntTypeWidth() > b.uintptrType.IntTypeWidth() {
mapValueSize = llvm.ConstTrunc(mapValueSize, b.uintptrType)
}
mapValueAlloca, mapValuePtr, mapValueSize := c.createTemporaryAlloca(llvmValueType, "hashmap.value")
// Do the lookup. How it is done depends on the key type.
var commaOkValue llvm.Value
keyType = keyType.Underlying()
if t, ok := keyType.(*types.Basic); ok && t.Info()&types.IsString != 0 {
// key is a string
params := []llvm.Value{m, key, mapValuePtr, mapValueSize}
commaOkValue = b.createRuntimeCall("hashmapStringGet", params, "")
params := []llvm.Value{m, key, mapValuePtr}
commaOkValue = c.createRuntimeCall("hashmapStringGet", params, "")
} else if hashmapIsBinaryKey(keyType) {
// key can be compared with runtime.memequal
// Store the key in an alloca, in the entry block to avoid dynamic stack
// growth.
mapKeyAlloca, mapKeyPtr, mapKeySize := b.createTemporaryAlloca(key.Type(), "hashmap.key")
b.CreateStore(key, mapKeyAlloca)
mapKeyAlloca, mapKeyPtr, mapKeySize := c.createTemporaryAlloca(key.Type(), "hashmap.key")
c.builder.CreateStore(key, mapKeyAlloca)
// Fetch the value from the hashmap.
params := []llvm.Value{m, mapKeyPtr, mapValuePtr, mapValueSize}
commaOkValue = b.createRuntimeCall("hashmapBinaryGet", params, "")
b.emitLifetimeEnd(mapKeyPtr, mapKeySize)
params := []llvm.Value{m, mapKeyPtr, mapValuePtr}
commaOkValue = c.createRuntimeCall("hashmapBinaryGet", params, "")
c.emitLifetimeEnd(mapKeyPtr, mapKeySize)
} else {
// Not trivially comparable using memcmp. Make it an interface instead.
itfKey := key
if _, ok := keyType.(*types.Interface); !ok {
// Not already an interface, so convert it to an interface now.
itfKey = b.createMakeInterface(key, keyType, pos)
}
params := []llvm.Value{m, itfKey, mapValuePtr, mapValueSize}
commaOkValue = b.createRuntimeCall("hashmapInterfaceGet", params, "")
// Not trivially comparable using memcmp.
return llvm.Value{}, c.makeError(pos, "only strings, bools, ints, pointers or structs of bools/ints are supported as map keys, but got: "+keyType.String())
}
// Load the resulting value from the hashmap. The value is set to the zero
// value if the key doesn't exist in the hashmap.
mapValue := b.CreateLoad(mapValueAlloca, "")
b.emitLifetimeEnd(mapValuePtr, mapValueAllocaSize)
mapValue := c.builder.CreateLoad(mapValueAlloca, "")
c.emitLifetimeEnd(mapValuePtr, mapValueSize)
if commaOk {
tuple := llvm.Undef(b.ctx.StructType([]llvm.Type{llvmValueType, b.ctx.Int1Type()}, false))
tuple = b.CreateInsertValue(tuple, mapValue, 0, "")
tuple = b.CreateInsertValue(tuple, commaOkValue, 1, "")
tuple := llvm.Undef(c.ctx.StructType([]llvm.Type{llvmValueType, c.ctx.Int1Type()}, false))
tuple = c.builder.CreateInsertValue(tuple, mapValue, 0, "")
tuple = c.builder.CreateInsertValue(tuple, commaOkValue, 1, "")
return tuple, nil
} else {
return mapValue, nil
}
}
// createMapUpdate updates a map key to a given value, by creating an
// appropriate runtime call.
func (b *builder) createMapUpdate(keyType types.Type, m, key, value llvm.Value, pos token.Pos) {
valueAlloca, valuePtr, valueSize := b.createTemporaryAlloca(value.Type(), "hashmap.value")
b.CreateStore(value, valueAlloca)
func (c *Compiler) emitMapUpdate(keyType types.Type, m, key, value llvm.Value, pos token.Pos) {
valueAlloca, valuePtr, valueSize := c.createTemporaryAlloca(value.Type(), "hashmap.value")
c.builder.CreateStore(value, valueAlloca)
keyType = keyType.Underlying()
if t, ok := keyType.(*types.Basic); ok && t.Info()&types.IsString != 0 {
// key is a string
params := []llvm.Value{m, key, valuePtr}
b.createRuntimeCall("hashmapStringSet", params, "")
c.createRuntimeCall("hashmapStringSet", params, "")
} else if hashmapIsBinaryKey(keyType) {
// key can be compared with runtime.memequal
keyAlloca, keyPtr, keySize := b.createTemporaryAlloca(key.Type(), "hashmap.key")
b.CreateStore(key, keyAlloca)
keyAlloca, keyPtr, keySize := c.createTemporaryAlloca(key.Type(), "hashmap.key")
c.builder.CreateStore(key, keyAlloca)
params := []llvm.Value{m, keyPtr, valuePtr}
b.createRuntimeCall("hashmapBinarySet", params, "")
b.emitLifetimeEnd(keyPtr, keySize)
c.createRuntimeCall("hashmapBinarySet", params, "")
c.emitLifetimeEnd(keyPtr, keySize)
} else {
// Key is not trivially comparable, so compare it as an interface instead.
itfKey := key
if _, ok := keyType.(*types.Interface); !ok {
// Not already an interface, so convert it to an interface first.
itfKey = b.createMakeInterface(key, keyType, pos)
}
params := []llvm.Value{m, itfKey, valuePtr}
b.createRuntimeCall("hashmapInterfaceSet", params, "")
c.addError(pos, "only strings, bools, ints, pointers or structs of bools/ints are supported as map keys, but got: "+keyType.String())
}
b.emitLifetimeEnd(valuePtr, valueSize)
c.emitLifetimeEnd(valuePtr, valueSize)
}
// createMapDelete deletes a key from a map by calling the appropriate runtime
// function. It is the implementation of the Go delete() builtin.
func (b *builder) createMapDelete(keyType types.Type, m, key llvm.Value, pos token.Pos) error {
func (c *Compiler) emitMapDelete(keyType types.Type, m, key llvm.Value, pos token.Pos) error {
keyType = keyType.Underlying()
if t, ok := keyType.(*types.Basic); ok && t.Info()&types.IsString != 0 {
// key is a string
params := []llvm.Value{m, key}
b.createRuntimeCall("hashmapStringDelete", params, "")
c.createRuntimeCall("hashmapStringDelete", params, "")
return nil
} else if hashmapIsBinaryKey(keyType) {
keyAlloca, keyPtr, keySize := b.createTemporaryAlloca(key.Type(), "hashmap.key")
b.CreateStore(key, keyAlloca)
keyAlloca, keyPtr, keySize := c.createTemporaryAlloca(key.Type(), "hashmap.key")
c.builder.CreateStore(key, keyAlloca)
params := []llvm.Value{m, keyPtr}
b.createRuntimeCall("hashmapBinaryDelete", params, "")
b.emitLifetimeEnd(keyPtr, keySize)
c.createRuntimeCall("hashmapBinaryDelete", params, "")
c.emitLifetimeEnd(keyPtr, keySize)
return nil
} else {
// Key is not trivially comparable, so compare it as an interface
// instead.
itfKey := key
if _, ok := keyType.(*types.Interface); !ok {
// Not already an interface, so convert it to an interface first.
itfKey = b.createMakeInterface(key, keyType, pos)
}
params := []llvm.Value{m, itfKey}
b.createRuntimeCall("hashmapInterfaceDelete", params, "")
return nil
return c.makeError(pos, "only strings, bools, ints, pointers or structs of bools/ints are supported as map keys, but got: "+keyType.String())
}
}
// Get FNV-1a hash of this string.
//
// https://en.wikipedia.org/wiki/Fowler%E2%80%93Noll%E2%80%93Vo_hash_function#FNV-1a_hash
func hashmapHash(data []byte) uint32 {
var result uint32 = 2166136261 // FNV offset basis
for _, c := range data {
result ^= uint32(c)
result *= 16777619 // FNV prime
}
return result
}
// Get the topmost 8 bits of the hash, without using a special value (like 0).
func hashmapTopHash(hash uint32) uint8 {
tophash := uint8(hash >> 24)
if tophash < 1 {
// 0 means empty slot, so make it bigger.
tophash += 1
}
return tophash
}
// Returns true if this key type does not contain strings, interfaces etc., so
// can be compared with runtime.memequal.
func hashmapIsBinaryKey(keyType types.Type) bool {
+397
View File
@@ -0,0 +1,397 @@
package compiler
import (
"errors"
"tinygo.org/x/go-llvm"
)
// Run the LLVM optimizer over the module.
// The inliner can be disabled (if necessary) by passing 0 to the inlinerThreshold.
func (c *Compiler) Optimize(optLevel, sizeLevel int, inlinerThreshold uint) error {
builder := llvm.NewPassManagerBuilder()
defer builder.Dispose()
builder.SetOptLevel(optLevel)
builder.SetSizeLevel(sizeLevel)
if inlinerThreshold != 0 {
builder.UseInlinerWithThreshold(inlinerThreshold)
}
builder.AddCoroutinePassesToExtensionPoints()
if c.PanicStrategy == "trap" {
c.replacePanicsWithTrap() // -panic=trap
}
// run a check of all of our code
c.check()
if err := c.Verify(); err != nil {
return errors.New("verification failure before optimizations")
}
// Run function passes for each function.
funcPasses := llvm.NewFunctionPassManagerForModule(c.mod)
defer funcPasses.Dispose()
builder.PopulateFunc(funcPasses)
funcPasses.InitializeFunc()
for fn := c.mod.FirstFunction(); !fn.IsNil(); fn = llvm.NextFunction(fn) {
funcPasses.RunFunc(fn)
}
funcPasses.FinalizeFunc()
if optLevel > 0 {
// Run some preparatory passes for the Go optimizer.
goPasses := llvm.NewPassManager()
defer goPasses.Dispose()
goPasses.AddGlobalOptimizerPass()
goPasses.AddConstantPropagationPass()
goPasses.AddAggressiveDCEPass()
goPasses.AddFunctionAttrsPass()
goPasses.Run(c.mod)
// Run Go-specific optimization passes.
c.OptimizeMaps()
c.OptimizeStringToBytes()
c.OptimizeAllocs()
c.LowerInterfaces()
c.LowerFuncValues()
// After interfaces are lowered, there are many more opportunities for
// interprocedural optimizations. To get them to work, function
// attributes have to be updated first.
goPasses.Run(c.mod)
// Run TinyGo-specific interprocedural optimizations.
c.OptimizeAllocs()
c.OptimizeStringToBytes()
// Lower runtime.isnil calls to regular nil comparisons.
isnil := c.mod.NamedFunction("runtime.isnil")
if !isnil.IsNil() {
for _, use := range getUses(isnil) {
c.builder.SetInsertPointBefore(use)
ptr := use.Operand(0)
if !ptr.IsABitCastInst().IsNil() {
ptr = ptr.Operand(0)
}
nilptr := llvm.ConstPointerNull(ptr.Type())
icmp := c.builder.CreateICmp(llvm.IntEQ, ptr, nilptr, "")
use.ReplaceAllUsesWith(icmp)
use.EraseFromParentAsInstruction()
}
}
err := c.LowerGoroutines()
if err != nil {
return err
}
} else {
// Must be run at any optimization level.
c.LowerInterfaces()
c.LowerFuncValues()
err := c.LowerGoroutines()
if err != nil {
return err
}
}
if err := c.Verify(); err != nil {
return errors.New("optimizations caused a verification failure")
}
if sizeLevel >= 2 {
// Set the "optsize" attribute to make slightly smaller binaries at the
// cost of some performance.
kind := llvm.AttributeKindID("optsize")
attr := c.ctx.CreateEnumAttribute(kind, 0)
for fn := c.mod.FirstFunction(); !fn.IsNil(); fn = llvm.NextFunction(fn) {
fn.AddFunctionAttr(attr)
}
}
// After TinyGo-specific transforms have finished, undo exporting these functions.
for _, name := range functionsUsedInTransforms {
fn := c.mod.NamedFunction(name)
if fn.IsNil() {
continue
}
fn.SetLinkage(llvm.InternalLinkage)
}
// Run function passes again, because without it, llvm.coro.size.i32()
// doesn't get lowered.
for fn := c.mod.FirstFunction(); !fn.IsNil(); fn = llvm.NextFunction(fn) {
funcPasses.RunFunc(fn)
}
funcPasses.FinalizeFunc()
// Run module passes.
modPasses := llvm.NewPassManager()
defer modPasses.Dispose()
builder.Populate(modPasses)
modPasses.Run(c.mod)
hasGCPass := c.addGlobalsBitmap()
hasGCPass = c.makeGCStackSlots() || hasGCPass
if hasGCPass {
if err := c.Verify(); err != nil {
return errors.New("GC pass caused a verification failure")
}
}
return nil
}
// Replace panic calls with calls to llvm.trap, to reduce code size. This is the
// -panic=trap intrinsic.
func (c *Compiler) replacePanicsWithTrap() {
trap := c.mod.NamedFunction("llvm.trap")
for _, name := range []string{"runtime._panic", "runtime.runtimePanic"} {
fn := c.mod.NamedFunction(name)
if fn.IsNil() {
continue
}
for _, use := range getUses(fn) {
if use.IsACallInst().IsNil() || use.CalledValue() != fn {
panic("expected use of a panic function to be a call")
}
c.builder.SetInsertPointBefore(use)
c.builder.CreateCall(trap, nil, "")
}
}
}
// Eliminate created but not used maps.
//
// In the future, this should statically allocate created but never modified
// maps. This has not yet been implemented, however.
func (c *Compiler) OptimizeMaps() {
hashmapMake := c.mod.NamedFunction("runtime.hashmapMake")
if hashmapMake.IsNil() {
// nothing to optimize
return
}
hashmapBinarySet := c.mod.NamedFunction("runtime.hashmapBinarySet")
hashmapStringSet := c.mod.NamedFunction("runtime.hashmapStringSet")
for _, makeInst := range getUses(hashmapMake) {
updateInsts := []llvm.Value{}
unknownUses := false // are there any uses other than setting a value?
for _, use := range getUses(makeInst) {
if use := use.IsACallInst(); !use.IsNil() {
switch use.CalledValue() {
case hashmapBinarySet, hashmapStringSet:
updateInsts = append(updateInsts, use)
default:
unknownUses = true
}
} else {
unknownUses = true
}
}
if !unknownUses {
// This map can be entirely removed, as it is only created but never
// used.
for _, inst := range updateInsts {
inst.EraseFromParentAsInstruction()
}
makeInst.EraseFromParentAsInstruction()
}
}
}
// Transform runtime.stringToBytes(...) calls into const []byte slices whenever
// possible. This optimizes the following pattern:
// w.Write([]byte("foo"))
// where Write does not store to the slice.
func (c *Compiler) OptimizeStringToBytes() {
stringToBytes := c.mod.NamedFunction("runtime.stringToBytes")
if stringToBytes.IsNil() {
// nothing to optimize
return
}
for _, call := range getUses(stringToBytes) {
strptr := call.Operand(0)
strlen := call.Operand(1)
// strptr is always constant because strings are always constant.
convertedAllUses := true
for _, use := range getUses(call) {
nilValue := llvm.Value{}
if use.IsAExtractValueInst() == nilValue {
convertedAllUses = false
continue
}
switch use.Type().TypeKind() {
case llvm.IntegerTypeKind:
// A length (len or cap). Propagate the length value.
use.ReplaceAllUsesWith(strlen)
use.EraseFromParentAsInstruction()
case llvm.PointerTypeKind:
// The string pointer itself.
if !c.isReadOnly(use) {
convertedAllUses = false
continue
}
use.ReplaceAllUsesWith(strptr)
use.EraseFromParentAsInstruction()
default:
// should not happen
panic("unknown return type of runtime.stringToBytes: " + use.Type().String())
}
}
if convertedAllUses {
// Call to runtime.stringToBytes can be eliminated: both the input
// and the output is constant.
call.EraseFromParentAsInstruction()
}
}
}
// Basic escape analysis: translate runtime.alloc calls into alloca
// instructions.
func (c *Compiler) OptimizeAllocs() {
allocator := c.mod.NamedFunction("runtime.alloc")
if allocator.IsNil() {
// nothing to optimize
return
}
heapallocs := getUses(allocator)
for _, heapalloc := range heapallocs {
nilValue := llvm.Value{}
if heapalloc.Operand(0).IsAConstant() == nilValue {
// Do not allocate variable length arrays on the stack.
continue
}
size := heapalloc.Operand(0).ZExtValue()
if size > 256 {
// The maximum value for a stack allocation.
// TODO: tune this, this is just a random value.
continue
}
// In general the pattern is:
// %0 = call i8* @runtime.alloc(i32 %size)
// %1 = bitcast i8* %0 to type*
// (use %1 only)
// But the bitcast might sometimes be dropped when allocating an *i8.
// The 'bitcast' variable below is thus usually a bitcast of the
// heapalloc but not always.
bitcast := heapalloc // instruction that creates the value
if uses := getUses(heapalloc); len(uses) == 1 && uses[0].IsABitCastInst() != nilValue {
// getting only bitcast use
bitcast = uses[0]
}
if !c.doesEscape(bitcast) {
// Insert alloca in the entry block. Do it here so that mem2reg can
// promote it to a SSA value.
fn := bitcast.InstructionParent().Parent()
c.builder.SetInsertPointBefore(fn.EntryBasicBlock().FirstInstruction())
alignment := c.targetData.ABITypeAlignment(c.i8ptrType)
sizeInWords := (size + uint64(alignment) - 1) / uint64(alignment)
allocaType := llvm.ArrayType(c.ctx.IntType(alignment*8), int(sizeInWords))
alloca := c.builder.CreateAlloca(allocaType, "stackalloc.alloca")
zero := c.getZeroValue(alloca.Type().ElementType())
c.builder.CreateStore(zero, alloca)
stackalloc := c.builder.CreateBitCast(alloca, bitcast.Type(), "stackalloc")
bitcast.ReplaceAllUsesWith(stackalloc)
if heapalloc != bitcast {
bitcast.EraseFromParentAsInstruction()
}
heapalloc.EraseFromParentAsInstruction()
}
}
}
// Very basic escape analysis.
func (c *Compiler) doesEscape(value llvm.Value) bool {
uses := getUses(value)
for _, use := range uses {
nilValue := llvm.Value{}
if use.IsAGetElementPtrInst() != nilValue {
if c.doesEscape(use) {
return true
}
} else if use.IsABitCastInst() != nilValue {
// A bitcast escapes if the casted-to value escapes.
if c.doesEscape(use) {
return true
}
} else if use.IsALoadInst() != nilValue {
// Load does not escape.
} else if use.IsAStoreInst() != nilValue {
// Store only escapes when the value is stored to, not when the
// value is stored into another value.
if use.Operand(0) == value {
return true
}
} else if use.IsACallInst() != nilValue {
if !c.hasFlag(use, value, "nocapture") {
return true
}
} else if use.IsAICmpInst() != nilValue {
// Comparing pointers don't let the pointer escape.
// This is often a compiler-inserted nil check.
} else {
// Unknown instruction, might escape.
return true
}
}
// does not escape
return false
}
// Check whether the given value (which is of pointer type) is never stored to.
func (c *Compiler) isReadOnly(value llvm.Value) bool {
uses := getUses(value)
for _, use := range uses {
nilValue := llvm.Value{}
if use.IsAGetElementPtrInst() != nilValue {
if !c.isReadOnly(use) {
return false
}
} else if use.IsACallInst() != nilValue {
if !c.hasFlag(use, value, "readonly") {
return false
}
} else {
// Unknown instruction, might not be readonly.
return false
}
}
return true
}
// Check whether all uses of this param as parameter to the call have the given
// flag. In most cases, there will only be one use but a function could take the
// same parameter twice, in which case both must have the flag.
// A flag can be any enum flag, like "readonly".
func (c *Compiler) hasFlag(call, param llvm.Value, kind string) bool {
fn := call.CalledValue()
nilValue := llvm.Value{}
if fn.IsAFunction() == nilValue {
// This is not a function but something else, like a function pointer.
return false
}
kindID := llvm.AttributeKindID(kind)
for i := 0; i < fn.ParamsCount(); i++ {
if call.Operand(i) != param {
// This is not the parameter we're checking.
continue
}
index := i + 1 // param attributes start at 1
attr := fn.GetEnumAttributeAtIndex(index, kindID)
nilAttribute := llvm.Attribute{}
if attr == nilAttribute {
// At least one parameter doesn't have the flag (there may be
// multiple).
return false
}
}
return true
}
+21 -18
View File
@@ -1,4 +1,4 @@
package transform
package compiler
// This file has some compiler support for run-time reflection using the reflect
// package. In particular, it encodes type information in type codes in such a
@@ -125,27 +125,34 @@ type typeCodeAssignmentState struct {
// assignTypeCodes is used to assign a type code to each type in the program
// that is ever stored in an interface. It tries to use the smallest possible
// numbers to make the code that works with interfaces as small as possible.
func assignTypeCodes(mod llvm.Module, typeSlice typeInfoSlice) {
// if reflect were not used, we could skip generating the sidetable
// this does not help in practice, and is difficult to do correctly
func (c *Compiler) assignTypeCodes(typeSlice typeInfoSlice) {
fn := c.mod.NamedFunction("reflect.ValueOf")
if fn.IsNil() {
// reflect.ValueOf is never used, so we can use the most efficient
// encoding possible.
for i, t := range typeSlice {
t.num = uint64(i + 1)
}
return
}
// Assign typecodes the way the reflect package expects.
state := typeCodeAssignmentState{
fallbackIndex: 1,
uintptrLen: llvm.NewTargetData(mod.DataLayout()).PointerSize() * 8,
uintptrLen: c.uintptrType.IntTypeWidth(),
namedBasicTypes: make(map[string]int),
namedNonBasicTypes: make(map[string]int),
arrayTypes: make(map[string]int),
structTypes: make(map[string]int),
structNames: make(map[string]int),
needsNamedNonBasicTypesSidetable: len(getUses(mod.NamedGlobal("reflect.namedNonBasicTypesSidetable"))) != 0,
needsStructTypesSidetable: len(getUses(mod.NamedGlobal("reflect.structTypesSidetable"))) != 0,
needsStructNamesSidetable: len(getUses(mod.NamedGlobal("reflect.structNamesSidetable"))) != 0,
needsArrayTypesSidetable: len(getUses(mod.NamedGlobal("reflect.arrayTypesSidetable"))) != 0,
needsNamedNonBasicTypesSidetable: len(getUses(c.mod.NamedGlobal("reflect.namedNonBasicTypesSidetable"))) != 0,
needsStructTypesSidetable: len(getUses(c.mod.NamedGlobal("reflect.structTypesSidetable"))) != 0,
needsStructNamesSidetable: len(getUses(c.mod.NamedGlobal("reflect.structNamesSidetable"))) != 0,
needsArrayTypesSidetable: len(getUses(c.mod.NamedGlobal("reflect.arrayTypesSidetable"))) != 0,
}
for _, t := range typeSlice {
num := state.getTypeCodeNum(t.typecode)
if num.BitLen() > state.uintptrLen || !num.IsUint64() {
if num.BitLen() > c.uintptrType.IntTypeWidth() || !num.IsUint64() {
// TODO: support this in some way, using a side table for example.
// That's less efficient but better than not working at all.
// Particularly important on systems with 16-bit pointers (e.g.
@@ -157,28 +164,24 @@ func assignTypeCodes(mod llvm.Module, typeSlice typeInfoSlice) {
// Only create this sidetable when it is necessary.
if state.needsNamedNonBasicTypesSidetable {
global := replaceGlobalIntWithArray(mod, "reflect.namedNonBasicTypesSidetable", state.namedNonBasicTypesSidetable)
global := c.replaceGlobalIntWithArray("reflect.namedNonBasicTypesSidetable", state.namedNonBasicTypesSidetable)
global.SetLinkage(llvm.InternalLinkage)
global.SetUnnamedAddr(true)
global.SetGlobalConstant(true)
}
if state.needsArrayTypesSidetable {
global := replaceGlobalIntWithArray(mod, "reflect.arrayTypesSidetable", state.arrayTypesSidetable)
global := c.replaceGlobalIntWithArray("reflect.arrayTypesSidetable", state.arrayTypesSidetable)
global.SetLinkage(llvm.InternalLinkage)
global.SetUnnamedAddr(true)
global.SetGlobalConstant(true)
}
if state.needsStructTypesSidetable {
global := replaceGlobalIntWithArray(mod, "reflect.structTypesSidetable", state.structTypesSidetable)
global := c.replaceGlobalIntWithArray("reflect.structTypesSidetable", state.structTypesSidetable)
global.SetLinkage(llvm.InternalLinkage)
global.SetUnnamedAddr(true)
global.SetGlobalConstant(true)
}
if state.needsStructNamesSidetable {
global := replaceGlobalIntWithArray(mod, "reflect.structNamesSidetable", state.structNamesSidetable)
global := c.replaceGlobalIntWithArray("reflect.structNamesSidetable", state.structNamesSidetable)
global.SetLinkage(llvm.InternalLinkage)
global.SetUnnamedAddr(true)
global.SetGlobalConstant(true)
}
}
+33 -13
View File
@@ -10,13 +10,13 @@ import (
// The original license can be found here:
// https://golang.org/LICENSE
type stdSizes struct {
type StdSizes struct {
IntSize int64
PtrSize int64
MaxAlign int64
}
func (s *stdSizes) Alignof(T types.Type) int64 {
func (s *StdSizes) Alignof(T types.Type) int64 {
// For arrays and structs, alignment is defined in terms
// of alignment of the elements and fields, respectively.
switch t := T.Underlying().(type) {
@@ -61,8 +61,14 @@ func (s *stdSizes) Alignof(T types.Type) int64 {
return a
}
func (s *stdSizes) Offsetsof(fields []*types.Var) []int64 {
func (s *StdSizes) Offsetsof(fields []*types.Var) []int64 {
offsets := make([]int64, len(fields))
if len(fields) > 1 && fields[0].Name() == "C union" {
// This struct contains the magic "C union" field which indicates that
// this is actually a union from CGo.
// All fields in the union start at 0 so return that.
return offsets // all fields are still set to 0
}
var o int64
for i, f := range fields {
a := s.Alignof(f.Type())
@@ -89,7 +95,7 @@ var basicSizes = [...]byte{
types.Complex128: 16,
}
func (s *stdSizes) Sizeof(T types.Type) int64 {
func (s *StdSizes) Sizeof(T types.Type) int64 {
switch t := T.Underlying().(type) {
case *types.Basic:
k := t.Kind()
@@ -110,9 +116,6 @@ func (s *stdSizes) Sizeof(T types.Type) int64 {
if k == types.UnsafePointer {
return s.PtrSize
}
if k == types.Invalid {
return 0 // only relevant when there is a type error somewhere
}
panic("unknown basic type: " + t.String())
case *types.Array:
n := t.Len()
@@ -140,12 +143,29 @@ func (s *stdSizes) Sizeof(T types.Type) int64 {
maxAlign = al
}
}
// Pick the size that fits this struct and add some alignment. Some
// structs have some extra padding at the end which should also be taken
// care of:
// struct { int32 n; byte b }
offsets := s.Offsetsof(fields)
return align(offsets[n-1]+s.Sizeof(fields[n-1].Type()), maxAlign)
if fields[0].Name() == "C union" {
// Magic field that indicates this is a CGo union and not a struct.
// The size is the biggest element, aligned to the element with the
// biggest alignment. This is not necessarily the same, for example
// in the following union:
// union { int32_t l; int16_t s[3] }
maxSize := int64(0)
for _, field := range fields[1:] {
si := s.Sizeof(field.Type())
if si > maxSize {
maxSize = si
}
}
return align(maxSize, maxAlign)
} else {
// This is a regular struct.
// Pick the size that fits this struct and add some alignment. Some
// structs have some extra padding at the end which should also be
// taken care of:
// struct { int32 n; byte b }
offsets := s.Offsetsof(fields)
return align(offsets[n-1]+s.Sizeof(fields[n-1].Type()), maxAlign)
}
case *types.Interface:
return s.PtrSize * 2
case *types.Pointer:
+5 -45
View File
@@ -7,7 +7,6 @@ import (
"go/ast"
"go/token"
"go/types"
"strconv"
"strings"
"github.com/tinygo-org/tinygo/loader"
@@ -21,12 +20,11 @@ import (
type globalInfo struct {
linkName string // go:extern
extern bool // go:extern
align int // go:align
}
// loadASTComments loads comments on globals from the AST, for use later in the
// program. In particular, they are required for //go:extern pragmas on globals.
func (c *compilerContext) loadASTComments(lprogram *loader.Program) {
func (c *Compiler) loadASTComments(lprogram *loader.Program) {
c.astComments = map[string]*ast.CommentGroup{}
for _, pkgInfo := range lprogram.Sorted() {
for _, file := range pkgInfo.Files {
@@ -56,55 +54,22 @@ func (c *compilerContext) loadASTComments(lprogram *loader.Program) {
// getGlobal returns a LLVM IR global value for a Go SSA global. It is added to
// the LLVM IR if it has not been added already.
func (c *compilerContext) getGlobal(g *ssa.Global) llvm.Value {
func (c *Compiler) getGlobal(g *ssa.Global) llvm.Value {
info := c.getGlobalInfo(g)
llvmGlobal := c.mod.NamedGlobal(info.linkName)
if llvmGlobal.IsNil() {
typ := g.Type().(*types.Pointer).Elem()
llvmType := c.getLLVMType(typ)
llvmType := c.getLLVMType(g.Type().(*types.Pointer).Elem())
llvmGlobal = llvm.AddGlobal(c.mod, llvmType, info.linkName)
if !info.extern {
llvmGlobal.SetInitializer(llvm.ConstNull(llvmType))
llvmGlobal.SetInitializer(c.getZeroValue(llvmType))
llvmGlobal.SetLinkage(llvm.InternalLinkage)
}
// Set alignment from the //go:align comment.
var alignInBits uint32
if info.align < 0 || info.align&(info.align-1) != 0 {
// Check for power-of-two (or 0).
// See: https://stackoverflow.com/a/108360
c.addError(g.Pos(), "global variable alignment must be a positive power of two")
} else {
// Set the alignment only when it is a power of two.
alignInBits = uint32(info.align) ^ uint32(info.align-1)
if info.align > c.targetData.ABITypeAlignment(llvmType) {
llvmGlobal.SetAlignment(info.align)
}
}
if c.Debug() && !info.extern {
// Add debug info.
// TODO: this should be done for every global in the program, not just
// the ones that are referenced from some code.
pos := c.ir.Program.Fset.Position(g.Pos())
diglobal := c.dibuilder.CreateGlobalVariableExpression(c.difiles[pos.Filename], llvm.DIGlobalVariableExpression{
Name: g.RelString(nil),
LinkageName: info.linkName,
File: c.getDIFile(pos.Filename),
Line: pos.Line,
Type: c.getDIType(typ),
LocalToUnit: false,
Expr: c.dibuilder.CreateExpression(nil),
AlignInBits: alignInBits,
})
llvmGlobal.AddMetadata(0, diglobal)
}
}
return llvmGlobal
}
// getGlobalInfo returns some information about a specific global.
func (c *compilerContext) getGlobalInfo(g *ssa.Global) globalInfo {
func (c *Compiler) getGlobalInfo(g *ssa.Global) globalInfo {
info := globalInfo{}
if strings.HasPrefix(g.Name(), "C.") {
// Created by CGo: such a name cannot be created by regular C code.
@@ -137,11 +102,6 @@ func (info *globalInfo) parsePragmas(doc *ast.CommentGroup) {
if len(parts) == 2 {
info.linkName = parts[1]
}
case "//go:align":
align, err := strconv.Atoi(parts[1])
if err == nil {
info.align = align
}
}
}
}
+46 -46
View File
@@ -10,14 +10,14 @@ import (
"tinygo.org/x/go-llvm"
)
// createSyscall emits an inline system call instruction, depending on the
// target OS/arch.
func (b *builder) createSyscall(call *ssa.CallCommon) (llvm.Value, error) {
num := b.getValue(call.Args[0])
// emitSyscall emits an inline system call instruction, depending on the target
// OS/arch.
func (c *Compiler) emitSyscall(frame *Frame, call *ssa.CallCommon) (llvm.Value, error) {
num := c.getValue(frame, call.Args[0])
var syscallResult llvm.Value
switch {
case b.GOARCH() == "amd64":
if b.GOOS() == "darwin" {
case c.GOARCH == "amd64":
if c.GOOS == "darwin" {
// Darwin adds this magic number to system call numbers:
//
// > Syscall classes for 64-bit system call entry.
@@ -28,13 +28,13 @@ func (b *builder) createSyscall(call *ssa.CallCommon) (llvm.Value, error) {
// > All system classes enter the kernel via the syscall instruction.
//
// Source: https://opensource.apple.com/source/xnu/xnu-792.13.8/osfmk/mach/i386/syscall_sw.h
num = b.CreateOr(num, llvm.ConstInt(b.uintptrType, 0x2000000, false), "")
num = c.builder.CreateOr(num, llvm.ConstInt(c.uintptrType, 0x2000000, false), "")
}
// Sources:
// https://stackoverflow.com/a/2538212
// https://en.wikibooks.org/wiki/X86_Assembly/Interfacing_with_Linux#syscall
args := []llvm.Value{num}
argTypes := []llvm.Type{b.uintptrType}
argTypes := []llvm.Type{c.uintptrType}
// Constraints will look something like:
// "={rax},0,{rdi},{rsi},{rdx},{r10},{r8},{r9},~{rcx},~{r11}"
constraints := "={rax},0"
@@ -50,21 +50,21 @@ func (b *builder) createSyscall(call *ssa.CallCommon) (llvm.Value, error) {
"{r12}",
"{r13}",
}[i]
llvmValue := b.getValue(arg)
llvmValue := c.getValue(frame, arg)
args = append(args, llvmValue)
argTypes = append(argTypes, llvmValue.Type())
}
constraints += ",~{rcx},~{r11}"
fnType := llvm.FunctionType(b.uintptrType, argTypes, false)
fnType := llvm.FunctionType(c.uintptrType, argTypes, false)
target := llvm.InlineAsm(fnType, "syscall", constraints, true, false, llvm.InlineAsmDialectIntel)
syscallResult = b.CreateCall(target, args, "")
case b.GOARCH() == "386" && b.GOOS() == "linux":
syscallResult = c.builder.CreateCall(target, args, "")
case c.GOARCH == "386" && c.GOOS == "linux":
// Sources:
// syscall(2) man page
// https://stackoverflow.com/a/2538212
// https://en.wikibooks.org/wiki/X86_Assembly/Interfacing_with_Linux#int_0x80
args := []llvm.Value{num}
argTypes := []llvm.Type{b.uintptrType}
argTypes := []llvm.Type{c.uintptrType}
// Constraints will look something like:
// "={eax},0,{ebx},{ecx},{edx},{esi},{edi},{ebp}"
constraints := "={eax},0"
@@ -77,14 +77,14 @@ func (b *builder) createSyscall(call *ssa.CallCommon) (llvm.Value, error) {
"{edi}",
"{ebp}",
}[i]
llvmValue := b.getValue(arg)
llvmValue := c.getValue(frame, arg)
args = append(args, llvmValue)
argTypes = append(argTypes, llvmValue.Type())
}
fnType := llvm.FunctionType(b.uintptrType, argTypes, false)
fnType := llvm.FunctionType(c.uintptrType, argTypes, false)
target := llvm.InlineAsm(fnType, "int 0x80", constraints, true, false, llvm.InlineAsmDialectIntel)
syscallResult = b.CreateCall(target, args, "")
case b.GOARCH() == "arm" && b.GOOS() == "linux":
syscallResult = c.builder.CreateCall(target, args, "")
case c.GOARCH == "arm" && c.GOOS == "linux":
// Implement the EABI system call convention for Linux.
// Source: syscall(2) man page.
args := []llvm.Value{}
@@ -102,21 +102,21 @@ func (b *builder) createSyscall(call *ssa.CallCommon) (llvm.Value, error) {
"{r5}",
"{r6}",
}[i]
llvmValue := b.getValue(arg)
llvmValue := c.getValue(frame, arg)
args = append(args, llvmValue)
argTypes = append(argTypes, llvmValue.Type())
}
args = append(args, num)
argTypes = append(argTypes, b.uintptrType)
argTypes = append(argTypes, c.uintptrType)
constraints += ",{r7}" // syscall number
for i := len(call.Args) - 1; i < 4; i++ {
// r0-r3 get clobbered after the syscall returns
constraints += ",~{r" + strconv.Itoa(i) + "}"
}
fnType := llvm.FunctionType(b.uintptrType, argTypes, false)
fnType := llvm.FunctionType(c.uintptrType, argTypes, false)
target := llvm.InlineAsm(fnType, "svc #0", constraints, true, false, 0)
syscallResult = b.CreateCall(target, args, "")
case b.GOARCH() == "arm64" && b.GOOS() == "linux":
syscallResult = c.builder.CreateCall(target, args, "")
case c.GOARCH == "arm64" && c.GOOS == "linux":
// Source: syscall(2) man page.
args := []llvm.Value{}
argTypes := []llvm.Type{}
@@ -132,12 +132,12 @@ func (b *builder) createSyscall(call *ssa.CallCommon) (llvm.Value, error) {
"{x4}",
"{x5}",
}[i]
llvmValue := b.getValue(arg)
llvmValue := c.getValue(frame, arg)
args = append(args, llvmValue)
argTypes = append(argTypes, llvmValue.Type())
}
args = append(args, num)
argTypes = append(argTypes, b.uintptrType)
argTypes = append(argTypes, c.uintptrType)
constraints += ",{x8}" // syscall number
for i := len(call.Args) - 1; i < 8; i++ {
// x0-x7 may get clobbered during the syscall following the aarch64
@@ -145,14 +145,14 @@ func (b *builder) createSyscall(call *ssa.CallCommon) (llvm.Value, error) {
constraints += ",~{x" + strconv.Itoa(i) + "}"
}
constraints += ",~{x16},~{x17}" // scratch registers
fnType := llvm.FunctionType(b.uintptrType, argTypes, false)
fnType := llvm.FunctionType(c.uintptrType, argTypes, false)
target := llvm.InlineAsm(fnType, "svc #0", constraints, true, false, 0)
syscallResult = b.CreateCall(target, args, "")
syscallResult = c.builder.CreateCall(target, args, "")
default:
return llvm.Value{}, b.makeError(call.Pos(), "unknown GOOS/GOARCH for syscall: "+b.GOOS()+"/"+b.GOARCH())
return llvm.Value{}, c.makeError(call.Pos(), "unknown GOOS/GOARCH for syscall: "+c.GOOS+"/"+c.GOARCH)
}
switch b.GOOS() {
case "linux", "freebsd":
switch c.GOOS {
case "linux":
// Return values: r0, r1 uintptr, err Errno
// Pseudocode:
// var err uintptr
@@ -160,15 +160,15 @@ func (b *builder) createSyscall(call *ssa.CallCommon) (llvm.Value, error) {
// err = -syscallResult
// }
// return syscallResult, 0, err
zero := llvm.ConstInt(b.uintptrType, 0, false)
inrange1 := b.CreateICmp(llvm.IntSLT, syscallResult, llvm.ConstInt(b.uintptrType, 0, false), "")
inrange2 := b.CreateICmp(llvm.IntSGT, syscallResult, llvm.ConstInt(b.uintptrType, 0xfffffffffffff000, true), "") // -4096
hasError := b.CreateAnd(inrange1, inrange2, "")
errResult := b.CreateSelect(hasError, b.CreateSub(zero, syscallResult, ""), zero, "syscallError")
retval := llvm.Undef(b.ctx.StructType([]llvm.Type{b.uintptrType, b.uintptrType, b.uintptrType}, false))
retval = b.CreateInsertValue(retval, syscallResult, 0, "")
retval = b.CreateInsertValue(retval, zero, 1, "")
retval = b.CreateInsertValue(retval, errResult, 2, "")
zero := llvm.ConstInt(c.uintptrType, 0, false)
inrange1 := c.builder.CreateICmp(llvm.IntSLT, syscallResult, llvm.ConstInt(c.uintptrType, 0, false), "")
inrange2 := c.builder.CreateICmp(llvm.IntSGT, syscallResult, llvm.ConstInt(c.uintptrType, 0xfffffffffffff000, true), "") // -4096
hasError := c.builder.CreateAnd(inrange1, inrange2, "")
errResult := c.builder.CreateSelect(hasError, c.builder.CreateSub(zero, syscallResult, ""), zero, "syscallError")
retval := llvm.Undef(c.ctx.StructType([]llvm.Type{c.uintptrType, c.uintptrType, c.uintptrType}, false))
retval = c.builder.CreateInsertValue(retval, syscallResult, 0, "")
retval = c.builder.CreateInsertValue(retval, zero, 1, "")
retval = c.builder.CreateInsertValue(retval, errResult, 2, "")
return retval, nil
case "darwin":
// Return values: r0, r1 uintptr, err Errno
@@ -178,15 +178,15 @@ func (b *builder) createSyscall(call *ssa.CallCommon) (llvm.Value, error) {
// err = syscallResult
// }
// return syscallResult, 0, err
zero := llvm.ConstInt(b.uintptrType, 0, false)
hasError := b.CreateICmp(llvm.IntNE, syscallResult, llvm.ConstInt(b.uintptrType, 0, false), "")
errResult := b.CreateSelect(hasError, syscallResult, zero, "syscallError")
retval := llvm.Undef(b.ctx.StructType([]llvm.Type{b.uintptrType, b.uintptrType, b.uintptrType}, false))
retval = b.CreateInsertValue(retval, syscallResult, 0, "")
retval = b.CreateInsertValue(retval, zero, 1, "")
retval = b.CreateInsertValue(retval, errResult, 2, "")
zero := llvm.ConstInt(c.uintptrType, 0, false)
hasError := c.builder.CreateICmp(llvm.IntNE, syscallResult, llvm.ConstInt(c.uintptrType, 0, false), "")
errResult := c.builder.CreateSelect(hasError, syscallResult, zero, "syscallError")
retval := llvm.Undef(c.ctx.StructType([]llvm.Type{c.uintptrType, c.uintptrType, c.uintptrType}, false))
retval = c.builder.CreateInsertValue(retval, syscallResult, 0, "")
retval = c.builder.CreateInsertValue(retval, zero, 1, "")
retval = c.builder.CreateInsertValue(retval, errResult, 2, "")
return retval, nil
default:
return llvm.Value{}, b.makeError(call.Pos(), "unknown GOOS/GOARCH for syscall: "+b.GOOS()+"/"+b.GOARCH())
return llvm.Value{}, c.makeError(call.Pos(), "unknown GOOS/GOARCH for syscall: "+c.GOOS+"/"+c.GOARCH)
}
}
+9 -13
View File
@@ -8,23 +8,19 @@ import (
"tinygo.org/x/go-llvm"
)
// createVolatileLoad is the implementation of the intrinsic function
// runtime/volatile.LoadT().
func (b *builder) createVolatileLoad(instr *ssa.CallCommon) (llvm.Value, error) {
addr := b.getValue(instr.Args[0])
b.createNilCheck(instr.Args[0], addr, "deref")
val := b.CreateLoad(addr, "")
func (c *Compiler) emitVolatileLoad(frame *Frame, instr *ssa.CallCommon) (llvm.Value, error) {
addr := c.getValue(frame, instr.Args[0])
c.emitNilCheck(frame, addr, "deref")
val := c.builder.CreateLoad(addr, "")
val.SetVolatile(true)
return val, nil
}
// createVolatileStore is the implementation of the intrinsic function
// runtime/volatile.StoreT().
func (b *builder) createVolatileStore(instr *ssa.CallCommon) (llvm.Value, error) {
addr := b.getValue(instr.Args[0])
val := b.getValue(instr.Args[1])
b.createNilCheck(instr.Args[0], addr, "deref")
store := b.CreateStore(val, addr)
func (c *Compiler) emitVolatileStore(frame *Frame, instr *ssa.CallCommon) (llvm.Value, error) {
addr := c.getValue(frame, instr.Args[0])
val := c.getValue(frame, instr.Args[1])
c.emitNilCheck(frame, addr, "deref")
store := c.builder.CreateStore(val, addr)
store.SetVolatile(true)
return llvm.Value{}, nil
}
+120
View File
@@ -0,0 +1,120 @@
package compiler
// This file contains utility functions to pack and unpack sets of values. It
// can take in a list of values and tries to store it efficiently in the pointer
// itself if possible and legal.
import (
"tinygo.org/x/go-llvm"
)
// emitPointerPack packs the list of values into a single pointer value using
// bitcasts, or else allocates a value on the heap if it cannot be packed in the
// pointer value directly. It returns the pointer with the packed data.
func (c *Compiler) emitPointerPack(values []llvm.Value) llvm.Value {
valueTypes := make([]llvm.Type, len(values))
for i, value := range values {
valueTypes[i] = value.Type()
}
packedType := c.ctx.StructType(valueTypes, false)
// Allocate memory for the packed data.
var packedAlloc, packedHeapAlloc llvm.Value
size := c.targetData.TypeAllocSize(packedType)
if size == 0 {
return llvm.ConstPointerNull(c.i8ptrType)
} else if len(values) == 1 && values[0].Type().TypeKind() == llvm.PointerTypeKind {
return c.builder.CreateBitCast(values[0], c.i8ptrType, "pack.ptr")
} else if size <= c.targetData.TypeAllocSize(c.i8ptrType) {
// Packed data fits in a pointer, so store it directly inside the
// pointer.
if len(values) == 1 && values[0].Type().TypeKind() == llvm.IntegerTypeKind {
// Try to keep this cast in SSA form.
return c.builder.CreateIntToPtr(values[0], c.i8ptrType, "pack.int")
}
// Because packedType is a struct and we have to cast it to a *i8, store
// it in an alloca first for bitcasting (store+bitcast+load).
packedAlloc, _, _ = c.createTemporaryAlloca(packedType, "")
} else {
// Packed data is bigger than a pointer, so allocate it on the heap.
sizeValue := llvm.ConstInt(c.uintptrType, size, false)
packedHeapAlloc = c.createRuntimeCall("alloc", []llvm.Value{sizeValue}, "")
if c.needsStackObjects() {
c.trackPointer(packedHeapAlloc)
}
packedAlloc = c.builder.CreateBitCast(packedHeapAlloc, llvm.PointerType(packedType, 0), "")
}
// Store all values in the alloca or heap pointer.
for i, value := range values {
indices := []llvm.Value{
llvm.ConstInt(c.ctx.Int32Type(), 0, false),
llvm.ConstInt(c.ctx.Int32Type(), uint64(i), false),
}
gep := c.builder.CreateInBoundsGEP(packedAlloc, indices, "")
c.builder.CreateStore(value, gep)
}
if packedHeapAlloc.IsNil() {
// Load value (as *i8) from the alloca.
packedAlloc = c.builder.CreateBitCast(packedAlloc, llvm.PointerType(c.i8ptrType, 0), "")
result := c.builder.CreateLoad(packedAlloc, "")
packedPtr := c.builder.CreateBitCast(packedAlloc, c.i8ptrType, "")
packedSize := llvm.ConstInt(c.ctx.Int64Type(), c.targetData.TypeAllocSize(packedAlloc.Type()), false)
c.emitLifetimeEnd(packedPtr, packedSize)
return result
} else {
// Get the original heap allocation pointer, which already is an *i8.
return packedHeapAlloc
}
}
// emitPointerUnpack extracts a list of values packed using emitPointerPack.
func (c *Compiler) emitPointerUnpack(ptr llvm.Value, valueTypes []llvm.Type) []llvm.Value {
packedType := c.ctx.StructType(valueTypes, false)
// Get a correctly-typed pointer to the packed data.
var packedAlloc, packedRawAlloc llvm.Value
size := c.targetData.TypeAllocSize(packedType)
if size == 0 {
// No data to unpack.
} else if len(valueTypes) == 1 && valueTypes[0].TypeKind() == llvm.PointerTypeKind {
// A single pointer is always stored directly.
return []llvm.Value{c.builder.CreateBitCast(ptr, valueTypes[0], "unpack.ptr")}
} else if size <= c.targetData.TypeAllocSize(c.i8ptrType) {
// Packed data stored directly in pointer.
if len(valueTypes) == 1 && valueTypes[0].TypeKind() == llvm.IntegerTypeKind {
// Keep this cast in SSA form.
return []llvm.Value{c.builder.CreatePtrToInt(ptr, valueTypes[0], "unpack.int")}
}
// Fallback: load it using an alloca.
packedRawAlloc, _, _ = c.createTemporaryAlloca(llvm.PointerType(c.i8ptrType, 0), "unpack.raw.alloc")
packedRawValue := c.builder.CreateBitCast(ptr, llvm.PointerType(c.i8ptrType, 0), "unpack.raw.value")
c.builder.CreateStore(packedRawValue, packedRawAlloc)
packedAlloc = c.builder.CreateBitCast(packedRawAlloc, llvm.PointerType(packedType, 0), "unpack.alloc")
} else {
// Packed data stored on the heap. Bitcast the passed-in pointer to the
// correct pointer type.
packedAlloc = c.builder.CreateBitCast(ptr, llvm.PointerType(packedType, 0), "unpack.raw.ptr")
}
// Load each value from the packed data.
values := make([]llvm.Value, len(valueTypes))
for i, valueType := range valueTypes {
if c.targetData.TypeAllocSize(valueType) == 0 {
// This value has length zero, so there's nothing to load.
values[i] = c.getZeroValue(valueType)
continue
}
indices := []llvm.Value{
llvm.ConstInt(c.ctx.Int32Type(), 0, false),
llvm.ConstInt(c.ctx.Int32Type(), uint64(i), false),
}
gep := c.builder.CreateInBoundsGEP(packedAlloc, indices, "")
values[i] = c.builder.CreateLoad(gep, "")
}
if !packedRawAlloc.IsNil() {
allocPtr := c.builder.CreateBitCast(packedRawAlloc, c.i8ptrType, "")
allocSize := llvm.ConstInt(c.ctx.Int64Type(), c.targetData.TypeAllocSize(c.uintptrType), false)
c.emitLifetimeEnd(allocPtr, allocSize)
}
return values
}
+2 -5
View File
@@ -4,10 +4,7 @@ go 1.11
require (
github.com/blakesmith/ar v0.0.0-20150311145944-8bd4349a67f2
github.com/google/shlex v0.0.0-20181106134648-c34317bd91bf
github.com/marcinbor85/gohex v0.0.0-20180128172054-7a43cd876e46
go.bug.st/serial v1.0.0
golang.org/x/tools v0.0.0-20200216192241-b320d3a0f5a2
google.golang.org/appengine v1.4.0 // indirect
tinygo.org/x/go-llvm v0.0.0-20200401165421-8d120882fc7a
golang.org/x/tools v0.0.0-20190227180812-8dcc6e70cdef
tinygo.org/x/go-llvm v0.0.0-20190224120431-7707ae5d1261
)
-45
View File
@@ -1,59 +1,14 @@
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/creack/goselect v0.1.1 h1:tiSSgKE1eJtxs1h/VgGQWuXUP0YS4CDIFMp6vaI1ls0=
github.com/creack/goselect v0.1.1/go.mod h1:a/NhLweNvqIYMuxcMOuWY516Cimucms3DglDzQP3hKY=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/google/shlex v0.0.0-20181106134648-c34317bd91bf h1:7+FW5aGwISbqUtkfmIpZJGRgNFg2ioYPvFaUxdqpDsg=
github.com/google/shlex v0.0.0-20181106134648-c34317bd91bf/go.mod h1:RpwtwJQFrIEPstU94h88MWPXP2ektJZ8cZ0YntAmXiE=
github.com/marcinbor85/gohex v0.0.0-20180128172054-7a43cd876e46 h1:wXG2bA8fO7Vv7lLk2PihFMTqmbT173Tje39oKzQ50Mo=
github.com/marcinbor85/gohex v0.0.0-20180128172054-7a43cd876e46/go.mod h1:Pb6XcsXyropB9LNHhnqaknG/vEwYztLkQzVCHv8sQ3M=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
go.bug.st/serial v1.0.0 h1:ogEPzrllCsnG00EqKRjeYvPRsO7NJW6DqykzkdD6E/k=
go.bug.st/serial v1.0.0/go.mod h1:rpXPISGjuNjPTRTcMlxi9lN6LoIPxd1ixVjBd8aSk/Q=
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/mod v0.1.1-0.20191105210325-c90efee705ee h1:WG0RUwxtNT4qqaXX3DPA8zHFNm/D9xaBpxzHt1WcA/E=
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191128015809-6d18c012aee9 h1:ZBzSG/7F4eNKz2L3GE9o300RX0Az1Bw5HF7PDraD+qU=
golang.org/x/sys v0.0.0-20191128015809-6d18c012aee9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/tools v0.0.0-20190227180812-8dcc6e70cdef h1:ymc9FeDom3RIEA3coKokSllBB1hRcMT0tZ1W3Jf9Ids=
golang.org/x/tools v0.0.0-20190227180812-8dcc6e70cdef/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
golang.org/x/tools v0.0.0-20200216192241-b320d3a0f5a2 h1:0sfSpGSa544Fwnbot3Oxq/U6SXqjty6Jy/3wRhVS7ig=
golang.org/x/tools v0.0.0-20200216192241-b320d3a0f5a2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898 h1:/atklqdjdhuosWIl6AIbOeHJjicWYPqR9bpxqxYG2pA=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
tinygo.org/x/go-llvm v0.0.0-20190224120431-7707ae5d1261 h1:rJS2Hga39YAnm7DE4qrPm6Dr/67EOojL0XPzvbEeBiw=
tinygo.org/x/go-llvm v0.0.0-20190224120431-7707ae5d1261/go.mod h1:fv1F0BSNpxMfCL0zF3M4OPFbgYHnhtB6ST0HvUtu/LE=
tinygo.org/x/go-llvm v0.0.0-20190818154551-95bc4ffe1add h1:dFjMH1sLhYADg8UQm7DB56B7e+TfvAmWmEZLhyv3r/w=
tinygo.org/x/go-llvm v0.0.0-20190818154551-95bc4ffe1add/go.mod h1:fv1F0BSNpxMfCL0zF3M4OPFbgYHnhtB6ST0HvUtu/LE=
tinygo.org/x/go-llvm v0.0.0-20191103182207-90b6e4bdc0b9 h1:d6rAX39a3C0pKrY5HcojEGyN8w9ocU0v7X28lC/TRKU=
tinygo.org/x/go-llvm v0.0.0-20191103182207-90b6e4bdc0b9/go.mod h1:fv1F0BSNpxMfCL0zF3M4OPFbgYHnhtB6ST0HvUtu/LE=
tinygo.org/x/go-llvm v0.0.0-20191103200204-37e93e3f04e2 h1:Q5Hv3e5cLMGkiYwYgZL1Zrv6nb/EY+DJpRWrdO6ws6o=
tinygo.org/x/go-llvm v0.0.0-20191103200204-37e93e3f04e2/go.mod h1:fv1F0BSNpxMfCL0zF3M4OPFbgYHnhtB6ST0HvUtu/LE=
tinygo.org/x/go-llvm v0.0.0-20191113125529-bad6d01809e8 h1:9Bfvso+tTVQg16UzOA614NaYA4x8vsRBNtd3eBrXwp0=
tinygo.org/x/go-llvm v0.0.0-20191113125529-bad6d01809e8/go.mod h1:fv1F0BSNpxMfCL0zF3M4OPFbgYHnhtB6ST0HvUtu/LE=
tinygo.org/x/go-llvm v0.0.0-20191124211856-b2db3df3f257 h1:o8VDylrMN7gWemBMu8rEyuogKPhcLTdx5KrUAp9macc=
tinygo.org/x/go-llvm v0.0.0-20191124211856-b2db3df3f257/go.mod h1:fv1F0BSNpxMfCL0zF3M4OPFbgYHnhtB6ST0HvUtu/LE=
tinygo.org/x/go-llvm v0.0.0-20191215173731-ad71f3d24aae h1:s8J5EyxCkHxXB08UI3gk9W9IS/ekizRvSX+PfZxnAB0=
tinygo.org/x/go-llvm v0.0.0-20191215173731-ad71f3d24aae/go.mod h1:fv1F0BSNpxMfCL0zF3M4OPFbgYHnhtB6ST0HvUtu/LE=
tinygo.org/x/go-llvm v0.0.0-20200104190746-1ff21df33566 h1:a4y30bTf7U0zDA75v2PTL+XQ2OzJetj19gK8XwQpUNY=
tinygo.org/x/go-llvm v0.0.0-20200104190746-1ff21df33566/go.mod h1:fv1F0BSNpxMfCL0zF3M4OPFbgYHnhtB6ST0HvUtu/LE=
tinygo.org/x/go-llvm v0.0.0-20200226165415-53522ab6713d h1:mtgZh/e8a3wxneQFuLXoQYO//1mvlki02yZ1JCwMKp4=
tinygo.org/x/go-llvm v0.0.0-20200226165415-53522ab6713d/go.mod h1:fv1F0BSNpxMfCL0zF3M4OPFbgYHnhtB6ST0HvUtu/LE=
tinygo.org/x/go-llvm v0.0.0-20200401165421-8d120882fc7a h1:Ugje2Lxuv8CFncHzs5W+hWfJvPsM+W4K0zRvzFbLvoE=
tinygo.org/x/go-llvm v0.0.0-20200401165421-8d120882fc7a/go.mod h1:fv1F0BSNpxMfCL0zF3M4OPFbgYHnhtB6ST0HvUtu/LE=
-197
View File
@@ -1,197 +0,0 @@
// Package goenv returns environment variables that are used in various parts of
// the compiler. You can query it manually with the `tinygo env` subcommand.
package goenv
import (
"fmt"
"os"
"os/exec"
"os/user"
"path/filepath"
"runtime"
)
// Keys is a slice of all available environment variable keys.
var Keys = []string{
"GOOS",
"GOARCH",
"GOROOT",
"GOPATH",
"GOCACHE",
"CGO_ENABLED",
"TINYGOROOT",
}
// TINYGOROOT is the path to the final location for checking tinygo files. If
// unset (by a -X ldflag), then sourceDir() will fallback to the original build
// directory.
var TINYGOROOT string
// Get returns a single environment variable, possibly calculating it on-demand.
// The empty string is returned for unknown environment variables.
func Get(name string) string {
switch name {
case "GOOS":
if dir := os.Getenv("GOOS"); dir != "" {
return dir
}
return runtime.GOOS
case "GOARCH":
if dir := os.Getenv("GOARCH"); dir != "" {
return dir
}
return runtime.GOARCH
case "GOROOT":
return getGoroot()
case "GOPATH":
if dir := os.Getenv("GOPATH"); dir != "" {
return dir
}
// fallback
home := getHomeDir()
return filepath.Join(home, "go")
case "GOCACHE":
// Get the cache directory, usually ~/.cache/tinygo
dir, err := os.UserCacheDir()
if err != nil {
panic("could not find cache dir: " + err.Error())
}
return filepath.Join(dir, "tinygo")
case "CGO_ENABLED":
val := os.Getenv("CGO_ENABLED")
if val == "1" || val == "0" {
return val
}
// Default to enabling CGo.
return "1"
case "TINYGOROOT":
return sourceDir()
default:
return ""
}
}
// Return the TINYGOROOT, or exit with an error.
func sourceDir() string {
// Use $TINYGOROOT as root, if available.
root := os.Getenv("TINYGOROOT")
if root != "" {
if !isSourceDir(root) {
fmt.Fprintln(os.Stderr, "error: $TINYGOROOT was not set to the correct root")
os.Exit(1)
}
return root
}
if TINYGOROOT != "" {
if !isSourceDir(TINYGOROOT) {
fmt.Fprintln(os.Stderr, "error: TINYGOROOT was not set to the correct root")
os.Exit(1)
}
return TINYGOROOT
}
// Find root from executable path.
path, err := os.Executable()
if err != nil {
// Very unlikely. Bail out if it happens.
panic("could not get executable path: " + err.Error())
}
root = filepath.Dir(filepath.Dir(path))
if isSourceDir(root) {
return root
}
// Fallback: use the original directory from where it was built
// https://stackoverflow.com/a/32163888/559350
_, path, _, _ = runtime.Caller(0)
root = filepath.Dir(filepath.Dir(path))
if isSourceDir(root) {
return root
}
fmt.Fprintln(os.Stderr, "error: could not autodetect root directory, set the TINYGOROOT environment variable to override")
os.Exit(1)
panic("unreachable")
}
// isSourceDir returns true if the directory looks like a TinyGo source directory.
func isSourceDir(root string) bool {
_, err := os.Stat(filepath.Join(root, "src/runtime/internal/sys/zversion.go"))
if err != nil {
return false
}
_, err = os.Stat(filepath.Join(root, "src/device/arm/arm.go"))
return err == nil
}
func getHomeDir() string {
u, err := user.Current()
if err != nil {
panic("cannot get current user: " + err.Error())
}
if u.HomeDir == "" {
// This is very unlikely, so panic here.
// Not the nicest solution, however.
panic("could not find home directory")
}
return u.HomeDir
}
// getGoroot returns an appropriate GOROOT from various sources. If it can't be
// found, it returns an empty string.
func getGoroot() string {
goroot := os.Getenv("GOROOT")
if goroot != "" {
// An explicitly set GOROOT always has preference.
return goroot
}
// Check for the location of the 'go' binary and base GOROOT on that.
binpath, err := exec.LookPath("go")
if err == nil {
binpath, err = filepath.EvalSymlinks(binpath)
if err == nil {
goroot := filepath.Dir(filepath.Dir(binpath))
if isGoroot(goroot) {
return goroot
}
}
}
// Check what GOROOT was at compile time.
if isGoroot(runtime.GOROOT()) {
return runtime.GOROOT()
}
// Check for some standard locations, as a last resort.
var candidates []string
switch runtime.GOOS {
case "linux":
candidates = []string{
"/usr/local/go", // manually installed
"/usr/lib/go", // from the distribution
}
case "darwin":
candidates = []string{
"/usr/local/go", // manually installed
"/usr/local/opt/go/libexec", // from Homebrew
}
}
for _, candidate := range candidates {
if isGoroot(candidate) {
return candidate
}
}
// Can't find GOROOT...
return ""
}
// isGoroot checks whether the given path looks like a GOROOT.
func isGoroot(goroot string) bool {
_, err := os.Stat(filepath.Join(goroot, "src", "runtime", "internal", "sys", "zversion.go"))
return err == nil
}
-64
View File
@@ -1,64 +0,0 @@
package goenv
import (
"errors"
"fmt"
"io"
"io/ioutil"
"path/filepath"
"regexp"
"strings"
)
// GetGorootVersion returns the major and minor version for a given GOROOT path.
// If the goroot cannot be determined, (0, 0) is returned.
func GetGorootVersion(goroot string) (major, minor int, err error) {
s, err := GorootVersionString(goroot)
if err != nil {
return 0, 0, err
}
if s == "" || s[:2] != "go" {
return 0, 0, errors.New("could not parse Go version: version does not start with 'go' prefix")
}
parts := strings.Split(s[2:], ".")
if len(parts) < 2 {
return 0, 0, errors.New("could not parse Go version: version has less than two parts")
}
// Ignore the errors, we don't really handle errors here anyway.
var trailing string
n, err := fmt.Sscanf(s, "go%d.%d%s", &major, &minor, &trailing)
if n == 2 && err == io.EOF {
// Means there were no trailing characters (i.e., not an alpha/beta)
err = nil
}
if err != nil {
return 0, 0, fmt.Errorf("failed to parse version: %s", err)
}
return
}
// GorootVersionString returns the version string as reported by the Go
// toolchain for the given GOROOT path. It is usually of the form `go1.x.y` but
// can have some variations (for beta releases, for example).
func GorootVersionString(goroot string) (string, error) {
if data, err := ioutil.ReadFile(filepath.Join(
goroot, "src", "runtime", "internal", "sys", "zversion.go")); err == nil {
r := regexp.MustCompile("const TheVersion = `(.*)`")
matches := r.FindSubmatch(data)
if len(matches) != 2 {
return "", errors.New("Invalid go version output:\n" + string(data))
}
return string(matches[1]), nil
} else if data, err := ioutil.ReadFile(filepath.Join(goroot, "VERSION")); err == nil {
return string(data), nil
} else {
return "", err
}
}
+5 -71
View File
@@ -3,81 +3,15 @@ package interp
// This file provides useful types for errors encountered during IR evaluation.
import (
"errors"
"go/scanner"
"go/token"
"path/filepath"
"tinygo.org/x/go-llvm"
)
// errUnreachable is returned when an unreachable instruction is executed. This
// error should not be visible outside of the interp package.
var errUnreachable = &Error{Err: errors.New("interp: unreachable executed")}
// unsupportedInstructionError returns a new "unsupported instruction" error for
// the given instruction. It includes source location information, when
// available.
func (e *evalPackage) unsupportedInstructionError(inst llvm.Value) *Error {
return e.errorAt(inst, errors.New("interp: unsupported instruction"))
}
// ErrorLine is one line in a traceback. The position may be missing.
type ErrorLine struct {
Pos token.Position
type Unsupported struct {
Inst llvm.Value
}
// Error encapsulates compile-time interpretation errors with an associated
// import path. The errors may not have a precise location attached.
type Error struct {
ImportPath string
Inst llvm.Value
Pos token.Position
Err error
Traceback []ErrorLine
}
// Error returns the string of the first error in the list of errors.
func (e *Error) Error() string {
return e.Pos.String() + ": " + e.Err.Error()
}
// errorAt returns an error value for the currently interpreted package at the
// location of the instruction. The location information may not be complete as
// it depends on debug information in the IR.
func (e *evalPackage) errorAt(inst llvm.Value, err error) *Error {
return &Error{
ImportPath: e.packagePath,
Pos: getPosition(inst),
Err: err,
}
}
// errorAt returns an error value at the location of the instruction.
// The location information may not be complete as it depends on debug
// information in the IR.
func errorAt(inst llvm.Value, msg string) scanner.Error {
return scanner.Error{
Pos: getPosition(inst),
Msg: msg,
}
}
// getPosition returns the position information for the given instruction, as
// far as it is available.
func getPosition(inst llvm.Value) token.Position {
if inst.IsAInstruction().IsNil() {
return token.Position{}
}
loc := inst.InstructionDebugLoc()
if loc.IsNil() {
return token.Position{}
}
file := loc.LocationScope().ScopeFile()
return token.Position{
Filename: filepath.Join(file.FileDirectory(), file.FileFilename()),
Line: int(loc.LocationLine()),
Column: int(loc.LocationColumn()),
}
func (e Unsupported) Error() string {
// TODO: how to return the actual instruction string?
// It looks like LLVM provides no function for that...
return "interp: unsupported instruction"
}
+54 -194
View File
@@ -11,18 +11,21 @@ import (
)
type frame struct {
*evalPackage
fn llvm.Value
locals map[llvm.Value]Value
*Eval
fn llvm.Value
pkgName string
locals map[llvm.Value]Value
}
var ErrUnreachable = errors.New("interp: unreachable executed")
// evalBasicBlock evaluates a single basic block, returning the return value (if
// ending with a ret instruction), a list of outgoing basic blocks (if not
// ending with a ret instruction), or an error on failure.
// Most of it works at compile time. Some calls get translated into calls to be
// executed at runtime: calls to functions with side effects, external calls,
// and operations on the result of such instructions.
func (fr *frame) evalBasicBlock(bb, incoming llvm.BasicBlock, indent string) (retval Value, outgoing []llvm.Value, err *Error) {
func (fr *frame) evalBasicBlock(bb, incoming llvm.BasicBlock, indent string) (retval Value, outgoing []llvm.Value, err error) {
for inst := bb.FirstInstruction(); !inst.IsNil(); inst = llvm.NextInstruction(inst) {
if fr.Debug {
print(indent)
@@ -76,14 +79,14 @@ func (fr *frame) evalBasicBlock(bb, incoming llvm.BasicBlock, indent string) (re
fr.locals[inst] = &LocalValue{fr.Eval, fr.builder.CreateXor(lhs, rhs, "")}
default:
return nil, nil, fr.unsupportedInstructionError(inst)
return nil, nil, &Unsupported{inst}
}
// Memory operators
case !inst.IsAAllocaInst().IsNil():
allocType := inst.Type().ElementType()
alloca := llvm.AddGlobal(fr.Mod, allocType, fr.packagePath+"$alloca")
alloca.SetInitializer(llvm.ConstNull(allocType))
alloca := llvm.AddGlobal(fr.Mod, allocType, fr.pkgName+"$alloca")
alloca.SetInitializer(getZeroValue(allocType))
alloca.SetLinkage(llvm.InternalLinkage)
fr.locals[inst] = &LocalValue{
Underlying: alloca,
@@ -98,7 +101,7 @@ func (fr *frame) evalBasicBlock(bb, incoming llvm.BasicBlock, indent string) (re
value = operand.Load()
}
if value.Type() != inst.Type() {
return nil, nil, fr.errorAt(inst, errors.New("interp: load: type does not match"))
panic("interp: load: type does not match")
}
fr.locals[inst] = fr.getValue(value)
case !inst.IsAStoreInst().IsNil():
@@ -122,16 +125,15 @@ func (fr *frame) evalBasicBlock(bb, incoming llvm.BasicBlock, indent string) (re
// Not a constant operation.
// This should be detected by the scanner, but isn't at the
// moment.
return nil, nil, fr.errorAt(inst, errors.New("todo: non-const gep"))
panic("todo: non-const gep")
}
indices[i] = uint32(operand.Value().ZExtValue())
}
result, err := value.GetElementPtr(indices)
if err != nil {
return nil, nil, fr.errorAt(inst, err)
}
result := value.GetElementPtr(indices)
if result.Type() != inst.Type() {
return nil, nil, fr.errorAt(inst, errors.New("interp: gep: type does not match"))
println(" expected:", inst.Type().String())
println(" actual: ", result.Type().String())
panic("interp: gep: type does not match")
}
fr.locals[inst] = result
@@ -186,7 +188,7 @@ func (fr *frame) evalBasicBlock(bb, incoming llvm.BasicBlock, indent string) (re
}
}
// It is not possible in Go to bitcast a map value to a pointer.
return nil, nil, fr.errorAt(inst, errors.New("unimplemented: bitcast of map"))
panic("unimplemented: bitcast of map")
}
value := fr.getLocal(operand).(*LocalValue)
fr.locals[inst] = &LocalValue{fr.Eval, fr.builder.CreateBitCast(value.Value(), inst.Type(), "")}
@@ -196,28 +198,20 @@ func (fr *frame) evalBasicBlock(bb, incoming llvm.BasicBlock, indent string) (re
lhs := fr.getLocal(inst.Operand(0)).(*LocalValue).Underlying
rhs := fr.getLocal(inst.Operand(1)).(*LocalValue).Underlying
predicate := inst.IntPredicate()
if predicate == llvm.IntEQ {
var lhsZero, rhsZero bool
var ok1, ok2 bool
if lhs.Type().TypeKind() == llvm.PointerTypeKind {
// Unfortunately, the const propagation in the IR builder
// doesn't handle pointer compares of inttoptr values. So we
// implement it manually here.
lhsZero, ok1 = isPointerNil(lhs)
rhsZero, ok2 = isPointerNil(rhs)
}
if lhs.Type().TypeKind() == llvm.IntegerTypeKind {
lhsZero, ok1 = isZero(lhs)
rhsZero, ok2 = isZero(rhs)
}
if predicate == llvm.IntEQ && lhs.Type().TypeKind() == llvm.PointerTypeKind {
// Unfortunately, the const propagation in the IR builder
// doesn't handle pointer compares of inttoptr values. So we
// implement it manually here.
lhsNil, ok1 := isPointerNil(lhs)
rhsNil, ok2 := isPointerNil(rhs)
if ok1 && ok2 {
if lhsZero && rhsZero {
// Both are zero, so this icmp is always evaluated to true.
if lhsNil && rhsNil {
// Both are nil, so this icmp is always evaluated to true.
fr.locals[inst] = &LocalValue{fr.Eval, llvm.ConstInt(fr.Mod.Context().Int1Type(), 1, false)}
continue
}
if lhsZero != rhsZero {
// Only one of them is zero, so this comparison must return false.
if lhsNil != rhsNil {
// Only one of them is nil, so this comparison must return false.
fr.locals[inst] = &LocalValue{fr.Eval, llvm.ConstInt(fr.Mod.Context().Int1Type(), 0, false)}
continue
}
@@ -253,13 +247,13 @@ func (fr *frame) evalBasicBlock(bb, incoming llvm.BasicBlock, indent string) (re
if size != typeSize {
// allocate an array
if size%typeSize != 0 {
return nil, nil, fr.unsupportedInstructionError(inst)
return nil, nil, &Unsupported{inst}
}
elementCount = int(size / typeSize)
allocType = llvm.ArrayType(allocType, elementCount)
}
alloc := llvm.AddGlobal(fr.Mod, allocType, fr.packagePath+"$alloc")
alloc.SetInitializer(llvm.ConstNull(allocType))
alloc := llvm.AddGlobal(fr.Mod, allocType, fr.pkgName+"$alloc")
alloc.SetInitializer(getZeroValue(allocType))
alloc.SetLinkage(llvm.InternalLinkage)
result := &LocalValue{
Underlying: alloc,
@@ -268,11 +262,7 @@ func (fr *frame) evalBasicBlock(bb, incoming llvm.BasicBlock, indent string) (re
if elementCount == 1 {
fr.locals[resultInst] = result
} else {
result, err := result.GetElementPtr([]uint32{0, 0})
if err != nil {
return nil, nil, fr.errorAt(inst, err)
}
fr.locals[resultInst] = result
fr.locals[resultInst] = result.GetElementPtr([]uint32{0, 0})
}
case callee.Name() == "runtime.hashmapMake":
// create a map
@@ -280,55 +270,24 @@ func (fr *frame) evalBasicBlock(bb, incoming llvm.BasicBlock, indent string) (re
valueSize := inst.Operand(1).ZExtValue()
fr.locals[inst] = &MapValue{
Eval: fr.Eval,
PkgName: fr.packagePath,
PkgName: fr.pkgName,
KeySize: int(keySize),
ValueSize: int(valueSize),
}
case callee.Name() == "runtime.hashmapStringSet":
// set a string key in the map
m := fr.getLocal(inst.Operand(0)).(*MapValue)
// "key" is a Go string value, which in the TinyGo calling convention is split up
// into separate pointer and length parameters.
keyBuf := fr.getLocal(inst.Operand(1)).(*LocalValue)
keyLen := fr.getLocal(inst.Operand(2)).(*LocalValue)
valPtr := fr.getLocal(inst.Operand(3)).(*LocalValue)
m, ok := fr.getLocal(inst.Operand(0)).(*MapValue)
if !ok || !keyBuf.IsConstant() || !keyLen.IsConstant() || !valPtr.IsConstant() {
// The mapassign operation could not be done at compile
// time. Do it at runtime instead.
m := fr.getLocal(inst.Operand(0)).Value()
fr.markDirty(m)
llvmParams := []llvm.Value{
m, // *runtime.hashmap
fr.getLocal(inst.Operand(1)).Value(), // key.ptr
fr.getLocal(inst.Operand(2)).Value(), // key.len
fr.getLocal(inst.Operand(3)).Value(), // value (unsafe.Pointer)
fr.getLocal(inst.Operand(4)).Value(), // context
fr.getLocal(inst.Operand(5)).Value(), // parentHandle
}
fr.builder.CreateCall(callee, llvmParams, "")
continue
}
// "key" is a Go string value, which in the TinyGo calling convention is split up
// into separate pointer and length parameters.
m.PutString(keyBuf, keyLen, valPtr)
case callee.Name() == "runtime.hashmapBinarySet":
// set a binary (int etc.) key in the map
m := fr.getLocal(inst.Operand(0)).(*MapValue)
keyBuf := fr.getLocal(inst.Operand(1)).(*LocalValue)
valPtr := fr.getLocal(inst.Operand(2)).(*LocalValue)
m, ok := fr.getLocal(inst.Operand(0)).(*MapValue)
if !ok || !keyBuf.IsConstant() || !valPtr.IsConstant() {
// The mapassign operation could not be done at compile
// time. Do it at runtime instead.
m := fr.getLocal(inst.Operand(0)).Value()
fr.markDirty(m)
llvmParams := []llvm.Value{
m, // *runtime.hashmap
fr.getLocal(inst.Operand(1)).Value(), // key
fr.getLocal(inst.Operand(2)).Value(), // value
fr.getLocal(inst.Operand(3)).Value(), // context
fr.getLocal(inst.Operand(4)).Value(), // parentHandle
}
fr.builder.CreateCall(callee, llvmParams, "")
continue
}
m.PutBinary(keyBuf, valPtr)
case callee.Name() == "runtime.stringConcat":
// adding two strings together
@@ -345,7 +304,7 @@ func (fr *frame) evalBasicBlock(bb, incoming llvm.BasicBlock, indent string) (re
}
globalType := llvm.ArrayType(fr.Mod.Context().Int8Type(), len(result))
globalValue := llvm.ConstArray(fr.Mod.Context().Int8Type(), vals)
global := llvm.AddGlobal(fr.Mod, globalType, fr.packagePath+"$stringconcat")
global := llvm.AddGlobal(fr.Mod, globalType, fr.pkgName+"$stringconcat")
global.SetInitializer(globalValue)
global.SetLinkage(llvm.InternalLinkage)
global.SetGlobalConstant(true)
@@ -353,70 +312,10 @@ func (fr *frame) evalBasicBlock(bb, incoming llvm.BasicBlock, indent string) (re
stringType := fr.Mod.GetTypeByName("runtime._string")
retPtr := llvm.ConstGEP(global, getLLVMIndices(fr.Mod.Context().Int32Type(), []uint32{0, 0}))
retLen := llvm.ConstInt(stringType.StructElementTypes()[1], uint64(len(result)), false)
ret := llvm.ConstNull(stringType)
ret := getZeroValue(stringType)
ret = llvm.ConstInsertValue(ret, retPtr, []uint32{0})
ret = llvm.ConstInsertValue(ret, retLen, []uint32{1})
fr.locals[inst] = &LocalValue{fr.Eval, ret}
case callee.Name() == "runtime.sliceCopy":
elementSize := fr.getLocal(inst.Operand(4)).(*LocalValue).Value().ZExtValue()
dstArray := fr.getLocal(inst.Operand(0)).(*LocalValue).stripPointerCasts()
srcArray := fr.getLocal(inst.Operand(1)).(*LocalValue).stripPointerCasts()
dstLen := fr.getLocal(inst.Operand(2)).(*LocalValue)
srcLen := fr.getLocal(inst.Operand(3)).(*LocalValue)
if elementSize != 1 && dstArray.Type().ElementType().TypeKind() == llvm.ArrayTypeKind && srcArray.Type().ElementType().TypeKind() == llvm.ArrayTypeKind {
// Slice data pointers are created by adding a global array
// and getting the address of the first element using a GEP.
// However, before the compiler can pass it to
// runtime.sliceCopy, it has to perform a bitcast to a *i8,
// to make it a unsafe.Pointer. Now, when the IR builder
// sees a bitcast of a GEP with zero indices, it will make
// a bitcast of the original array instead of the GEP,
// which breaks our assumptions.
// Re-add this GEP, in the hope that it it is then of the correct type...
dstArrayValue, err := dstArray.GetElementPtr([]uint32{0, 0})
if err != nil {
return nil, nil, fr.errorAt(inst, err)
}
dstArray = dstArrayValue.(*LocalValue)
srcArrayValue, err := srcArray.GetElementPtr([]uint32{0, 0})
if err != nil {
return nil, nil, fr.errorAt(inst, err)
}
srcArray = srcArrayValue.(*LocalValue)
}
if fr.Eval.TargetData.TypeAllocSize(dstArray.Type().ElementType()) != elementSize {
return nil, nil, fr.errorAt(inst, errors.New("interp: slice dst element size does not match pointer type"))
}
if fr.Eval.TargetData.TypeAllocSize(srcArray.Type().ElementType()) != elementSize {
return nil, nil, fr.errorAt(inst, errors.New("interp: slice src element size does not match pointer type"))
}
if dstArray.Type() != srcArray.Type() {
return nil, nil, fr.errorAt(inst, errors.New("interp: slice element types don't match"))
}
length := dstLen.Value().SExtValue()
if srcLength := srcLen.Value().SExtValue(); srcLength < length {
length = srcLength
}
if length < 0 {
return nil, nil, fr.errorAt(inst, errors.New("interp: trying to copy a slice with negative length?"))
}
for i := int64(0); i < length; i++ {
var err error
// *dst = *src
dstArray.Store(srcArray.Load())
// dst++
dstArrayValue, err := dstArray.GetElementPtr([]uint32{1})
if err != nil {
return nil, nil, fr.errorAt(inst, err)
}
dstArray = dstArrayValue.(*LocalValue)
// src++
srcArrayValue, err := srcArray.GetElementPtr([]uint32{1})
if err != nil {
return nil, nil, fr.errorAt(inst, err)
}
srcArray = srcArrayValue.(*LocalValue)
}
case callee.Name() == "runtime.stringToBytes":
// convert a string to a []byte
bufPtr := fr.getLocal(inst.Operand(0))
@@ -428,7 +327,7 @@ func (fr *frame) evalBasicBlock(bb, incoming llvm.BasicBlock, indent string) (re
}
globalType := llvm.ArrayType(fr.Mod.Context().Int8Type(), len(result))
globalValue := llvm.ConstArray(fr.Mod.Context().Int8Type(), vals)
global := llvm.AddGlobal(fr.Mod, globalType, fr.packagePath+"$bytes")
global := llvm.AddGlobal(fr.Mod, globalType, fr.pkgName+"$bytes")
global.SetInitializer(globalValue)
global.SetLinkage(llvm.InternalLinkage)
global.SetGlobalConstant(true)
@@ -436,40 +335,25 @@ func (fr *frame) evalBasicBlock(bb, incoming llvm.BasicBlock, indent string) (re
sliceType := inst.Type()
retPtr := llvm.ConstGEP(global, getLLVMIndices(fr.Mod.Context().Int32Type(), []uint32{0, 0}))
retLen := llvm.ConstInt(sliceType.StructElementTypes()[1], uint64(len(result)), false)
ret := llvm.ConstNull(sliceType)
ret := getZeroValue(sliceType)
ret = llvm.ConstInsertValue(ret, retPtr, []uint32{0}) // ptr
ret = llvm.ConstInsertValue(ret, retLen, []uint32{1}) // len
ret = llvm.ConstInsertValue(ret, retLen, []uint32{2}) // cap
fr.locals[inst] = &LocalValue{fr.Eval, ret}
case callee.Name() == "runtime.typeAssert":
actualTypeInt := fr.getLocal(inst.Operand(0)).(*LocalValue).Underlying
assertedType := fr.getLocal(inst.Operand(1)).(*LocalValue).Underlying
if actualTypeInt.IsAConstantExpr().IsNil() || actualTypeInt.Opcode() != llvm.PtrToInt {
return nil, nil, fr.errorAt(inst, errors.New("interp: expected typecode in runtime.typeAssert to be a ptrtoint"))
}
actualType := actualTypeInt.Operand(0)
if actualType.IsAConstant().IsNil() || assertedType.IsAConstant().IsNil() {
return nil, nil, fr.errorAt(inst, errors.New("interp: unimplemented: type assert with non-constant interface value"))
}
assertOk := uint64(0)
if llvm.ConstExtractValue(actualType.Initializer(), []uint32{0}) == assertedType {
assertOk = 1
}
fr.locals[inst] = &LocalValue{fr.Eval, llvm.ConstInt(fr.Mod.Context().Int1Type(), assertOk, false)}
case callee.Name() == "runtime.interfaceImplements":
typecode := fr.getLocal(inst.Operand(0)).(*LocalValue).Underlying
interfaceMethodSet := fr.getLocal(inst.Operand(1)).(*LocalValue).Underlying
if typecode.IsAConstantExpr().IsNil() || typecode.Opcode() != llvm.PtrToInt {
return nil, nil, fr.errorAt(inst, errors.New("interp: expected typecode to be a ptrtoint"))
panic("interp: expected typecode to be a ptrtoint")
}
typecode = typecode.Operand(0)
if interfaceMethodSet.IsAConstantExpr().IsNil() || interfaceMethodSet.Opcode() != llvm.GetElementPtr {
return nil, nil, fr.errorAt(inst, errors.New("interp: expected method set in runtime.interfaceImplements to be a constant gep"))
panic("interp: expected method set in runtime.interfaceImplements to be a constant gep")
}
interfaceMethodSet = interfaceMethodSet.Operand(0).Initializer()
methodSet := llvm.ConstExtractValue(typecode.Initializer(), []uint32{1})
if methodSet.IsAConstantExpr().IsNil() || methodSet.Opcode() != llvm.GetElementPtr {
return nil, nil, fr.errorAt(inst, errors.New("interp: expected method set to be a constant gep"))
panic("interp: expected method set to be a constant gep")
}
methodSet = methodSet.Operand(0).Initializer()
@@ -498,8 +382,6 @@ func (fr *frame) evalBasicBlock(bb, incoming llvm.BasicBlock, indent string) (re
fr.locals[inst] = &LocalValue{fr.Eval, llvm.ConstInt(fr.Mod.Context().Int64Type(), 0, false)}
case callee.Name() == "llvm.dbg.value":
// do nothing
case strings.HasPrefix(callee.Name(), "llvm.lifetime."):
// do nothing
case callee.Name() == "runtime.trackPointer":
// do nothing
case strings.HasPrefix(callee.Name(), "runtime.print") || callee.Name() == "runtime._panic":
@@ -541,10 +423,7 @@ func (fr *frame) evalBasicBlock(bb, incoming llvm.BasicBlock, indent string) (re
params = append(params, local)
}
var ret Value
scanResult, err := fr.hasSideEffects(callee)
if err != nil {
return nil, nil, err
}
scanResult := fr.Eval.hasSideEffects(callee)
if scanResult.severity == sideEffectLimited || dirtyParams && scanResult.severity != sideEffectAll {
// Side effect is bounded. This means the operation invokes
// side effects (like calling an external function) but it
@@ -567,13 +446,8 @@ func (fr *frame) evalBasicBlock(bb, incoming llvm.BasicBlock, indent string) (re
// compile time.
// * Unbounded: cannot call at runtime so we'll try to
// interpret anyway and hope for the best.
ret, err = fr.function(callee, params, indent+" ")
ret, err = fr.function(callee, params, fr.pkgName, indent+" ")
if err != nil {
// Record this function call in the backtrace.
err.Traceback = append(err.Traceback, ErrorLine{
Pos: getPosition(inst),
Inst: inst,
})
return nil, nil, err
}
}
@@ -582,7 +456,7 @@ func (fr *frame) evalBasicBlock(bb, incoming llvm.BasicBlock, indent string) (re
}
default:
// function pointers, etc.
return nil, nil, fr.unsupportedInstructionError(inst)
return nil, nil, &Unsupported{inst}
}
case !inst.IsAExtractValueInst().IsNil():
agg := fr.getLocal(inst.Operand(0)).(*LocalValue) // must be constant
@@ -592,7 +466,7 @@ func (fr *frame) evalBasicBlock(bb, incoming llvm.BasicBlock, indent string) (re
fr.locals[inst] = fr.getValue(newValue)
} else {
if len(indices) != 1 {
return nil, nil, fr.errorAt(inst, errors.New("interp: cannot handle extractvalue with not exactly 1 index"))
return nil, nil, errors.New("cannot handle extractvalue with not exactly 1 index")
}
fr.locals[inst] = &LocalValue{fr.Eval, fr.builder.CreateExtractValue(agg.Underlying, int(indices[0]), inst.Name())}
}
@@ -605,22 +479,10 @@ func (fr *frame) evalBasicBlock(bb, incoming llvm.BasicBlock, indent string) (re
fr.locals[inst] = &LocalValue{fr.Eval, newValue}
} else {
if len(indices) != 1 {
return nil, nil, fr.errorAt(inst, errors.New("interp: cannot handle insertvalue with not exactly 1 index"))
return nil, nil, errors.New("cannot handle insertvalue with not exactly 1 index")
}
fr.locals[inst] = &LocalValue{fr.Eval, fr.builder.CreateInsertValue(agg.Underlying, val.Value(), int(indices[0]), inst.Name())}
}
case !inst.IsASelectInst().IsNil():
// var result T
// if cond {
// result = x
// } else {
// result = y
// }
// return result
cond := fr.getLocal(inst.Operand(0)).(*LocalValue).Underlying
x := fr.getLocal(inst.Operand(1)).(*LocalValue).Underlying
y := fr.getLocal(inst.Operand(2)).(*LocalValue).Underlying
fr.locals[inst] = &LocalValue{fr.Eval, fr.builder.CreateSelect(cond, x, y, "")}
case !inst.IsAReturnInst().IsNil() && inst.OperandsCount() == 0:
return nil, nil, nil // ret void
@@ -630,17 +492,17 @@ func (fr *frame) evalBasicBlock(bb, incoming llvm.BasicBlock, indent string) (re
// conditional branch (if/then/else)
cond := fr.getLocal(inst.Operand(0)).Value()
if cond.Type() != fr.Mod.Context().Int1Type() {
return nil, nil, fr.errorAt(inst, errors.New("expected an i1 in a branch instruction"))
panic("expected an i1 in a branch instruction")
}
thenBB := inst.Operand(1)
elseBB := inst.Operand(2)
if !cond.IsAInstruction().IsNil() {
return nil, nil, fr.errorAt(inst, errors.New("interp: branch on a non-constant"))
return nil, nil, errors.New("interp: branch on a non-constant")
}
if !cond.IsAConstantExpr().IsNil() {
// This may happen when the instruction builder could not
// const-fold some instructions.
return nil, nil, fr.errorAt(inst, errors.New("interp: branch on a non-const-propagated constant expression"))
return nil, nil, errors.New("interp: branch on a non-const-propagated constant expression")
}
switch cond {
case llvm.ConstInt(fr.Mod.Context().Int1Type(), 0, false): // false
@@ -648,7 +510,7 @@ func (fr *frame) evalBasicBlock(bb, incoming llvm.BasicBlock, indent string) (re
case llvm.ConstInt(fr.Mod.Context().Int1Type(), 1, false): // true
return nil, []llvm.Value{elseBB}, nil // else
default:
return nil, nil, fr.errorAt(inst, errors.New("branch was not true or false"))
panic("branch was not true or false")
}
case !inst.IsABranchInst().IsNil() && inst.OperandsCount() == 1:
// unconditional branch (goto)
@@ -656,11 +518,10 @@ func (fr *frame) evalBasicBlock(bb, incoming llvm.BasicBlock, indent string) (re
case !inst.IsAUnreachableInst().IsNil():
// Unreachable was reached (e.g. after a call to panic()).
// Report this as an error, as it is not supposed to happen.
// This is a sentinel error value.
return nil, nil, errUnreachable
return nil, nil, ErrUnreachable
default:
return nil, nil, fr.unsupportedInstructionError(inst)
return nil, nil, &Unsupported{inst}
}
}
@@ -674,7 +535,6 @@ func (fr *frame) getLocal(v llvm.Value) Value {
} else if value := fr.getValue(v); value != nil {
return value
} else {
// This should not happen under normal circumstances.
panic("cannot find value")
}
}
+16 -25
View File
@@ -7,6 +7,7 @@ package interp
// methods.
import (
"errors"
"strings"
"tinygo.org/x/go-llvm"
@@ -21,17 +22,9 @@ type Eval struct {
sideEffectFuncs map[llvm.Value]*sideEffectResult // cache of side effect scan results
}
// evalPackage encapsulates the Eval type for just a single package. The Eval
// type keeps state across the whole program, the evalPackage type keeps extra
// state for the currently interpreted package.
type evalPackage struct {
*Eval
packagePath string
}
// Run evaluates the function with the given name and then eliminates all
// callers.
func Run(mod llvm.Module, debug bool) error {
func Run(mod llvm.Module, targetData llvm.TargetData, debug bool) error {
if debug {
println("\ncompile-time evaluation:")
}
@@ -39,7 +32,7 @@ func Run(mod llvm.Module, debug bool) error {
name := "runtime.initAll"
e := &Eval{
Mod: mod,
TargetData: llvm.NewTargetData(mod.DataLayout()),
TargetData: targetData,
Debug: debug,
dirtyGlobals: map[llvm.Value]struct{}{},
}
@@ -63,7 +56,7 @@ func Run(mod llvm.Module, debug bool) error {
break // ret void
}
if inst.IsACallInst().IsNil() || inst.CalledValue().IsAFunction().IsNil() {
return errorAt(inst, "interp: expected all instructions in "+name+" to be direct calls")
return errors.New("expected all instructions in " + name + " to be direct calls")
}
initCalls = append(initCalls, inst)
}
@@ -73,17 +66,13 @@ func Run(mod llvm.Module, debug bool) error {
for _, call := range initCalls {
initName := call.CalledValue().Name()
if !strings.HasSuffix(initName, ".init") {
return errorAt(call, "interp: expected all instructions in "+name+" to be *.init() calls")
return errors.New("expected all instructions in " + name + " to be *.init() calls")
}
pkgName := initName[:len(initName)-5]
fn := call.CalledValue()
call.EraseFromParentAsInstruction()
evalPkg := evalPackage{
Eval: e,
packagePath: pkgName,
}
_, err := evalPkg.function(fn, []Value{&LocalValue{e, undefPtr}, &LocalValue{e, undefPtr}}, "")
if err == errUnreachable {
_, err := e.Function(fn, []Value{&LocalValue{e, undefPtr}, &LocalValue{e, undefPtr}}, pkgName)
if err == ErrUnreachable {
break
}
if err != nil {
@@ -94,14 +83,16 @@ func Run(mod llvm.Module, debug bool) error {
return nil
}
// function interprets the given function. The params are the function params
// and the indent is the string indentation to use when dumping all interpreted
// instructions.
func (e *evalPackage) function(fn llvm.Value, params []Value, indent string) (Value, *Error) {
func (e *Eval) Function(fn llvm.Value, params []Value, pkgName string) (Value, error) {
return e.function(fn, params, pkgName, "")
}
func (e *Eval) function(fn llvm.Value, params []Value, pkgName, indent string) (Value, error) {
fr := frame{
evalPackage: e,
fn: fn,
locals: make(map[llvm.Value]Value),
Eval: e,
fn: fn,
pkgName: pkgName,
locals: make(map[llvm.Value]Value),
}
for i, param := range fn.Params() {
fr.locals[param] = params[i]
-103
View File
@@ -1,103 +0,0 @@
package interp
import (
"io/ioutil"
"os"
"strings"
"testing"
"tinygo.org/x/go-llvm"
)
func TestInterp(t *testing.T) {
for _, name := range []string{
"basic",
"slice-copy",
"consteval",
"map",
"interface",
} {
name := name // make tc local to this closure
t.Run(name, func(t *testing.T) {
t.Parallel()
runTest(t, "testdata/"+name)
})
}
}
func runTest(t *testing.T, pathPrefix string) {
// Read the input IR.
ctx := llvm.NewContext()
buf, err := llvm.NewMemoryBufferFromFile(pathPrefix + ".ll")
os.Stat(pathPrefix + ".ll") // make sure this file is tracked by `go test` caching
if err != nil {
t.Fatalf("could not read file %s: %v", pathPrefix+".ll", err)
}
mod, err := ctx.ParseIR(buf)
if err != nil {
t.Fatalf("could not load module:\n%v", err)
}
// Perform the transform.
err = Run(mod, false)
if err != nil {
t.Fatal(err)
}
// Run some cleanup passes to get easy-to-read outputs.
pm := llvm.NewPassManager()
defer pm.Dispose()
pm.AddGlobalOptimizerPass()
pm.AddDeadStoreEliminationPass()
pm.Run(mod)
// Read the expected output IR.
out, err := ioutil.ReadFile(pathPrefix + ".out.ll")
if err != nil {
t.Fatalf("could not read output file %s: %v", pathPrefix+".out.ll", err)
}
// See whether the transform output matches with the expected output IR.
expected := string(out)
actual := mod.String()
if !fuzzyEqualIR(expected, actual) {
t.Logf("output does not match expected output:\n%s", actual)
t.Fail()
}
}
// fuzzyEqualIR returns true if the two LLVM IR strings passed in are roughly
// equal. That means, only relevant lines are compared (excluding comments
// etc.).
func fuzzyEqualIR(s1, s2 string) bool {
lines1 := filterIrrelevantIRLines(strings.Split(s1, "\n"))
lines2 := filterIrrelevantIRLines(strings.Split(s2, "\n"))
if len(lines1) != len(lines2) {
return false
}
for i, line := range lines1 {
if line != lines2[i] {
return false
}
}
return true
}
// filterIrrelevantIRLines removes lines from the input slice of strings that
// are not relevant in comparing IR. For example, empty lines and comments are
// stripped out.
func filterIrrelevantIRLines(lines []string) []string {
var out []string
for _, line := range lines {
line = strings.TrimSpace(line) // drop '\r' on Windows
if line == "" || line[0] == ';' {
continue
}
if strings.HasPrefix(line, "source_filename = ") {
continue
}
out = append(out, line)
}
return out
}
+22 -64
View File
@@ -1,29 +1,11 @@
package interp
import (
"errors"
"strings"
"tinygo.org/x/go-llvm"
)
type sideEffectSeverity int
func (severity sideEffectSeverity) String() string {
switch severity {
case sideEffectInProgress:
return "in progress"
case sideEffectNone:
return "none"
case sideEffectLimited:
return "limited"
case sideEffectAll:
return "all"
default:
return "unknown"
}
}
const (
sideEffectInProgress sideEffectSeverity = iota // computing side effects is in progress (for recursive functions)
sideEffectNone // no side effects at all (pure)
@@ -41,38 +23,28 @@ type sideEffectResult struct {
// hasSideEffects scans this function and all descendants, recursively. It
// returns whether this function has side effects and if it does, which globals
// it mentions anywhere in this function or any called functions.
func (e *evalPackage) hasSideEffects(fn llvm.Value) (*sideEffectResult, *Error) {
name := fn.Name()
switch {
case name == "runtime.alloc":
func (e *Eval) hasSideEffects(fn llvm.Value) *sideEffectResult {
switch fn.Name() {
case "runtime.alloc":
// Cannot be scanned but can be interpreted.
return &sideEffectResult{severity: sideEffectNone}, nil
case name == "runtime.nanotime":
return &sideEffectResult{severity: sideEffectNone}
case "runtime.nanotime":
// Fixed value at compile time.
return &sideEffectResult{severity: sideEffectNone}, nil
case name == "runtime._panic":
return &sideEffectResult{severity: sideEffectLimited}, nil
case name == "runtime.typeAssert":
return &sideEffectResult{severity: sideEffectNone}, nil
case name == "runtime.interfaceImplements":
return &sideEffectResult{severity: sideEffectNone}, nil
case name == "runtime.sliceCopy":
return &sideEffectResult{severity: sideEffectNone}, nil
case name == "runtime.trackPointer":
return &sideEffectResult{severity: sideEffectNone}, nil
case name == "llvm.dbg.value":
return &sideEffectResult{severity: sideEffectNone}, nil
case strings.HasPrefix(name, "llvm.lifetime."):
return &sideEffectResult{severity: sideEffectNone}, nil
}
if fn.IsDeclaration() {
return &sideEffectResult{severity: sideEffectLimited}, nil
return &sideEffectResult{severity: sideEffectNone}
case "runtime._panic":
return &sideEffectResult{severity: sideEffectLimited}
case "runtime.interfaceImplements":
return &sideEffectResult{severity: sideEffectNone}
case "runtime.trackPointer":
return &sideEffectResult{severity: sideEffectNone}
case "llvm.dbg.value":
return &sideEffectResult{severity: sideEffectNone}
}
if e.sideEffectFuncs == nil {
e.sideEffectFuncs = make(map[llvm.Value]*sideEffectResult)
}
if se, ok := e.sideEffectFuncs[fn]; ok {
return se, nil
return se
}
result := &sideEffectResult{
severity: sideEffectInProgress,
@@ -83,7 +55,6 @@ func (e *evalPackage) hasSideEffects(fn llvm.Value) (*sideEffectResult, *Error)
for bb := fn.EntryBasicBlock(); !bb.IsNil(); bb = llvm.NextBasicBlock(bb) {
for inst := bb.FirstInstruction(); !inst.IsNil(); inst = llvm.NextInstruction(inst) {
if inst.IsAInstruction().IsNil() {
// Should not happen in valid IR.
panic("not an instruction")
}
@@ -100,7 +71,7 @@ func (e *evalPackage) hasSideEffects(fn llvm.Value) (*sideEffectResult, *Error)
switch inst.InstructionOpcode() {
case llvm.IndirectBr, llvm.Invoke:
// Not emitted by the compiler.
return nil, e.errorAt(inst, errors.New("unknown instructions"))
panic("unknown instructions")
case llvm.Call:
child := inst.CalledValue()
if !child.IsAInlineAsm().IsNil() {
@@ -121,21 +92,12 @@ func (e *evalPackage) hasSideEffects(fn llvm.Value) (*sideEffectResult, *Error)
if child.IsDeclaration() {
// External function call. Assume only limited side effects
// (no affected globals, etc.).
switch child.Name() {
case "runtime.typeAssert":
continue // implemented in interp
case "runtime.interfaceImplements":
continue // implemented in interp
}
if e.hasLocalSideEffects(dirtyLocals, inst) {
result.updateSeverity(sideEffectLimited)
}
continue
}
childSideEffects, err := e.hasSideEffects(child)
if err != nil {
return nil, err
}
childSideEffects := e.hasSideEffects(child)
switch childSideEffects.severity {
case sideEffectInProgress, sideEffectNone:
// no side effects or recursive function - continue scanning
@@ -177,7 +139,7 @@ func (e *evalPackage) hasSideEffects(fn llvm.Value) (*sideEffectResult, *Error)
// No side effect was reported for this function.
result.severity = sideEffectNone
}
return result, nil
return result
}
// hasLocalSideEffects checks whether the given instruction flows into a branch
@@ -192,7 +154,6 @@ func (e *Eval) hasLocalSideEffects(dirtyLocals map[llvm.Value]struct{}, inst llv
for use := inst.FirstUse(); !use.IsNil(); use = use.NextUse() {
user := use.User()
if user.IsAInstruction().IsNil() {
// Should not happen in valid IR.
panic("user not an instruction")
}
switch user.InstructionOpcode() {
@@ -212,13 +173,10 @@ func (e *Eval) hasLocalSideEffects(dirtyLocals map[llvm.Value]struct{}, inst llv
// Already handled in (*Eval).hasSideEffects.
continue
}
// This store might affect all kinds of values. While it is
// certainly possible to traverse through all of them, the easiest
// option right now is to just assume the worst and say that this
// function has side effects.
// TODO: traverse through all stores and mark all relevant allocas /
// globals dirty.
return true
// But a store might also store to an alloca, in which case all uses
// of the alloca (possibly indirect through a GEP, bitcast, etc.)
// must be marked dirty.
panic("todo: store")
default:
// All instructions that take 0 or more operands (1 or more if it
// was a use) and produce a result.
-95
View File
@@ -1,95 +0,0 @@
package interp
import (
"os"
"sort"
"testing"
"tinygo.org/x/go-llvm"
)
var scanTestTable = []struct {
name string
severity sideEffectSeverity
mentionsGlobals []string
}{
{"returnsConst", sideEffectNone, nil},
{"returnsArg", sideEffectNone, nil},
{"externalCallOnly", sideEffectNone, nil},
{"externalCallAndReturn", sideEffectLimited, nil},
{"externalCallBranch", sideEffectLimited, nil},
{"readCleanGlobal", sideEffectNone, []string{"cleanGlobalInt"}},
{"readDirtyGlobal", sideEffectLimited, []string{"dirtyGlobalInt"}},
{"callFunctionPointer", sideEffectAll, []string{"functionPointer"}},
{"getDirtyPointer", sideEffectLimited, nil},
{"storeToPointer", sideEffectLimited, nil},
{"callTypeAssert", sideEffectNone, nil},
{"callInterfaceImplements", sideEffectNone, nil},
}
func TestScan(t *testing.T) {
t.Parallel()
// Read the input IR.
path := "testdata/scan.ll"
ctx := llvm.NewContext()
buf, err := llvm.NewMemoryBufferFromFile(path)
os.Stat(path) // make sure this file is tracked by `go test` caching
if err != nil {
t.Fatalf("could not read file %s: %v", path, err)
}
mod, err := ctx.ParseIR(buf)
if err != nil {
t.Fatalf("could not load module:\n%v", err)
}
// Check all to-be-tested functions.
for _, tc := range scanTestTable {
// Create an eval object, for testing.
e := &Eval{
Mod: mod,
TargetData: llvm.NewTargetData(mod.DataLayout()),
dirtyGlobals: map[llvm.Value]struct{}{},
}
// Mark some globals dirty, for testing.
e.markDirty(mod.NamedGlobal("dirtyGlobalInt"))
// Scan for side effects.
fn := mod.NamedFunction(tc.name)
if fn.IsNil() {
t.Errorf("scan test: could not find tested function %s in the IR", tc.name)
continue
}
evalPkg := &evalPackage{e, "testdata"}
result, err := evalPkg.hasSideEffects(fn)
if err != nil {
t.Errorf("scan test: failed to scan %s for side effects: %v", fn.Name(), err)
}
// Check whether the result is what we expect.
if result.severity != tc.severity {
t.Errorf("scan test: function %s should have severity %s but it has %s", tc.name, tc.severity, result.severity)
}
// Check whether the mentioned globals match with what we'd expect.
mentionsGlobalNames := make([]string, 0, len(result.mentionsGlobals))
for global := range result.mentionsGlobals {
mentionsGlobalNames = append(mentionsGlobalNames, global.Name())
}
sort.Strings(mentionsGlobalNames)
globalsMismatch := false
if len(result.mentionsGlobals) != len(tc.mentionsGlobals) {
globalsMismatch = true
} else {
for i, globalName := range mentionsGlobalNames {
if tc.mentionsGlobals[i] != globalName {
globalsMismatch = true
}
}
}
if globalsMismatch {
t.Errorf("scan test: expected %s to mention globals %v, but it mentions globals %v", tc.name, tc.mentionsGlobals, mentionsGlobalNames)
}
}
}
-60
View File
@@ -1,60 +0,0 @@
target datalayout = "e-m:e-i64:64-f80:128-n8:16:32:64-S128"
target triple = "x86_64--linux"
@main.v1 = internal global i64 0
@main.nonConst1 = global [4 x i64] zeroinitializer
@main.nonConst2 = global i64 0
declare void @runtime.printint64(i64) unnamed_addr
declare void @runtime.printnl() unnamed_addr
define void @runtime.initAll() unnamed_addr {
entry:
call void @runtime.init()
call void @main.init()
ret void
}
define void @main() unnamed_addr {
entry:
%0 = load i64, i64* @main.v1
call void @runtime.printint64(i64 %0)
call void @runtime.printnl()
ret void
}
define internal void @runtime.init() unnamed_addr {
entry:
ret void
}
define internal void @main.init() unnamed_addr {
entry:
store i64 3, i64* @main.v1
call void @"main.init#1"()
; test the following pattern:
; func someValue() int // extern function
; var nonConst1 = [4]int{someValue(), 0, 0, 0}
%value1 = call i64 @someValue()
%gep1 = getelementptr [4 x i64], [4 x i64]* @main.nonConst1, i32 0, i32 0
store i64 %value1, i64* %gep1
; Test that the global really is marked dirty:
; var nonConst2 = nonConst1[0]
%gep2 = getelementptr [4 x i64], [4 x i64]* @main.nonConst1, i32 0, i32 0
%value2 = load i64, i64* %gep2
store i64 %value2, i64* @main.nonConst2
ret void
}
define internal void @"main.init#1"() unnamed_addr {
entry:
call void @runtime.printint64(i64 5)
call void @runtime.printnl()
ret void
}
declare i64 @someValue()
-29
View File
@@ -1,29 +0,0 @@
target datalayout = "e-m:e-i64:64-f80:128-n8:16:32:64-S128"
target triple = "x86_64--linux"
@main.nonConst1 = local_unnamed_addr global [4 x i64] zeroinitializer
@main.nonConst2 = local_unnamed_addr global i64 0
declare void @runtime.printint64(i64) unnamed_addr
declare void @runtime.printnl() unnamed_addr
define void @runtime.initAll() unnamed_addr {
entry:
call void @runtime.printint64(i64 5)
call void @runtime.printnl()
%value1 = call i64 @someValue()
store i64 %value1, i64* getelementptr inbounds ([4 x i64], [4 x i64]* @main.nonConst1, i32 0, i32 0)
%value2 = load i64, i64* getelementptr inbounds ([4 x i64], [4 x i64]* @main.nonConst1, i32 0, i32 0)
store i64 %value2, i64* @main.nonConst2
ret void
}
define void @main() unnamed_addr {
entry:
call void @runtime.printint64(i64 3)
call void @runtime.printnl()
ret void
}
declare i64 @someValue() local_unnamed_addr

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