Compare commits

..

3 Commits

Author SHA1 Message Date
Ayke van Laethem a1e69bbc13 WIP: precise GC 2019-05-14 14:20:39 +02:00
Ayke van Laethem 1b4d71bd3d runtime: refactor garbage collectors 2019-05-14 14:16:24 +02:00
Ayke van Laethem 43b3bb6e83 all: rename garbage collectors
dumb -> leaking:
  make it more clear what this "GC" does: leak everything.
marksweep -> conservative:
  "marksweep" is too generic, use "conservative" to differentiate
  between future garbage collectors: precise marksweep / mark-compact /
  refcounting.
2019-05-14 14:16:16 +02:00
508 changed files with 11268 additions and 38563 deletions
+91 -165
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 \
@@ -37,30 +40,51 @@ commands:
sudo tar -C /usr/local -xf node-v10.15.1-linux-x64.tar.xz
sudo ln -s /usr/local/node-v10.15.1-linux-x64/bin/node /usr/bin/node
rm node-v10.15.1-linux-x64.tar.xz
dep:
steps:
- run:
name: "Install Go dependencies"
command: |
curl https://raw.githubusercontent.com/golang/dep/master/install.sh | sh
dep ensure --vendor-only
llvm-source-linux:
steps:
- restore_cache:
keys:
- llvm-source-10-v0
- llvm-source-8-v2
- run:
name: "Fetch LLVM source"
command: make llvm-source
- save_cache:
key: llvm-source-10-v0
key: llvm-source-8-v2
paths:
- llvm-project
build-wasi-libc:
- llvm
smoketest:
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
- smoketest-no-avr
- run: tinygo build -size short -o test.elf -target=arduino examples/blinky1
- run: tinygo build -size short -o test.elf -target=digispark examples/blinky1
smoketest-no-avr:
steps:
- run: tinygo build -size short -o test.elf -target=pca10040 examples/blinky1
- run: tinygo build -size short -o test.elf -target=pca10040 examples/blinky2
- run: tinygo build -o blinky2 examples/blinky2 # TODO: re-enable -size flag with MachO support
- run: tinygo build -size short -o test.elf -target=pca10040 examples/test
- run: tinygo build -size short -o test.elf -target=microbit examples/echo
- run: tinygo build -size short -o test.elf -target=nrf52840-mdk examples/blinky1
- run: tinygo build -size short -o test.elf -target=pca10031 examples/blinky1
- run: tinygo build -size short -o test.elf -target=bluepill examples/blinky1
- run: tinygo build -size short -o test.elf -target=reelboard examples/blinky1
- run: tinygo build -size short -o test.elf -target=reelboard examples/blinky2
- run: tinygo build -size short -o test.elf -target=pca10056 examples/blinky1
- run: tinygo build -size short -o test.elf -target=pca10056 examples/blinky2
- run: tinygo build -size short -o test.elf -target=itsybitsy-m0 examples/blinky1
- run: tinygo build -size short -o test.elf -target=circuitplay-express examples/blinky1
- run: tinygo build -size short -o test.elf -target=stm32f4disco examples/blinky1
- run: tinygo build -size short -o test.elf -target=stm32f4disco examples/blinky2
- run: tinygo build -size short -o test.elf -target=circuitplay-express examples/i2s
- run: tinygo build -o wasm.wasm -target=wasm examples/wasm/export
- run: tinygo build -o wasm.wasm -target=wasm examples/wasm/main
test-linux:
parameters:
llvm:
@@ -69,91 +93,24 @@ commands:
- checkout
- submodules
- apt-dependencies:
llvm: "<<parameters.llvm>>"
llvm: <<parameters.llvm>>
- 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 ./compiler ./interp ./transform .
- dep
- run: go install .
- run: go test -v
- run: make gen-device -j4
- run: make smoketest
- smoketest
- 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 +119,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 +132,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-v4
- run:
name: "Build LLVM"
command: |
@@ -197,10 +155,16 @@ commands:
make llvm-build
fi
- save_cache:
key: llvm-build-10-linux-v0
key: llvm-build-8-linux-v4
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
- dep
- run:
name: "Test TinyGo"
command: make test
@@ -212,11 +176,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,7 +187,7 @@ commands:
tar -C ~/lib -xf /tmp/tinygo.linux-amd64.tar.gz
ln -s ~/lib/tinygo/bin/tinygo /go/bin/tinygo
tinygo version
- run: make smoketest
- smoketest
build-macos:
steps:
- checkout
@@ -232,27 +195,20 @@ 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
ln -s /usr/local/go/bin/go /usr/local/bin/go
HOMEBREW_NO_AUTO_UPDATE=1 brew install qemu
HOMEBREW_NO_AUTO_UPDATE=1 brew install go dep 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-v2
- run:
name: "Fetch LLVM source"
command: make llvm-source
- save_cache:
key: llvm-source-10-macos-v0
key: llvm-source-8-macos-v2
paths:
- llvm-project
- llvm
- restore_cache:
keys:
- llvm-build-10-macos-v0
- llvm-build-8-macos-v3
- run:
name: "Build LLVM"
command: |
@@ -264,19 +220,16 @@ commands:
make llvm-build
fi
- save_cache:
key: llvm-build-10-macos-v0
key: llvm-build-8-macos-v3
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: "Install Go dependencies"
command: dep ensure --vendor-only
- run:
name: "Test TinyGo"
command: make test
@@ -294,58 +247,34 @@ commands:
tar -C /usr/local/opt -xf /tmp/tinygo.darwin-amd64.tar.gz
ln -s /usr/local/opt/tinygo/bin/tinygo /usr/local/bin/tinygo
tinygo version
- run:
name: "Download SiFive GNU toolchain"
command: |
curl -O https://static.dev.sifive.com/dev-tools/riscv64-unknown-elf-gcc-8.2.0-2019.05.3-x86_64-apple-darwin.tar.gz
sudo tar -C /usr/local --strip-components=1 -xf riscv64-unknown-elf-gcc-8.2.0-2019.05.3-x86_64-apple-darwin.tar.gz
- run: make smoketest AVR=0
- save_cache:
key: go-cache-macos-v2-{{ checksum "go.mod" }}-{{ .Environment.CIRCLE_BUILD_NUM }}
paths:
- ~/.cache/go-build
- ~/.cache/tinygo
- /go/pkg/mod
- smoketest-no-avr
jobs:
test-llvm9-go111:
test-llvm8-go111:
docker:
- image: circleci/golang:1.11-buster
- image: circleci/golang:1.11
working_directory: /go/src/github.com/tinygo-org/tinygo
steps:
- test-linux:
llvm: "9"
test-llvm10-go112:
llvm: "-8"
test-llvm8-go112:
docker:
- image: circleci/golang:1.12-buster
- image: circleci/golang:1.12
working_directory: /go/src/github.com/tinygo-org/tinygo
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
llvm: "-8"
build-linux:
docker:
- image: circleci/golang:1.14-stretch
- image: circleci/golang:1.12
working_directory: /go/src/github.com/tinygo-org/tinygo
steps:
- build-linux
build-macos:
macos:
xcode: "10.1.0"
working_directory: ~/go/src/github.com/tinygo-org/tinygo
steps:
- build-macos
@@ -355,10 +284,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
+3 -5
View File
@@ -5,12 +5,10 @@ src/device/avr/*.ld
src/device/avr/*.s
src/device/nrf/*.go
src/device/nrf/*.s
src/device/sam/*.go
src/device/sam/*.s
src/device/sifive/*.go
src/device/sifive/*.s
src/device/stm32/*.go
src/device/stm32/*.s
src/device/sam/*.go
src/device/sam/*.s
vendor
llvm
llvm-build
llvm-project
-6
View File
@@ -14,9 +14,3 @@
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
+5 -8
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
@@ -26,20 +27,16 @@ 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 first step is to download the TinyGo sources. 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
-425
View File
@@ -1,428 +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**
- `atsamd21`: add support for the `-port` flag in the flash subcommand
0.7.0
---
* **command line**
- try more locations to find Clang built-in headers
- add support for `tinygo test`
- build current directory if no package is specified
- support custom .json target spec with `-target` flag
- use zversion.go to detect version of GOROOT version
- make initial heap size configurable for some targets (currently WebAssembly
only)
* **cgo**
- add support for bitfields using generated getters and setters
- add support for anonymous structs
* **compiler**
- show an error instead of panicking on duplicate function definitions
- allow packages like github.com/tinygo-org/tinygo/src/\* by aliasing it
- remove `//go:volatile` support
It has been replaced with the runtime/volatile package.
- allow poiners in map keys
- support non-constant syscall numbers
- implement non-blocking selects
- add support for the `-tags` flag
- add support for `string` to `[]rune` conversion
- implement a portable conservative garbage collector (with support for wasm)
- add the `//go:noinline` pragma
* **standard library**
- `os`: add `os.Exit` and `syscall.Exit`
- `os`: add several stubs
- `runtime`: fix heap corruption in conservative GC
- `runtime`: add support for math intrinsics where supported, massively
speeding up some benchmarks
- `testing`: add basic support for testing
* **targets**
- add support for a generic target that calls `__tinygo_*` functions for
peripheral access
- `arduino-nano33`: add support for this board
- `hifive1`: add support for this RISC-V board
- `reelboard`: add e-paper pins
- `reelboard`: add `PowerSupplyActive` to enable voltage for on-board devices
- `wasm`: put the stack at the start of linear memory, to detect stack
overflows
0.6.0
---
* **command line**
- some portability improvements
- make `$GOROOT` more robust and configurable
- check for Clang at the Homebrew install location as fallback
* **compiler driver**
- support multiple variations of LLVM commands, for non-Debian distributions
* **compiler**
- improve code quality in multiple ways
- make panic configurable, adding trap on panic
- refactor many internal parts of the compiler
- print all errors encountered during compilation
- implement calling function values of a named type
- implement returning values from blocking functions
- allow larger-than-int values to be sent across a channel
- implement complex arithmetic
- improve hashmap support
- add debuginfo for function arguments
- insert nil checks on stores (increasing code size)
- implement volatile operations as compiler builtins
- add `//go:inline` pragma
- add build tags for the Go stdlib version
* **cgo**
- implement `char`, `enum` and `void*` types
- support `#include` for builtin headers
- improve typedef/struct/enum support
- only include symbols that are necessary, for broader support
- mark external function args as `nocapture`
- implement support for some `#define` constants
- implement support for multiple CGo files in a single package
- **standard library**
- `machine`: remove microbit matrix (moved to drivers repository)
- `machine`: refactor pins to use `Pin` type instead of `GPIO`
- `runtime`: print more interface types on panic, including `error`
* **targets**
- `arm`: print an error on HardFault (including stack overflows)
- `atsamd21`: fix a bug in the ADC peripheral
- `atsamd21`: add support for I2S
- `feather-m0`: add support for this board
- `nrf51`: fix a bug in I2C
- `stm32f103xx`: fix a bug in I2C
- `syscall`: implement `Exit` on unix
- `trinket-m0`: add support for this board
- `wasm`: make _main_ example smaller
- `wasm`: don't cache wasm file in the server, for ease of debugging
- `wasm`: work around bug #41508 that caused a deadlock while linking
- `wasm`: add support for `js.FuncOf`
0.5.0
---
- **compiler driver**
-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
-4
View File
@@ -16,16 +16,12 @@ Please open a Github issue with your problem, and we will be happy to assist.
We probably have not implemented it yet. Please take a look at our [Roadmap](https://github.com/tinygo-org/tinygo/wiki/Roadmap). Your pull request adding the functionality to TinyGo would be greatly appreciated.
Please open a Github issue. We want to help, and also make sure that there is no duplications of efforts. Sometimes what you need is already being worked on by someone else.
A long tail of small (and large) language features haven't been implemented yet. In almost all cases, the compiler will show a `todo:` error from `compiler/compiler.go` when you try to use it. You can try implementing it, or open a bug report with a small code sample that fails to compile.
### Some specific hardware you want to use does not appear to be in TinyGo
As above, we probably have not implemented it yet. Your contribution adding the hardware support to TinyGo would be greatly appreciated.
Please start by opening a Github issue. We want to help you to help us to help you.
Lots of targets/boards are still unsupported. Adding an architecture often requires a few compiler changes, but if the architecture is supported you can try implementing support for a new chip or board in `src/runtime`. For details, see [this wiki entry on adding archs/chips/boards](https://github.com/tinygo-org/tinygo/wiki/Adding-a-new-board).
Microcontrollers have lots of peripherals (I2C, SPI, ADC, etc.) and many don't have an implementation yet in the `machine` package. Adding support for new peripherals is very useful.
-18
View File
@@ -1,18 +0,0 @@
# This is the official list of TinyGo authors for copyright purposes.
#
# This file is not actively maintained.
# To be included, send a change adding the individual or
# company who owns a contribution's copyright.
#
# Names should be added to this file as one of
# Organization's name
# Individual's name <submission email address>
# Individual's name <submission email address> <email2> <emailN>
#
# Please keep the list sorted.
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/stretch/ llvm-toolchain-stretch-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/stretch/ llvm-toolchain-stretch-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 make && \
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 make && \
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 make && \
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
+47 -285
View File
@@ -1,328 +1,100 @@
# aliases
all: tinygo
tinygo: build/tinygo
.PHONY: all tinygo build/tinygo test llvm-build llvm-source clean fmt gen-device gen-device-nrf gen-device-avr
# 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/tools/clang
LLD_SRC ?= llvm/tools/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
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
# Go binary and GOROOT to select
GO ?= go
export GOROOT = $(shell $(GO) env GOROOT)
# 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 -lclangToolingRefactor $(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)
endif
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))
clean:
@rm -rf build
FMT_PATHS = ./*.go builder cgo compiler compiler/testdata interp loader src/device/arm src/examples src/machine src/os src/reflect src/runtime src/sync src/syscall src/internal/reflectlite transform
FMT_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:
@unformatted=$$(gofmt -l $(FMT_PATHS)); [ -z "$$unformatted" ] && exit 0; echo "Unformatted:"; for fn in $$unformatted; do echo " $$fn"; done; exit 1
gen-device: gen-device-avr gen-device-nrf gen-device-sam gen-device-sifive gen-device-stm32
gen-device: gen-device-avr gen-device-nrf gen-device-sam 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: 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/README.txt:
git clone -b release_80 https://github.com/llvm-mirror/llvm.git llvm
llvm/tools/clang/README.txt:
git clone -b release_80 https://github.com/llvm-mirror/clang.git llvm/tools/clang
llvm/tools/lld/README.md:
git clone -b release_80 https://github.com/llvm-mirror/lld.git llvm/tools/lld
llvm-source: llvm/README.txt llvm/tools/clang/README.txt llvm/tools/lld/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)
llvm-build/build.ninja: llvm-source
mkdir -p llvm-build; cd llvm-build; cmake -G Ninja ../llvm "-DLLVM_TARGETS_TO_BUILD=X86;ARM;AArch64;WebAssembly" "-DLLVM_EXPERIMENTAL_TARGETS_TO_BUILD=AVR" -DCMAKE_BUILD_TYPE=Release -DLLVM_ENABLE_ASSERTIONS=OFF -DLIBCLANG_BUILD_STATIC=ON -DLLVM_ENABLE_TERMINFO=OFF -DLLVM_ENABLE_ZLIB=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)
llvm-build: llvm-build/build.ninja
cd llvm-build; ninja
# Build the Go compiler.
tinygo:
@if [ ! -f "$(LLVM_BUILDDIR)/bin/llvm-config" ]; then echo "Fetch and build LLVM first by running:"; echo " make llvm-source"; echo " make $(LLVM_BUILDDIR)"; exit 1; fi
CGO_CPPFLAGS="$(CGO_CPPFLAGS)" CGO_CXXFLAGS="$(CGO_CXXFLAGS)" CGO_LDFLAGS="$(CGO_LDFLAGS)" $(GO) build -o build/tinygo$(EXE) -tags byollvm .
build/tinygo:
@if [ ! -f llvm-build/bin/llvm-config ]; then echo "Fetch and build LLVM first by running:\n make llvm-source\n make llvm-build"; exit 1; fi
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 ./compiler ./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
# 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
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
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
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 +102,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
+8 -31
View File
@@ -1,8 +1,8 @@
# 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.
TinyGo is a Go compiler intended for use in small places such as microcontrollers, WebAssembly (WASM), and command-line tools.
It reuses libraries used by the [Go language tools](https://golang.org/pkg/go/) alongside [LLVM](http://llvm.org) to provide an alternative way to compile programs written in the Go programming language.
@@ -17,8 +17,8 @@ import (
)
func main() {
led := machine.LED
led.Configure(machine.PinConfig{Mode: machine.PinOutput})
led := machine.GPIO{machine.LED}
led.Configure(machine.GPIOConfig{Mode: machine.GPIO_OUTPUT})
for {
led.Low()
time.Sleep(time.Millisecond * 1000)
@@ -43,40 +43,19 @@ 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 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/)
* [BBC:Microbit](https://microbit.org/)
* [ST Micro STM32F103XX "Bluepill"](http://wiki.stm32duino.com/index.php?title=Blue_Pill)
* [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 +114,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 := 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
}
-127
View File
@@ -1,127 +0,0 @@
package builder
import (
"errors"
"fmt"
"io"
"io/ioutil"
"os"
"os/exec"
"path/filepath"
"regexp"
"sort"
"strings"
)
// getGorootVersion returns the major and minor version for a given GOROOT path.
// If the goroot cannot be determined, (0, 0) is returned.
func getGorootVersion(goroot string) (major, minor int, err error) {
s, err := GorootVersionString(goroot)
if err != nil {
return 0, 0, err
}
if s == "" || s[:2] != "go" {
return 0, 0, errors.New("could not parse Go version: version does not start with 'go' prefix")
}
parts := strings.Split(s[2:], ".")
if len(parts) < 2 {
return 0, 0, errors.New("could not parse Go version: version has less than two parts")
}
// Ignore the errors, we don't really handle errors here anyway.
var trailing string
n, err := fmt.Sscanf(s, "go%d.%d%s", &major, &minor, &trailing)
if n == 2 && err == io.EOF {
// Means there were no trailing characters (i.e., not an alpha/beta)
err = nil
}
if err != nil {
return 0, 0, fmt.Errorf("failed to parse version: %s", err)
}
return
}
// GorootVersionString returns the version string as reported by the Go
// toolchain for the given GOROOT path. It is usually of the form `go1.x.y` but
// can have some variations (for beta releases, for example).
func GorootVersionString(goroot string) (string, error) {
if data, err := ioutil.ReadFile(filepath.Join(
goroot, "src", "runtime", "internal", "sys", "zversion.go")); err == nil {
r := regexp.MustCompile("const TheVersion = `(.*)`")
matches := r.FindSubmatch(data)
if len(matches) != 2 {
return "", errors.New("Invalid go version output:\n" + string(data))
}
return string(matches[1]), nil
} else if data, err := ioutil.ReadFile(filepath.Join(goroot, "VERSION")); err == nil {
return string(data), nil
} else {
return "", err
}
}
// getClangHeaderPath returns the path to the built-in Clang headers. It tries
// multiple locations, which should make it find the directory when installed in
// various ways.
func getClangHeaderPath(TINYGOROOT string) string {
// Check whether we're running from the source directory.
path := filepath.Join(TINYGOROOT, "llvm-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)
}
+61 -798
View File
File diff suppressed because it is too large Load Diff
-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)
}
}
}
+153 -305
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>
@@ -49,13 +49,9 @@ GoCXCursor tinygo_clang_Cursor_getArgument(GoCXCursor c, unsigned i);
CXSourceLocation tinygo_clang_getCursorLocation(GoCXCursor c);
CXSourceRange tinygo_clang_getCursorExtent(GoCXCursor c);
CXTranslationUnit tinygo_clang_Cursor_getTranslationUnit(GoCXCursor c);
long long tinygo_clang_getEnumConstantDeclValue(GoCXCursor c);
CXType tinygo_clang_getEnumDeclIntegerType(GoCXCursor c);
unsigned tinygo_clang_Cursor_isBitField(GoCXCursor c);
int tinygo_clang_globals_visitor(GoCXCursor c, GoCXCursor parent, CXClientData client_data);
int tinygo_clang_struct_visitor(GoCXCursor c, GoCXCursor parent, CXClientData client_data);
int tinygo_clang_enum_visitor(GoCXCursor c, GoCXCursor parent, CXClientData client_data);
*/
import "C"
@@ -109,8 +105,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 +114,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 +146,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 +233,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 +248,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 +312,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 +330,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 +344,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 +455,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:
@@ -518,86 +501,90 @@ func (p *cgoPackage) makeASTType(typ C.CXType, pos token.Pos) ast.Expr {
switch underlying.kind {
case C.CXType_Record:
return p.makeASTType(underlying, pos)
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
var cgoName string
switch C.tinygo_clang_getCursorKind(cursor) {
case C.CXCursor_StructDecl:
cgoRecordPrefix = "struct_"
cgoName = "struct_" + name
case C.CXCursor_UnionDecl:
cgoRecordPrefix = "union_"
cgoName = "union_" + name
default:
// makeASTRecordType will create an appropriate error.
cgoRecordPrefix = "record_"
panic("unknown record declaration")
}
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).
p.anonStructNum++
cgoName := cgoRecordPrefix + strconv.Itoa(p.anonStructNum)
p.elaboratedTypes[cgoName] = typeInfo
return &ast.Ident{
NamePos: pos,
Name: "C." + cgoName,
}
if _, ok := p.elaboratedTypes[cgoName]; !ok {
p.elaboratedTypes[cgoName] = nil // predeclare (to avoid endless recursion)
fieldList := &ast.FieldList{
Opening: pos,
Closing: pos,
}
return typeInfo.typeExpr
} else {
cgoName := cgoRecordPrefix + name
if _, ok := p.elaboratedTypes[cgoName]; !ok {
p.elaboratedTypes[cgoName] = nil // predeclare (to avoid endless recursion)
p.elaboratedTypes[cgoName] = p.makeASTRecordType(cursor, pos)
}
return &ast.Ident{
NamePos: pos,
Name: "C." + cgoName,
}
}
case C.CXType_Enum:
cursor := C.tinygo_clang_getTypeDeclaration(typ)
name := getString(C.tinygo_clang_getCursorSpelling(cursor))
underlying := C.tinygo_clang_getEnumDeclIntegerType(cursor)
if name == "" {
// anonymous enum
ref := storedRefs.Put(p)
ref := storedRefs.Put(struct {
fieldList *ast.FieldList
pkg *cgoPackage
}{fieldList, p})
defer storedRefs.Remove(ref)
C.tinygo_clang_visitChildren(cursor, C.CXCursorVisitor(C.tinygo_clang_enum_visitor), C.CXClientData(ref))
return p.makeASTType(underlying, pos)
} else {
// named enum
if _, ok := p.enums[name]; !ok {
ref := storedRefs.Put(p)
defer storedRefs.Remove(ref)
C.tinygo_clang_visitChildren(cursor, C.CXCursorVisitor(C.tinygo_clang_enum_visitor), C.CXClientData(ref))
p.enums[name] = enumInfo{
typeExpr: p.makeASTType(underlying, pos),
pos: pos,
C.tinygo_clang_visitChildren(cursor, C.CXCursorVisitor(C.tinygo_clang_struct_visitor), C.CXClientData(ref))
switch C.tinygo_clang_getCursorKind(cursor) {
case C.CXCursor_StructDecl:
p.elaboratedTypes[cgoName] = &elaboratedTypeInfo{
typeExpr: &ast.StructType{
Struct: pos,
Fields: fieldList,
},
pos: pos,
}
case C.CXCursor_UnionDecl:
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...)
}
p.elaboratedTypes[cgoName] = &elaboratedTypeInfo{
typeExpr: &ast.StructType{
Struct: pos,
Fields: fieldList,
},
pos: pos,
}
default:
panic("unreachable")
}
return &ast.Ident{
NamePos: pos,
Name: "C.enum_" + name,
}
}
return &ast.Ident{
NamePos: pos,
Name: "C." + cgoName,
}
}
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>"
// Fallback, probably incorrect but at least the error points to an odd
// type name.
typeName = "C." + getString(C.clang_getTypeSpelling(typ))
}
return &ast.Ident{
NamePos: pos,
@@ -605,151 +592,25 @@ 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 {
fieldList := &ast.FieldList{
Opening: pos,
Closing: pos,
}
var bitfieldList []bitfieldInfo
inBitfield := false
bitfieldNum := 0
ref := storedRefs.Put(struct {
fieldList *ast.FieldList
pkg *cgoPackage
inBitfield *bool
bitfieldNum *int
bitfieldList *[]bitfieldInfo
}{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,
}
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")
}
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")
}
typeInfo.unionSize = sizeInBytes
typeInfo.unionAlign = alignInBytes
return typeInfo
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,
}
}
}
//export tinygo_clang_struct_visitor
func tinygo_clang_struct_visitor(c, parent C.GoCXCursor, client_data C.CXClientData) C.int {
passed := storedRefs.Get(unsafe.Pointer(client_data)).(struct {
fieldList *ast.FieldList
pkg *cgoPackage
inBitfield *bool
bitfieldNum *int
bitfieldList *[]bitfieldInfo
fieldList *ast.FieldList
pkg *cgoPackage
})
fieldList := passed.fieldList
p := passed.pkg
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 == "" {
// Assume this is a bitfield of 0 bits.
// Warning: this is not necessarily true!
return C.CXChildVisit_Continue
}
typ := C.tinygo_clang_getCursorType(c)
field := &ast.Field{
Type: p.makeASTType(typ, p.getCursorPosition(c)),
}
offsetof := int64(C.clang_Type_getOffsetOf(C.tinygo_clang_getCursorType(parent), C.CString(name)))
alignOf := int64(C.clang_Type_getAlignOf(typ) * 8)
bitfieldOffset := offsetof % alignOf
if bitfieldOffset != 0 {
if C.tinygo_clang_Cursor_isBitField(c) != 1 {
p.addError(pos, "expected a bitfield")
return C.CXChildVisit_Continue
}
if !*inBitfield {
*bitfieldNum++
}
bitfieldName := "__bitfield_" + strconv.Itoa(*bitfieldNum)
prevField := fieldList.List[len(fieldList.List)-1]
if !*inBitfield {
// The previous element also was a bitfield, but wasn't noticed
// then. Add it now.
*inBitfield = true
*bitfieldList = append(*bitfieldList, bitfieldInfo{
field: prevField,
name: prevField.Names[0].Name,
startBit: 0,
pos: prevField.Names[0].NamePos,
})
prevField.Names[0].Name = bitfieldName
prevField.Names[0].Obj.Name = bitfieldName
}
prevBitfield := &(*bitfieldList)[len(*bitfieldList)-1]
prevBitfield.endBit = bitfieldOffset
*bitfieldList = append(*bitfieldList, bitfieldInfo{
field: prevField,
name: name,
startBit: bitfieldOffset,
pos: pos,
})
return C.CXChildVisit_Continue
}
*inBitfield = false
field.Names = []*ast.Ident{
&ast.Ident{
NamePos: pos,
NamePos: p.getCursorPosition(c),
Name: name,
Obj: &ast.Object{
Kind: ast.Var,
@@ -761,16 +622,3 @@ func tinygo_clang_struct_visitor(c, parent C.GoCXCursor, client_data C.CXClientD
fieldList.List = append(fieldList.List, field)
return C.CXChildVisit_Continue
}
//export tinygo_clang_enum_visitor
func tinygo_clang_enum_visitor(c, parent C.GoCXCursor, client_data C.CXClientData) C.int {
p := storedRefs.Get(unsafe.Pointer(client_data)).(*cgoPackage)
name := getString(C.tinygo_clang_getCursorSpelling(c))
pos := p.getCursorPosition(c)
value := C.tinygo_clang_getEnumConstantDeclValue(c)
p.constants[name] = constantInfo{
expr: &ast.BasicLit{pos, token.INT, strconv.FormatInt(int64(value), 10)},
pos: pos,
}
return C.CXChildVisit_Continue
}
+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 -13
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);
@@ -56,15 +56,3 @@ CXSourceRange tinygo_clang_getCursorExtent(CXCursor c) {
CXTranslationUnit tinygo_clang_Cursor_getTranslationUnit(CXCursor c) {
return clang_Cursor_getTranslationUnit(c);
}
long long tinygo_clang_getEnumConstantDeclValue(CXCursor c) {
return clang_getEnumConstantDeclValue(c);
}
CXType tinygo_clang_getEnumDeclIntegerType(CXCursor c) {
return clang_getEnumDeclIntegerType(c);
}
unsigned tinygo_clang_Cursor_isBitField(CXCursor c) {
return clang_Cursor_isBitField(c);
}
-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
+33
View File
@@ -0,0 +1,33 @@
package main
import (
"errors"
"os"
"os/exec"
"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 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 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
}
-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)
}
}
+76 -156
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.info.nobounds {
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.info.nobounds {
func (c *Compiler) emitSliceBoundsCheck(frame *Frame, capacity, low, high llvm.Value, lowType, highType *types.Basic) {
if frame.fn.IsNoBounds() {
// The //go:nobounds pragma was added to the function to avoid bounds
// checking.
return
@@ -62,168 +71,79 @@ func (b *builder) createSliceBoundsCheck(capacity, low, high, max llvm.Value, lo
if high.Type().IntTypeWidth() > capacityType.IntTypeWidth() {
capacityType = high.Type()
}
if max.Type().IntTypeWidth() > capacityType.IntTypeWidth() {
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, "")
}
}
if max.Type().IntTypeWidth() < capacityType.IntTypeWidth() {
if maxType.Info()&types.IsUnsigned != 0 {
max = b.CreateZExt(max, capacityType, "")
} else {
max = b.CreateSExt(max, capacityType, "")
high = c.builder.CreateSExt(high, 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, capacity, "slice.highcap")
outOfBounds := c.builder.CreateOr(outOfBounds1, outOfBounds2, "slice.outofbounds")
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.info.nobounds {
// 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) {
// 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
}
}
func (c *Compiler) emitNilCheck(frame *Frame, ptr llvm.Value, blockPrefix string) {
// 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.info.nobounds {
// 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.llvmFn, blockPrefix+".throw")
nextBlock := b.ctx.AddBasicBlock(b.llvmFn, blockPrefix+".next")
b.blockExits[b.currentBlock] = nextBlock // adjust outgoing block for phi nodes
// Now branch to the out-of-bounds or the regular block.
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)
}
+65 -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,78 +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
// 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)
}
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)
}
// 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 {
llvmFn := b.getFunctionRaw(b.getRuntimeFuncType(fnName), functionInfo{
linkName: "runtime." + fnName,
})
args = append(args, llvm.Undef(b.i8ptrType)) // unused context parameter
args = append(args, llvm.ConstPointerNull(b.i8ptrType)) // coroutine handle
return b.createCall(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 {
// paramter list.
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
@@ -95,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
@@ -121,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())
@@ -230,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
@@ -247,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 {
+51 -209
View File
@@ -6,238 +6,80 @@ package compiler
import (
"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, "")
}
return b.createRuntimeCall("chanMake", []llvm.Value{elementSizeValue, bufSize}, "")
// emitMakeChan returns a new channel value for the given channel type.
func (c *Compiler) emitMakeChan(expr *ssa.MakeChan) (llvm.Value, error) {
chanType := c.mod.GetTypeByName("runtime.channel")
size := c.targetData.TypeAllocSize(chanType)
sizeValue := llvm.ConstInt(c.uintptrType, size, false)
ptr := c.createRuntimeCall("alloc", []llvm.Value{sizeValue}, "chan.alloc")
ptr = c.builder.CreateBitCast(ptr, llvm.PointerType(chanType, 0), "chan")
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) {
valueType := c.getLLVMType(instr.X.Type())
ch := c.getValue(frame, instr.Chan)
chanValue := c.getValue(frame, instr.X)
valueSize := llvm.ConstInt(c.uintptrType, c.targetData.TypeAllocSize(chanValue.Type()), false)
coroutine := c.createRuntimeCall("getCoroutine", nil, "")
// store value-to-send
valueType := b.getLLVMType(instr.X.Type())
valueAlloca, valueAllocaCast, valueAllocaSize := b.createTemporaryAlloca(valueType, "chan.value")
b.CreateStore(chanValue, valueAlloca)
c.builder.SetInsertPointBefore(coroutine.InstructionParent().Parent().EntryBasicBlock().FirstInstruction())
valueAlloca := c.builder.CreateAlloca(valueType, "chan.value")
c.builder.SetInsertPointBefore(coroutine)
c.builder.SetInsertPointAtEnd(coroutine.InstructionParent())
c.builder.CreateStore(chanValue, valueAlloca)
valueAllocaCast := c.builder.CreateBitCast(valueAlloca, c.i8ptrType, "chan.value.i8ptr")
// Do the send.
b.createRuntimeCall("chanSend", []llvm.Value{ch, valueAllocaCast}, "")
c.createRuntimeCall("chanSend", []llvm.Value{coroutine, ch, valueAllocaCast, valueSize}, "")
// 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)
// Make sure CoroSplit includes the alloca in the coroutine frame.
// This is a bit dirty, but it works (at least in LLVM 8).
valueSizeI64 := llvm.ConstInt(c.ctx.Int64Type(), c.targetData.TypeAllocSize(chanValue.Type()), false)
c.builder.CreateCall(c.getLifetimeEndFunc(), []llvm.Value{valueSizeI64, valueAllocaCast}, "")
}
// 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())
valueSize := llvm.ConstInt(c.uintptrType, c.targetData.TypeAllocSize(valueType), false)
ch := c.getValue(frame, unop.X)
coroutine := c.createRuntimeCall("getCoroutine", nil, "")
// Allocate memory to receive into.
valueAlloca, valueAllocaCast, valueAllocaSize := b.createTemporaryAlloca(valueType, "chan.value")
c.builder.SetInsertPointBefore(coroutine.InstructionParent().Parent().EntryBasicBlock().FirstInstruction())
valueAlloca := c.builder.CreateAlloca(valueType, "chan.value")
c.builder.SetInsertPointBefore(coroutine)
c.builder.SetInsertPointAtEnd(coroutine.InstructionParent())
valueAllocaCast := c.builder.CreateBitCast(valueAlloca, c.i8ptrType, "chan.value.i8ptr")
// Do the receive.
commaOk := b.createRuntimeCall("chanRecv", []llvm.Value{ch, valueAllocaCast}, "")
received := b.CreateLoad(valueAlloca, "chan.received")
b.emitLifetimeEnd(valueAllocaCast, valueAllocaSize)
c.createRuntimeCall("chanRecv", []llvm.Value{coroutine, ch, valueAllocaCast, valueSize}, "")
received := c.builder.CreateLoad(valueAlloca, "chan.received")
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("getTaskPromiseData", []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}, "")
}
// createSelect 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 {
if len(expr.States) == 0 {
// Shortcuts for some simple selects.
llvmType := b.getLLVMType(expr.Type())
if expr.Blocking {
// Blocks forever:
// select {}
b.createRuntimeCall("deadlock", nil, "")
return llvm.Undef(llvmType)
} else {
// No-op:
// select {
// default:
// }
retval := llvm.Undef(llvmType)
retval = b.CreateInsertValue(retval, llvm.ConstInt(b.intType, 0xffffffffffffffff, true), 0, "")
return retval // {-1, false}
}
}
// This code create a (stack-allocated) slice containing all the select
// cases and then calls runtime.chanSelect to perform the actual select
// statement.
// Simple selects (blocking and with just one case) are already transformed
// into regular chan operations during SSA construction so we don't have to
// optimize such small selects.
// Go through all the cases. Create the selectStates slice and and
// determine the receive buffer size and alignment.
recvbufSize := uint64(0)
recvbufAlign := 0
hasReceives := false
var selectStates []llvm.Value
chanSelectStateType := b.getLLVMRuntimeType("chanSelectState")
for _, state := range expr.States {
ch := b.getValue(state.Chan)
selectState := llvm.ConstNull(chanSelectStateType)
selectState = b.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 {
recvbufSize = size
}
if align := b.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, "")
default:
panic("unreachable")
}
selectStates = append(selectStates, selectState)
}
// Create a receive buffer, where the received value will be stored.
recvbuf := llvm.Undef(b.i8ptrType)
if hasReceives {
allocaType := llvm.ArrayType(b.ctx.Int8Type(), int(recvbufSize))
recvbufAlloca, _, _ := b.createTemporaryAlloca(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),
}, "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")
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),
}, "")
b.CreateStore(state, gep)
}
statesPtr := b.CreateGEP(statesAlloca, []llvm.Value{
llvm.ConstInt(b.ctx.Int32Type(), 0, false),
llvm.ConstInt(b.ctx.Int32Type(), 0, false),
}, "select.states")
statesLen := llvm.ConstInt(b.uintptrType, uint64(len(selectStates)), 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)
// 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)
}
b.selectRecvBuf[expr] = recvbuf
return results
}
// getChanSelectResult returns the special values from a *ssa.Extract expression
// 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 {
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, "")
}
return index
} else if expr.Index == 1 {
// comma-ok
value := b.getValue(expr.Tuple)
return b.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, "")
}
// emitChanClose closes the given channel.
func (c *Compiler) emitChanClose(frame *Frame, param ssa.Value) {
valueType := c.getLLVMType(param.Type().(*types.Chan).Elem())
valueSize := llvm.ConstInt(c.uintptrType, c.targetData.TypeAllocSize(valueType), false)
ch := c.getValue(frame, param)
c.createRuntimeCall("chanClose", []llvm.Value{ch, valueSize}, "")
}
+1313 -1306
View File
File diff suppressed because it is too large Load Diff
-138
View File
@@ -1,138 +0,0 @@
package compiler
import (
"bytes"
"flag"
"fmt"
"go/ast"
"go/parser"
"go/token"
"go/types"
"io/ioutil"
"path/filepath"
"strings"
"sync"
"testing"
"github.com/tinygo-org/tinygo/compileopts"
"github.com/tinygo-org/tinygo/compiler/ircheck"
"golang.org/x/tools/go/ssa"
"golang.org/x/tools/go/ssa/ssautil"
"tinygo.org/x/go-llvm"
)
var flagUpdate = flag.Bool("update", false, "update all tests")
func TestCompiler(t *testing.T) {
t.Parallel()
for _, name := range []string{"basic"} {
t.Run(name, func(t *testing.T) {
runCompilerTest(t, name)
})
}
}
func runCompilerTest(t *testing.T, name string) {
// Read the AST in memory.
path := filepath.Join("testdata", name+".go")
fset := token.NewFileSet()
f, err := parser.ParseFile(fset, path, nil, parser.ParseComments)
if err != nil {
t.Fatal("could not parse Go source file:", err)
}
files := []*ast.File{f}
// Create Go SSA from the AST.
var typecheckErrors []error
var typecheckErrorsLock sync.Mutex
typesConfig := types.Config{
Error: func(err error) {
typecheckErrorsLock.Lock()
defer typecheckErrorsLock.Unlock()
typecheckErrors = append(typecheckErrors, err)
},
Importer: simpleImporter{},
Sizes: types.SizesFor("gccgo", "arm"),
}
pkg, _, err := ssautil.BuildPackage(&typesConfig, fset, types.NewPackage("main", ""), files, ssa.SanityCheckFunctions|ssa.BareInits|ssa.GlobalDebug)
for _, err := range typecheckErrors {
t.Error(err)
}
if err != nil && len(typecheckErrors) == 0 {
// Only report errors when no type errors are found (an
// unexpected condition).
t.Error(err)
}
if t.Failed() {
return
}
// Configure the compiler.
config := compileopts.Config{
Options: &compileopts.Options{},
Target: &compileopts.TargetSpec{
Triple: "armv7m-none-eabi",
BuildTags: []string{"cortexm", "baremetal", "linux", "arm"},
Scheduler: "tasks",
},
}
machine, err := NewTargetMachine(&config)
if err != nil {
t.Fatal(err)
}
c := newCompilerContext("main", machine, &config)
c.runtimePkg = types.NewPackage("runtime", "runtime")
c.taskPkg = types.NewPackage("internal/task", "task")
irbuilder := c.ctx.NewBuilder()
defer irbuilder.Dispose()
// Create LLVM IR from the Go SSA.
c.createPackage(pkg, irbuilder)
// Check the IR with the LLVM verifier.
if err := llvm.VerifyModule(c.mod, llvm.PrintMessageAction); err != nil {
t.Error("verification error after IR construction")
}
// Check the IR with our own verifier (which checks for different things).
errs := ircheck.Module(c.mod)
for _, err := range errs {
t.Error(err)
}
// Check whether the IR matches the expected IR.
ir := c.mod.String()
ir = ir[strings.Index(ir, "\ntarget datalayout = ")+1:]
outfile := filepath.Join("testdata", name+".ll")
if *flagUpdate {
err := ioutil.WriteFile(outfile, []byte(ir), 0666)
if err != nil {
t.Error("could not read output file:", err)
}
} else {
ir2, err := ioutil.ReadFile(outfile)
if err != nil {
t.Fatal("could not read input file:", err)
}
ir2 = bytes.Replace(ir2, []byte("\r\n"), []byte("\n"), -1)
if ir != string(ir2) {
t.Error("output did not match")
}
}
}
// simpleImporter implements the types.Importer interface, but only allows
// importing the unsafe package.
type simpleImporter struct {
}
// Import implements the Importer interface. For testing usage only: it only
// supports importing the unsafe package.
func (i simpleImporter) Import(path string) (*types.Package, error) {
switch path {
case "unsafe":
return types.Unsafe, nil
default:
return nil, fmt.Errorf("importer not implemented for package %s", path)
}
}
+110 -164
View File
@@ -14,7 +14,7 @@ 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"
)
@@ -22,99 +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[*ssa.Function]int)
b.deferInvokeFuncs = make(map[string]int)
b.deferClosureFuncs = make(map[*ssa.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.mod.GetTypeByName("runtime._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 := c.ir.GetFunction(callee)
if _, ok := b.deferFuncs[callee]; !ok {
b.deferFuncs[callee] = len(b.allDeferFuncs)
b.allDeferFuncs = append(b.allDeferFuncs, callee)
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[callee]), 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())
}
@@ -126,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 := 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())
}
@@ -150,41 +116,28 @@ 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")
}
if b.NeedsStackObjects() {
b.trackPointer(alloca)
}
b.CreateStore(deferFrame, alloca)
// Put this struct in an alloca.
alloca := c.builder.CreateAlloca(deferFrameType, "defer.alloca")
c.builder.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
@@ -201,44 +154,44 @@ func (b *builder) createRunDefers() {
// }
// Create loop.
loophead := b.ctx.AddBasicBlock(b.llvmFn, "rundefers.loophead")
loop := b.ctx.AddBasicBlock(b.llvmFn, "rundefers.loop")
unreachable := b.ctx.AddBasicBlock(b.llvmFn, "rundefers.default")
end := b.ctx.AddBasicBlock(b.llvmFn, "rundefers.end")
b.CreateBr(loophead)
loophead := llvm.AddBasicBlock(frame.fn.LLVMFn, "rundefers.loophead")
loop := llvm.AddBasicBlock(frame.fn.LLVMFn, "rundefers.loop")
unreachable := llvm.AddBasicBlock(frame.fn.LLVMFn, "rundefers.default")
end := llvm.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.llvmFn, "rundefers.callback")
sw.AddCase(llvm.ConstInt(b.uintptrType, uint64(i), false), block)
b.SetInsertPointAtEnd(block)
block := llvm.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.
@@ -247,112 +200,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.mod.GetTypeByName("runtime._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 *ssa.Function:
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.mod.GetTypeByName("runtime._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 !b.getFunctionInfo(callback).exported {
// Add the context parameter. We know it is ignored by the receiving
// function, but we have to pass one anyway.
forwardParams = append(forwardParams, llvm.Undef(b.i8ptrType))
// 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(b.getFunction(callback), forwardParams, "")
c.createCall(callback.LLVMFn, forwardParams, "")
case *ssa.MakeClosure:
// Get the real defer struct type and cast to it.
fn := 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.mod.GetTypeByName("runtime._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(b.getFunction(fn), 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)
}
+3 -49
View File
@@ -1,64 +1,18 @@
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.program.Fset,
Fset: c.ir.Program.Fset,
Pos: pos,
Msg: msg,
}
}
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{}
}
}
+269
View File
@@ -0,0 +1,269 @@
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.mod.GetTypeByName("runtime.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(fn)
// if func.rawPtr == nil {
// runtime.nilpanic()
// }
// result := func.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.
var funcCall llvm.Value
for _, inttoptr := range getUses(getFuncPtrCall) {
if inttoptr.IsAIntToPtrInst().IsNil() {
panic("expected inttoptr")
}
for _, ptrUse := range getUses(inttoptr) {
if !ptrUse.IsABitCastInst().IsNil() {
for _, bitcastUse := range getUses(ptrUse) {
if bitcastUse.IsACallInst().IsNil() || bitcastUse.CalledValue().Name() != "runtime.isnil" {
panic("expected a call to runtime.isnil")
}
bitcastUse.ReplaceAllUsesWith(llvm.ConstInt(c.ctx.Int1Type(), 0, false))
bitcastUse.EraseFromParentAsInstruction()
}
ptrUse.EraseFromParentAsInstruction()
} else if !ptrUse.IsACallInst().IsNil() && ptrUse.CalledValue() == inttoptr {
if !funcCall.IsNil() {
panic("multiple calls on a single runtime.getFuncPtr")
}
funcCall = ptrUse
} else {
panic("unexpected getFuncPtrCall")
}
}
}
if funcCall.IsNil() {
panic("expected exactly one call use of a runtime.getFuncPtr")
}
// The block that cannot be reached with correct funcValues (to
// help the optimizer).
c.builder.SetInsertPointBefore(funcCall)
defaultBlock := llvm.AddBasicBlock(funcCall.InstructionParent().Parent(), "func.default")
c.builder.SetInsertPointAtEnd(defaultBlock)
c.builder.CreateUnreachable()
// Create the switch.
c.builder.SetInsertPointBefore(funcCall)
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 := llvm.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, funcCall.OperandsCount()-1)
for i := range callParams {
callParams[i] = funcCall.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 := llvm.InsertBasicBlock(nextBlock, "func.call"+strconv.Itoa(fn.id))
c.builder.SetInsertPointAtEnd(bb)
result := c.builder.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 funcCall.Type().TypeKind() != llvm.VoidTypeKind {
c.builder.SetInsertPointBefore(nextBlock.FirstInstruction())
phi := c.builder.CreatePHI(funcCall.Type(), "")
phi.AddIncoming(phiValues, phiBlocks)
funcCall.ReplaceAllUsesWith(phi)
}
// Finally, remove the old instructions.
funcCall.EraseFromParentAsInstruction()
for _, inttoptr := range getUses(getFuncPtrCall) {
inttoptr.EraseFromParentAsInstruction()
}
getFuncPtrCall.EraseFromParentAsInstruction()
}
}
}
}
+78 -54
View File
@@ -5,33 +5,54 @@ package compiler
import (
"go/types"
"strings"
"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 {
if c.GOARCH == "wasm" {
return funcValueSwitch
} else {
return funcValueDoubleword
}
}
// 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:
sigGlobal := c.getTypeCode(sig)
case funcValueSwitch:
sigGlobal := c.getFuncSignature(sig)
funcValueWithSignatureGlobalName := funcPtr.Name() + "$withSignature"
funcValueWithSignatureGlobal := c.mod.NamedGlobal(funcValueWithSignatureGlobalName)
if funcValueWithSignatureGlobal.IsNil() {
funcValueWithSignatureType := c.getLLVMRuntimeType("funcValueWithSignature")
funcValueWithSignatureType := c.mod.GetTypeByName("runtime.funcValueWithSignature")
funcValueWithSignature := llvm.ConstNamedStruct(funcValueWithSignatureType, []llvm.Value{
llvm.ConstPtrToInt(funcPtr, c.uintptrType),
sigGlobal,
@@ -47,35 +68,51 @@ 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
}
// getFuncSignature returns a global for identification of a particular function
// signature. It is used in runtime.funcValueWithSignature and in calls to
// getFuncPtr.
func (c *Compiler) getFuncSignature(sig *types.Signature) llvm.Value {
typeCodeName := getTypeCodeName(sig)
sigGlobalName := "reflect/types.type:" + typeCodeName
sigGlobal := c.mod.NamedGlobal(sigGlobalName)
if sigGlobal.IsNil() {
sigGlobal = llvm.AddGlobal(c.mod, c.ctx.Int8Type(), sigGlobalName)
sigGlobal.SetInitializer(llvm.Undef(c.ctx.Int8Type()))
sigGlobal.SetGlobalConstant(true)
sigGlobal.SetLinkage(llvm.InternalLinkage)
}
return sigGlobal
}
// 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, err error) {
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.getFuncSignature(sig)
funcPtr = c.createRuntimeCall("getFuncPtr", []llvm.Value{funcValue, sigGlobal}, "")
funcPtr = c.builder.CreateIntToPtr(funcPtr, llvmSig, "")
default:
panic("unimplemented func value variant")
}
@@ -83,20 +120,20 @@ 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:
return c.getLLVMRuntimeType("funcValue")
case funcValueSwitch:
return c.mod.GetTypeByName("runtime.funcValue")
default:
panic("unimplemented func value variant")
}
}
// 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() {
@@ -126,15 +163,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
@@ -146,33 +179,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 := expr.Fn.(*ssa.Function)
llvmFn := b.getFunction(f)
if strings.HasSuffix(f.Name(), "$bound") && llvmFn.IsDeclaration() {
// Hack: the ssa package does not expose bound methods so make sure
// they're built here when necessary.
irbuilder := b.ctx.NewBuilder()
defer irbuilder.Dispose()
b.createFunction(irbuilder, f, llvmFn)
}
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(llvmFn, context, f.Signature), nil
return c.createFuncValue(f.LLVMFn, context, f.Signature), nil
}
+104
View File
@@ -0,0 +1,104 @@
package compiler
import (
"math/big"
"tinygo.org/x/go-llvm"
)
func (c *Compiler) addGlobalsBitmap() {
if c.mod.NamedGlobal("runtime.trackedGlobalsStart").IsNil() {
return // 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(llvm.ArrayType(c.ctx.Int8Type(), len(bitmapBytes)), bitmapValues)
bitmapNew := llvm.AddGlobal(c.mod, bitmapArray.Type(), "runtime.trackedGlobalsBitmap.tmp")
bitmapOld := c.mod.NamedGlobal("runtime.trackedGlobalsBitmap")
bitmapOld.ReplaceAllUsesWith(bitmapNew)
bitmapNew.SetInitializer(bitmapArray)
bitmapNew.SetName("runtime.trackedGlobalsBitmap")
}
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)
}
}
-112
View File
@@ -1,112 +0,0 @@
package compiler
// This file provides IR transformations necessary for precise and portable
// garbage collectors.
import (
"go/token"
"golang.org/x/tools/go/ssa"
"tinygo.org/x/go-llvm"
)
// 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) {
// 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)
case *ssa.Call, *ssa.Convert, *ssa.MakeClosure, *ssa.MakeInterface, *ssa.MakeSlice, *ssa.Next:
if !value.IsNil() {
b.trackValue(value)
}
case *ssa.Select:
if alloca, ok := b.selectRecvBuf[expr]; ok {
if alloca.IsAUndefValue().IsNil() {
b.trackPointer(alloca)
}
}
case *ssa.UnOp:
switch expr.Op {
case token.MUL:
// Pointer dereference.
b.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)
}
}
}
// trackValue locates pointers in a value (possibly an aggregate) and tracks the
// individual pointers
func (b *builder) trackValue(value llvm.Value) {
typ := value.Type()
switch typ.TypeKind() {
case llvm.PointerTypeKind:
b.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)
}
case llvm.ArrayTypeKind:
if !typeHasPointers(typ) {
return
}
numElements := typ.ArrayLength()
for i := 0; i < numElements; i++ {
subValue := b.CreateExtractValue(value, i, "")
b.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, "")
}
b.createRuntimeCall("trackPointer", []llvm.Value{value}, "")
}
// typeHasPointers returns whether this type is a pointer or contains pointers.
// If the type is an aggregate type, it will check whether there is a pointer
// inside.
func typeHasPointers(t llvm.Type) bool {
switch t.TypeKind() {
case llvm.PointerTypeKind:
return true
case llvm.StructTypeKind:
for _, subType := range t.StructElementTypes() {
if typeHasPointers(subType) {
return true
}
}
return false
case llvm.ArrayTypeKind:
if typeHasPointers(t.ElementType()) {
return true
}
return false
default:
return false
}
}
+585
View File
@@ -0,0 +1,585 @@
package compiler
// 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.setTaskPromisePtr(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 is a pass called during optimization that transforms the IR
// into one where all blocking functions are turned into goroutines and blocking
// calls into await calls.
func (c *Compiler) LowerGoroutines() 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)
c.mod.NamedFunction("runtime.alloc").SetLinkage(llvm.InternalLinkage)
c.mod.NamedFunction("runtime.free").SetLinkage(llvm.InternalLinkage)
c.mod.NamedFunction("runtime.sleepTask").SetLinkage(llvm.InternalLinkage)
c.mod.NamedFunction("runtime.setTaskPromisePtr").SetLinkage(llvm.InternalLinkage)
c.mod.NamedFunction("runtime.getTaskPromisePtr").SetLinkage(llvm.InternalLinkage)
c.mod.NamedFunction("runtime.scheduler").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)
}
deadlockStub := c.mod.NamedFunction("runtime.deadlockStub")
if !deadlockStub.IsNil() {
worklist = append(worklist, deadlockStub)
}
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.BitCast {
bitcastUses := getUses(use)
for _, call := range bitcastUses {
if call.IsACallInst().IsNil() || call.CalledValue().Name() != "runtime.makeGoroutine" {
return false, errors.New("async function " + f.Name() + " incorrectly used in bitcast, 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.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 bitcast of function.
bitcast := use.Operand(0)
if !bitcast.IsConstant() || bitcast.Opcode() != llvm.BitCast {
panic("expected const bitcast operand of runtime.makeGoroutine")
}
goroutine := bitcast.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 == deadlockStub || 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 == deadlockStub || 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.mod.GetTypeByName("runtime.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")
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("setTaskPromisePtr", []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)
parentHandle := f.LastParam()
// 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("getTaskPromisePtr", []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(), 1, 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.deadlockStub into coroutine suspends (without
// resume).
for _, deadlockCall := range getUses(deadlockStub) {
// 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(), 1, false), // final suspend
}, "")
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) {
bitcastIn := goroutine.Operand(0)
origFunc := bitcastIn.Operand(0)
uses := getUses(goroutine)
if len(uses) != 1 || uses[0].IsABitCastInst().IsNil() {
return errors.New("expected exactly 1 bitcast use of runtime.makeGoroutine")
}
bitcastOut := uses[0]
uses = getUses(bitcastOut)
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()
bitcastOut.EraseFromParentAsInstruction()
goroutine.EraseFromParentAsInstruction()
}
return nil
}
-177
View File
@@ -1,177 +0,0 @@
package compiler
// This file implements the 'go' keyword to start a new goroutine. See
// goroutine-lowering.go for more details.
import (
"go/token"
"github.com/tinygo-org/tinygo/compiler/llvmutil"
"golang.org/x/tools/go/ssa"
"tinygo.org/x/go-llvm"
)
// createGoInstruction 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)
case "coroutines":
callee = b.CreatePtrToInt(funcPtr, b.uintptrType, "")
default:
panic("unreachable")
}
start := b.getFunction(b.program.ImportedPackage("internal/task").Members["start"].(*ssa.Function))
b.createCall(start, []llvm.Value{callee, paramBundle, llvm.Undef(b.i8ptrType), llvm.ConstPointerNull(b.i8ptrType)}, "")
return llvm.Undef(funcPtr.Type().ElementType().ReturnType())
}
// createGoroutineStartWrapper creates a wrapper for the task-based
// implementation of goroutines. For example, to call a function like this:
//
// func add(x, y int) int { ... }
//
// It creates a wrapper like this:
//
// func add$gowrapper(ptr *unsafe.Pointer) {
// args := (*struct{
// x, y int
// })(ptr)
// add(args.x, args.y)
// }
//
// This is useful because the task-based goroutine start implementation only
// 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 {
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)
}
// 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.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{})
}
// 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))
// Create the call.
builder.CreateCall(fn, params, "")
} else {
// For a function pointer like this:
//
// var funcPtr func(x, y int) int
//
// A wrapper like the following is created:
//
// func .gowrapper(ptr *unsafe.Pointer) {
// args := (*struct{
// x, y int
// fn func(x, y int) int
// })(ptr)
// args.fn(x, y)
// }
//
// With a bit of luck, identical wrapper functions like these can be
// merged into one.
// Create the wrapper.
wrapperType := llvm.FunctionType(c.ctx.VoidType(), []llvm.Type{c.i8ptrType}, false)
wrapper = llvm.AddFunction(c.mod, prefix+".gowrapper", wrapperType)
wrapper.SetLinkage(llvm.InternalLinkage)
wrapper.SetUnnamedAddr(true)
entry := c.ctx.AddBasicBlock(wrapper, "entry")
builder.SetInsertPointAtEnd(entry)
if c.Debug() {
pos := c.program.Fset.Position(pos)
diFuncType := c.dibuilder.CreateSubroutineType(llvm.DISubroutineType{
File: c.getDIFile(pos.Filename),
Parameters: nil, // do not show parameters in debugger
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{})
}
// 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)
// Get the function pointer.
fnPtr := params[len(params)-1]
// Ignore the last param, which isn't used anymore.
// TODO: avoid this extra "parent handle" parameter in most functions.
params[len(params)-1] = llvm.Undef(c.i8ptrType)
// Create the call.
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()
// Return a ptrtoint of the wrapper, not the function itself.
return builder.CreatePtrToInt(wrapper, c.uintptrType, "")
}
+42 -96
View File
@@ -13,6 +13,18 @@ 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(args []ssa.Value) (llvm.Value, error) {
fnType := llvm.FunctionType(c.uintptrType, []llvm.Type{}, false)
regname := constant.StringVal(args[0].(*ssa.Const).Value)
target := llvm.InlineAsm(fnType, "mov $0, "+regname, "=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 +32,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 +54,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 +83,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 +103,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 +112,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 +128,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 +141,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 +149,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,34 +140,28 @@ 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)
@@ -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
@@ -573,9 +603,9 @@ func (p *lowerInterfacesPass) createInterfaceImplementsFunc(itf *interfaceInfo)
// TODO: debug info
// Create all used basic blocks.
entry := p.ctx.AddBasicBlock(fn, "entry")
thenBlock := p.ctx.AddBasicBlock(fn, "then")
elseBlock := p.ctx.AddBasicBlock(fn, "else")
entry := llvm.AddBasicBlock(fn, "entry")
thenBlock := llvm.AddBasicBlock(fn, "then")
elseBlock := llvm.AddBasicBlock(fn, "else")
// Add all possible types as cases.
p.builder.SetInsertPointAtEnd(entry)
@@ -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
}
@@ -632,30 +661,30 @@ func (p *lowerInterfacesPass) createInterfaceMethodFunc(itf *interfaceInfo, sign
// TODO: debug info
// Create entry block.
entry := p.ctx.AddBasicBlock(fn, "entry")
entry := llvm.AddBasicBlock(fn, "entry")
// Create default block and make it unreachable (which it is, because all
// possible types are checked).
defaultBlock := p.ctx.AddBasicBlock(fn, "default")
defaultBlock := llvm.AddBasicBlock(fn, "default")
p.builder.SetInsertPointAtEnd(defaultBlock)
p.builder.CreateUnreachable()
// 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)
}
// Define all possible functions that can be called.
for _, typ := range itf.types {
bb := p.ctx.AddBasicBlock(fn, typ.name)
bb := llvm.AddBasicBlock(fn, typ.name)
sw.AddCase(llvm.ConstInt(p.uintptrType, typ.num, false), bb)
// The function we will redirect to when the interface has this type.
+145 -290
View File
@@ -11,133 +11,61 @@ import (
"strconv"
"strings"
"github.com/tinygo-org/tinygo/ir"
"golang.org/x/tools/go/ssa"
"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.mod.GetTypeByName("runtime.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.mod.GetTypeByName("runtime._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 {
globalName := "reflect/types.type:" + getTypeCodeName(typ)
func (c *Compiler) getTypeCode(typ types.Type) llvm.Value {
globalName := "type:" + getTypeCodeName(typ)
global := c.mod.NamedGlobal(globalName)
if global.IsNil() {
// Create a new typecode global.
global = llvm.AddGlobal(c.mod, c.getLLVMRuntimeType("typecodeID"), globalName)
// Some type classes contain more information for underlying types or
// element types. Store it directly in the typecode global to make
// reflect lowering simpler.
var references llvm.Value
var length int64
switch typ := typ.(type) {
case *types.Named:
references = c.getTypeCode(typ.Underlying())
case *types.Chan:
references = c.getTypeCode(typ.Elem())
case *types.Pointer:
references = c.getTypeCode(typ.Elem())
case *types.Slice:
references = c.getTypeCode(typ.Elem())
case *types.Array:
references = c.getTypeCode(typ.Elem())
length = typ.Len()
case *types.Struct:
// Take a pointer to the typecodeID of the first field (if it exists).
structGlobal := c.makeStructTypeFields(typ)
references = llvm.ConstBitCast(structGlobal, global.Type())
}
if !references.IsNil() {
// Set the 'references' field of the runtime.typecodeID struct.
globalValue := llvm.ConstNull(global.Type().ElementType())
globalValue = llvm.ConstInsertValue(globalValue, references, []uint32{0})
if length != 0 {
lengthValue := llvm.ConstInt(c.uintptrType, uint64(length), false)
globalValue = llvm.ConstInsertValue(globalValue, lengthValue, []uint32{1})
}
global.SetInitializer(globalValue)
global.SetLinkage(llvm.PrivateLinkage)
}
global = llvm.AddGlobal(c.mod, c.mod.GetTypeByName("runtime.typecodeID"), globalName)
global.SetGlobalConstant(true)
}
return global
}
// 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 {
// 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)
for i := 0; i < typ.NumFields(); i++ {
fieldGlobalValue := llvm.ConstNull(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)
fieldName.SetUnnamedAddr(true)
fieldName = llvm.ConstGEP(fieldName, []llvm.Value{
llvm.ConstInt(llvm.Int32Type(), 0, false),
llvm.ConstInt(llvm.Int32Type(), 0, false),
})
fieldGlobalValue = llvm.ConstInsertValue(fieldGlobalValue, fieldName, []uint32{1})
if typ.Tag(i) != "" {
fieldTag := c.makeGlobalArray([]byte(typ.Tag(i)), "reflect/types.structFieldTag", c.ctx.Int8Type())
fieldTag.SetLinkage(llvm.PrivateLinkage)
fieldTag.SetUnnamedAddr(true)
fieldTag = llvm.ConstGEP(fieldTag, []llvm.Value{
llvm.ConstInt(llvm.Int32Type(), 0, false),
llvm.ConstInt(llvm.Int32Type(), 0, false),
})
fieldGlobalValue = llvm.ConstInsertValue(fieldGlobalValue, fieldTag, []uint32{2})
}
if typ.Field(i).Embedded() {
fieldEmbedded := llvm.ConstInt(c.ctx.Int1Type(), 1, false)
fieldGlobalValue = llvm.ConstInsertValue(fieldGlobalValue, fieldEmbedded, []uint32{3})
}
structGlobalValue = llvm.ConstInsertValue(structGlobalValue, fieldGlobalValue, []uint32{uint32(i)})
}
structGlobal.SetInitializer(structGlobalValue)
structGlobal.SetUnnamedAddr(true)
structGlobal.SetLinkage(llvm.PrivateLinkage)
return structGlobal
}
// getTypeCodeName returns a name for this type that can be used in the
// interface lowering pass to assign type codes as expected by the reflect
// package. See getTypeCodeNum.
func getTypeCodeName(t types.Type) string {
name := ""
if named, ok := t.(*types.Named); ok {
name = "~" + named.String() + ":"
t = t.Underlying()
}
switch t := t.(type) {
case *types.Named:
return "named:" + t.String()
case *types.Array:
return "array:" + strconv.FormatInt(t.Len(), 10) + ":" + getTypeCodeName(t.Elem())
return "array:" + name + strconv.FormatInt(t.Len(), 10) + ":" + getTypeCodeName(t.Elem())
case *types.Basic:
var kind string
switch t.Kind() {
@@ -180,21 +108,21 @@ func getTypeCodeName(t types.Type) string {
default:
panic("unknown basic type: " + t.Name())
}
return "basic:" + kind
return "basic:" + name + kind
case *types.Chan:
return "chan:" + getTypeCodeName(t.Elem())
return "chan:" + name + getTypeCodeName(t.Elem())
case *types.Interface:
methods := make([]string, t.NumMethods())
for i := 0; i < t.NumMethods(); i++ {
methods[i] = getTypeCodeName(t.Method(i).Type())
}
return "interface:" + "{" + strings.Join(methods, ",") + "}"
return "interface:" + name + "{" + strings.Join(methods, ",") + "}"
case *types.Map:
keyType := getTypeCodeName(t.Key())
elemType := getTypeCodeName(t.Elem())
return "map:" + "{" + keyType + "," + elemType + "}"
return "map:" + name + "{" + keyType + "," + elemType + "}"
case *types.Pointer:
return "pointer:" + getTypeCodeName(t.Elem())
return "pointer:" + name + getTypeCodeName(t.Elem())
case *types.Signature:
params := make([]string, t.Params().Len())
for i := 0; i < t.Params().Len(); i++ {
@@ -204,22 +132,19 @@ func getTypeCodeName(t types.Type) string {
for i := 0; i < t.Results().Len(); i++ {
results[i] = getTypeCodeName(t.Results().At(i).Type())
}
return "func:" + "{" + strings.Join(params, ",") + "}{" + strings.Join(results, ",") + "}"
return "func:" + name + "{" + strings.Join(params, ",") + "}{" + strings.Join(results, ",") + "}"
case *types.Slice:
return "slice:" + getTypeCodeName(t.Elem())
return "slice:" + name + getTypeCodeName(t.Elem())
case *types.Struct:
elems := make([]string, t.NumFields())
for i := 0; i < t.NumFields(); i++ {
embedded := ""
if t.Field(i).Embedded() {
embedded = "#"
}
elems[i] = embedded + t.Field(i).Name() + ":" + getTypeCodeName(t.Field(i).Type())
if t.Tag(i) != "" {
elems[i] += "`" + t.Tag(i) + "`"
}
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")
}
return "struct:" + "{" + strings.Join(elems, ",") + "}"
for i := 0; i < t.NumFields(); i++ {
elems[i] = getTypeCodeName(t.Field(i).Type())
}
return "struct:" + name + "{" + strings.Join(elems, ",") + "}"
default:
panic("unknown type: " + t.String())
}
@@ -227,7 +152,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() {
@@ -235,34 +160,26 @@ func (c *compilerContext) getTypeMethodSet(typ types.Type) llvm.Value {
return llvm.ConstGEP(global, []llvm.Value{zero, zero})
}
ms := c.program.MethodSets.MethodSet(typ)
ms := c.ir.Program.MethodSets.MethodSet(typ)
if ms.Len() == 0 {
// no methods, so can leave that one out
return llvm.ConstPointerNull(llvm.PointerType(c.getLLVMRuntimeType("interfaceMethodInfo"), 0))
return llvm.ConstPointerNull(llvm.PointerType(c.mod.GetTypeByName("runtime.interfaceMethodInfo"), 0))
}
methods := make([]llvm.Value, ms.Len())
interfaceMethodInfoType := c.getLLVMRuntimeType("interfaceMethodInfo")
interfaceMethodInfoType := c.mod.GetTypeByName("runtime.interfaceMethodInfo")
for i := 0; i < ms.Len(); i++ {
method := ms.At(i)
signatureGlobal := c.getMethodSignature(method.Obj().(*types.Func))
fn := c.program.MethodValue(method)
llvmFn := c.getFunction(fn)
if llvmFn.IsNil() {
f := c.ir.GetFunction(c.ir.Program.MethodValue(method))
if f.LLVMFn.IsNil() {
// compiler error, so panic
panic("cannot find function: " + c.getFunctionInfo(fn).linkName)
panic("cannot find function: " + f.LinkName())
}
if isAnonymous(typ) && llvmFn.IsDeclaration() {
// Inline types may also have methods when they embed interface
// types with methods. Example: struct{ error }
irbuilder := c.ctx.NewBuilder()
defer irbuilder.Dispose()
c.createFunction(irbuilder, fn, llvmFn)
}
wrapper := c.getInterfaceInvokeWrapper(fn, llvmFn)
fn := c.getInterfaceInvokeWrapper(f)
methodInfo := llvm.ConstNamedStruct(interfaceMethodInfoType, []llvm.Value{
signatureGlobal,
llvm.ConstPtrToInt(wrapper, c.uintptrType),
llvm.ConstPtrToInt(fn, c.uintptrType),
})
methods[i] = methodInfo
}
@@ -278,28 +195,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)
@@ -307,10 +219,10 @@ 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 {
signature := methodSignature(method)
func (c *Compiler) getMethodSignature(method *types.Func) llvm.Value {
signature := ir.MethodSignature(method)
signatureGlobal := c.mod.NamedGlobal("func " + signature)
if signatureGlobal.IsNil() {
signatureGlobal = llvm.AddGlobal(c.mod, c.ctx.Int8Type(), "func "+signature)
@@ -319,18 +231,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.
@@ -341,15 +253,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
@@ -363,15 +275,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.llvmFn, "typeassert.ok")
nextBlock := b.ctx.AddBasicBlock(b.llvmFn, "typeassert.next")
b.blockExits[b.currentBlock] = nextBlock // adjust outgoing block for phi nodes
b.CreateCondBr(commaOk, okBlock, nextBlock)
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
@@ -380,71 +292,75 @@ 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(fn *ssa.Function, llvmFn llvm.Value) llvm.Value {
wrapperName := llvmFn.Name() + "$invoke"
// 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() {
// Wrapper already created. Return it directly.
@@ -452,11 +368,8 @@ func (c *compilerContext) getInterfaceInvokeWrapper(fn *ssa.Function, llvmFn llv
}
// Get the expanded receiver type.
receiverType := c.getLLVMType(fn.Params[0].Type())
var expandedReceiverType []llvm.Type
for _, info := range expandFormalParamType(receiverType, "", nil) {
expandedReceiverType = append(expandedReceiverType, info.llvmType)
}
receiverType := c.getLLVMType(f.Params[0].Type())
expandedReceiverType := c.expandFormalParamType(receiverType)
// Does this method even need any wrapping?
if len(expandedReceiverType) == 1 && receiverType.TypeKind() == llvm.PointerTypeKind {
@@ -464,107 +377,49 @@ func (c *compilerContext) getInterfaceInvokeWrapper(fn *ssa.Function, llvmFn llv
// Casting a function signature to a different signature and calling it
// with a receiver pointer bitcasted to *i8 (as done in calls on an
// interface) is hopefully a safe (defined) operation.
return llvmFn
return f.LLVMFn
}
// create wrapper function
fnType := llvmFn.Type().ElementType()
fnType := f.LLVMFn.Type().ElementType()
paramTypes := append([]llvm.Type{c.i8ptrType}, fnType.ParamTypes()[len(expandedReceiverType):]...)
wrapFnType := llvm.FunctionType(fnType.ReturnType(), paramTypes, false)
wrapper = llvm.AddFunction(c.mod, wrapperName, wrapFnType)
if llvmFn.LastParam().Name() == "parentHandle" {
wrapper.LastParam().SetName("parentHandle")
}
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.program.Fset.Position(fn.Pos())
difunc := c.attachDebugInfoRaw(fn, wrapper, "$invoke", pos.Filename, pos.Line)
b.SetCurrentDebugLocation(uint(pos.Line), uint(pos.Column), difunc, llvm.Metadata{})
}
// set up IR builder
block := b.ctx.AddBasicBlock(wrapper, "entry")
b.SetInsertPointAtEnd(block)
receiverValue := b.emitPointerUnpack(wrapper.Param(0), []llvm.Type{receiverType})[0]
params := append(b.expandFormalParam(receiverValue), wrapper.Params()[1:]...)
if llvmFn.Type().ElementType().ReturnType().TypeKind() == llvm.VoidTypeKind {
b.CreateCall(llvmFn, params, "")
b.CreateRetVoid()
} else {
ret := b.CreateCall(llvmFn, params, "ret")
b.CreateRet(ret)
}
c.interfaceInvokeWrappers = append(c.interfaceInvokeWrappers, interfaceInvokeWrapper{
fn: f,
wrapper: wrapper,
receiverType: receiverType,
})
return wrapper
}
// isAnonymous returns true if (and only if) this is an anonymous type: one that
// is created inline. It can have methods if it embeds a type with methods.
func isAnonymous(typ types.Type) bool {
if t, ok := typ.(*types.Pointer); ok {
typ = t.Elem()
}
if _, ok := typ.(*types.Named); !ok {
return true
}
return false
}
// 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)
// methodSignature creates a readable version of a method signature (including
// the function name, excluding the receiver name). This string is used
// internally to match interfaces and to call the correct method on an
// interface. Examples:
//
// String() string
// Read([]byte) (int, error)
func methodSignature(method *types.Func) string {
return method.Name() + signature(method.Type().(*types.Signature))
}
// add debug info if needed
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{})
}
// Make a readable version of a function (pointer) signature.
// Examples:
//
// () string
// (string, int) (int, error)
func signature(sig *types.Signature) string {
s := ""
if sig.Params().Len() == 0 {
s += "()"
// set up IR builder
block := c.ctx.AddBasicBlock(wrapper, "entry")
c.builder.SetInsertPointAtEnd(block)
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 {
s += "("
for i := 0; i < sig.Params().Len(); i++ {
if i > 0 {
s += ", "
}
s += sig.Params().At(i).Type().String()
}
s += ")"
ret := c.builder.CreateCall(fn.LLVMFn, params, "ret")
c.builder.CreateRet(ret)
}
if sig.Results().Len() == 0 {
// keep as-is
} else if sig.Results().Len() == 1 {
s += " " + sig.Results().At(0).Type().String()
} else {
s += " ("
for i := 0; i < sig.Results().Len(); i++ {
if i > 0 {
s += ", "
}
s += sig.Results().At(i).Type().String()
}
s += ")"
}
return s
}
-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.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.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{}
}
}
+106 -39
View File
@@ -1,7 +1,6 @@
package compiler
import (
"github.com/tinygo-org/tinygo/compiler/llvmutil"
"tinygo.org/x/go-llvm"
)
@@ -23,47 +22,115 @@ func getUses(value llvm.Value) []llvm.Value {
return uses
}
// 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
// before this point.
// createEntryBlockAlloca creates a new alloca in the entry block, even though
// the IR builder is located elsewhere. It assumes that the insert point is
// after the last instruction in the current block. Also, it adds lifetime
// information to 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)
// end the lifetime after you're done with it.
func (c *Compiler) createEntryBlockAlloca(t llvm.Type, name string) (alloca, bitcast, size llvm.Value) {
currentBlock := c.builder.GetInsertBlock()
c.builder.SetInsertPointBefore(currentBlock.Parent().EntryBasicBlock().FirstInstruction())
alloca = c.builder.CreateAlloca(t, name)
c.builder.SetInsertPointAtEnd(currentBlock)
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)
}
// 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)
}
// 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)
}
// 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))
global := llvm.AddGlobal(c.mod, globalType, name)
value := llvm.Undef(globalType)
for i := 0; i < len(buf); i++ {
ch := uint64(buf[i])
value = llvm.ConstInsertValue(value, llvm.ConstInt(elementType, ch, false), []uint32{uint32(i)})
// 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)
}
global.SetInitializer(value)
return global
return fn
}
// 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
}
-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 -110
View File
@@ -6,175 +6,121 @@ 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.createEntryBlockAlloca(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.createEntryBlockAlloca(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.builder.CreateCall(c.getLifetimeEndFunc(), []llvm.Value{mapKeySize, mapKeyPtr}, "")
} 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 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.builder.CreateCall(c.getLifetimeEndFunc(), []llvm.Value{mapValueSize, mapValuePtr}, "")
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.createEntryBlockAlloca(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.createEntryBlockAlloca(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.builder.CreateCall(c.getLifetimeEndFunc(), []llvm.Value{keySize, keyPtr}, "")
} 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 or structs of bools/ints are supported as map keys, but got: "+keyType.String())
}
b.emitLifetimeEnd(valuePtr, valueSize)
c.builder.CreateCall(c.getLifetimeEndFunc(), []llvm.Value{valueSize, valuePtr}, "")
}
// 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.createEntryBlockAlloca(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.builder.CreateCall(c.getLifetimeEndFunc(), []llvm.Value{keySize, keyPtr}, "")
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 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 {
switch keyType := keyType.(type) {
case *types.Basic:
return keyType.Info()&(types.IsBoolean|types.IsInteger) != 0
case *types.Pointer:
return true
case *types.Struct:
for i := 0; i < keyType.NumFields(); i++ {
fieldType := keyType.Field(i).Type().Underlying()
-344
View File
@@ -1,344 +0,0 @@
// +build none
// This file generates runtimetypes.go from the AST of the TinyGo runtime
// package. This type information is necessary to avoid having to compile the
// runtime to compile any package.
package main
import (
"bytes"
"fmt"
"go/ast"
"go/format"
"go/token"
"io"
"io/ioutil"
"os"
"strings"
"golang.org/x/tools/go/packages"
)
// The list of runtime types known by the compiler.
var runtimeTypes = []string{
// panic, recover
"_defer",
// strings
"_string", "stringIterator",
// map
"hashmap", "hashmapBucket", "hashmapIterator",
// channel
"channel", "channelBlockedList", "chanSelectState",
// interface
"_interface", "interfaceMethodInfo", "typecodeID", "structField", "typeInInterface",
// func
"funcValue", "funcValueWithSignature",
}
// The list of runtime calls known to the compiler.
var runtimeCalls = []string{
// panic, recover
"_panic", "_recover",
"nilPanic", "lookupPanic", "slicePanic", "chanMakePanic", "negativeShiftPanic",
// string
"stringEqual", "stringLess",
"stringConcat",
"stringFromBytes", "stringToBytes",
"stringFromRunes", "stringToRunes",
"stringFromUnicode",
"stringNext",
// complex
"complex64div", "complex128div",
// slice
"sliceAppend", "sliceCopy",
// memory
"alloc", "trackPointer",
// print builtin
"printbool",
"printint8", "printuint8",
"printint16", "printuint16",
"printint32", "printuint32",
"printint64", "printuint64",
"printfloat32", "printfloat64",
"printcomplex64", "printcomplex128",
"printstring", "printspace", "printnl",
"printptr", "printmap", "printitf",
// hashmap
"hashmapMake", "hashmapLen", "hashmapNext",
"hashmapStringGet", "hashmapStringSet", "hashmapStringDelete",
"hashmapInterfaceGet", "hashmapInterfaceSet", "hashmapInterfaceDelete",
"hashmapBinaryGet", "hashmapBinarySet", "hashmapBinaryDelete",
// channel, concurrency
"tryChanSelect", "chanMake", "chanSend", "chanRecv", "chanClose", "chanSelect",
"deadlock",
// interface, reflect
"interfaceEqual", "interfaceImplements", "interfaceMethod",
"typeAssert", "interfaceTypeAssert",
// func
"getFuncPtr",
}
// makeDefs generates runtimetypes.go and writes it out after formatting it.
func makeDefs() error {
// Load the runtime package.
pkgs, err := packages.Load(&packages.Config{
Mode: packages.NeedSyntax,
BuildFlags: []string{"-tags=gc.extalloc"},
}, "../src/runtime")
if err != nil {
return err
}
if len(pkgs) != 1 {
return fmt.Errorf("expected 1 package, got %d", len(pkgs))
}
runtimePkg := pkgs[0]
if len(runtimePkg.Errors) != 0 {
return runtimePkg.Errors[0]
}
// Start creating the new Go file.
buf := &bytes.Buffer{}
buf.WriteString(`// Autogenerated by mkruntimetypes.go, DO NOT EDIT.
package compiler
// This file contains definitions for runtime types and functions, so that the
// runtime package can be compiled independently of other packages.
import (
"go/token"
"go/types"
"strconv"
)
// getRuntimeType constructs a new runtime type with the given name. The types
// constructed here must match the types in the runtime package.
func (c *compilerContext) getRuntimeType(name string) types.Type {
if c.program != nil {
return c.program.ImportedPackage("runtime").Type(name).Type()
}
if typ, ok := c.runtimeTypes[name]; ok {
// This type was already created.
return typ
}
typeName := types.NewTypeName(token.NoPos, c.runtimePkg, name, nil)
named := types.NewNamed(typeName, nil, nil)
// Make sure recursive types are only defined once.
c.runtimeTypes[name] = named
var fieldTypes []types.Type
switch name {
`)
err = makeTypeDefs(buf, runtimePkg)
if err != nil {
return err
}
buf.WriteString(` default:
panic("could not find runtime type: runtime." + name)
}
// Create the named struct type.
var fields []*types.Var
for i, t := range fieldTypes {
// Field name doesn't matter: this type is only used to create a LLVM
// struct type which don't have field names.
fields = append(fields, types.NewField(token.NoPos, nil, "field"+strconv.Itoa(i), t, false))
}
named.SetUnderlying(types.NewStruct(fields, nil))
return named
}
// getRuntimeFuncType constructs a new runtime function signature with the given
// name. The function signatures constructed here must match the functions in
// the runtime package.
func (c *compilerContext) getRuntimeFuncType(name string) *types.Signature {
var params []*types.Var
addParam := func(name string, typ types.Type) {
params = append(params, types.NewParam(token.NoPos, c.runtimePkg, name, typ))
}
var results []*types.Var
addResult := func(typ types.Type) {
results = append(results, types.NewParam(token.NoPos, c.runtimePkg, "", typ))
}
switch name {
`)
err = makeFuncDefs(buf, runtimePkg)
if err != nil {
return err
}
buf.WriteString(` default:
panic("unknown runtime call: runtime." + name)
}
return types.NewSignature(nil, types.NewTuple(params...), types.NewTuple(results...), false)
}
`)
source, err := format.Source(buf.Bytes())
if err != nil {
// Fallback (useful for investigating errors).
source = buf.Bytes()
}
err2 := ioutil.WriteFile("runtimetypes.go", source, 0666)
if err2 != nil {
return err2 // error from ioutil.WriteFile
}
return err // error from format.Source (if any)
}
// makeTypeDefs generates the switch body of the getRuntimeType function.
func makeTypeDefs(w io.Writer, runtimePkg *packages.Package) error {
typeSpecs := map[string]*ast.TypeSpec{}
for _, file := range runtimePkg.Syntax {
for _, decl := range file.Decls {
switch decl := decl.(type) {
case *ast.GenDecl:
if decl.Tok != token.TYPE {
continue
}
for _, spec := range decl.Specs {
typeSpec := spec.(*ast.TypeSpec)
typeSpecs[typeSpec.Name.Name] = typeSpec
}
}
}
}
for _, name := range runtimeTypes {
typeSpec := typeSpecs[name]
if typeSpec == nil {
return fmt.Errorf("could not find type: %s", name)
}
fmt.Fprintf(w, "\tcase %#v:\n", typeSpec.Name.Name)
if name == "channelBlockedList" {
fmt.Fprintf(w, "\t\ttaskType := types.NewNamed(types.NewTypeName(token.NoPos, c.taskPkg, \"Task\", nil), nil, nil)\n")
}
fmt.Fprintf(w, "\t\tfieldTypes = []types.Type{\n")
for _, field := range typeSpec.Type.(*ast.StructType).Fields.List {
fieldType := getTypeFromExpr(field.Type, typeSpec.Name.Name)
for _, ident := range field.Names {
fmt.Fprintf(w, "\t\t\t%s, // %s\n", fieldType, ident.Name)
}
}
fmt.Fprintf(w, "\t\t}\n")
}
return nil
}
// makeFuncDefs generates the switch body of the getRuntimeFuncType function.
func makeFuncDefs(w io.Writer, runtimePkg *packages.Package) error {
functions := map[string]*ast.FuncDecl{}
for _, file := range runtimePkg.Syntax {
for _, decl := range file.Decls {
switch decl := decl.(type) {
case *ast.FuncDecl:
functions[decl.Name.Name] = decl
default:
}
}
}
for _, name := range runtimeCalls {
decl := functions[name]
if decl == nil {
return fmt.Errorf("could not find function: %s", name)
}
fmt.Fprintf(w, "\tcase %#v:\n", decl.Name.Name)
for _, field := range decl.Type.Params.List {
typeString := getTypeFromExpr(field.Type, "")
for _, name := range field.Names {
fmt.Fprintf(w, "\t\taddParam(%#v, %s)\n", name.Name, typeString)
}
}
if decl.Type.Results != nil {
for _, field := range decl.Type.Results.List {
typeString := getTypeFromExpr(field.Type, "")
for range field.Names {
fmt.Fprintf(w, "\t\taddResult(%s)\n", typeString)
}
if len(field.Names) == 0 {
fmt.Fprintf(w, "\t\taddResult(%s)\n", typeString)
}
}
}
}
return nil
}
// getTypeFromExpr returns a string which is a piece of Go code that constructs
// the type (as given in ast.Expr) using the go/types package.
func getTypeFromExpr(typ ast.Expr, currentTypeName string) string {
switch typ := typ.(type) {
case *ast.Ident:
if typ.Name == currentTypeName {
// Assume a global named "named" which refers to the currently
// created named type.
return "named"
}
switch typ.Name {
case "bool", "int", "int8", "int16", "int32", "int64", "uint", "uint8", "uint16", "uint32", "uint64", "uintptr", "float32", "float64", "complex64", "complex128", "string", "byte", "rune":
// Built-in types.
return fmt.Sprintf("types.Typ[types.%s]", strings.Title(typ.Name))
case "chanState":
// runtime.chanState is a named type, but that doesn't matter when
// generating LLVM IR.
return fmt.Sprintf("types.Typ[types.Uint8]")
default:
// Assume that we can simply get the type recursively.
return fmt.Sprintf("c.getRuntimeType(%#v)", typ.Name)
}
case *ast.StarExpr:
return fmt.Sprintf("types.NewPointer(%s)", getTypeFromExpr(typ.X, currentTypeName))
case *ast.InterfaceType:
if len(typ.Methods.List) != 0 {
// Unimplemented: interfaces with methods.
return "interface{?}"
}
return "types.NewInterfaceType(nil, nil)"
case *ast.ArrayType:
// Slices and arrays. Assume the array length is a numeric constant and
// not a Go named constant for example.
elementType := getTypeFromExpr(typ.Elt, currentTypeName)
if typ.Len == nil {
return fmt.Sprintf("types.NewSlice(%s)", elementType)
}
length := typ.Len.(*ast.BasicLit).Value
return fmt.Sprintf("types.NewArray(%s, %s)", elementType, length)
case *ast.SelectorExpr:
s := typ.X.(*ast.Ident).Name + "." + typ.Sel.Name
switch s {
case "unsafe.Pointer":
return "types.Typ[types.UnsafePointer]"
case "task.Task":
// Assume there is a variable taskType which refers to the task.Task
// structure.
return "taskType"
default:
return fmt.Sprintf("<unknown %s>", s)
}
case *ast.StructType:
// Inline struct type.
var fields string
for _, field := range typ.Fields.List {
fieldType := getTypeFromExpr(field.Type, currentTypeName)
for _, ident := range field.Names {
fields += fmt.Sprintf("\t\t\ttypes.NewField(token.NoPos, nil, %#v, %s, false),\n", ident.Name, fieldType)
}
}
return fmt.Sprintf("types.NewStruct([]*types.Var{\n%s\t\t}, nil)", fields)
default:
// Dump the raw typ value, for debugging.
return fmt.Sprintf("%#v", typ)
}
}
func main() {
err := makeDefs()
if err != nil {
fmt.Fprintln(os.Stderr, "could not create defs:", err)
os.Exit(1)
}
}
+382
View File
@@ -0,0 +1,382 @@
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 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.AddGlobalDCEPass()
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)
}
}
// 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)
if c.gcIsPrecise() {
c.addGlobalsBitmap()
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
}
+156
View File
@@ -0,0 +1,156 @@
package compiler
import (
"math/big"
"strings"
)
var basicTypes = map[string]int64{
"bool": 1,
"int": 2,
"int8": 3,
"int16": 4,
"int32": 5,
"int64": 6,
"uint": 7,
"uint8": 8,
"uint16": 9,
"uint32": 10,
"uint64": 11,
"uintptr": 12,
"float32": 13,
"float64": 14,
"complex64": 15,
"complex128": 16,
"string": 17,
"unsafeptr": 18,
}
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.
fallbackIndex := 1
namedTypes := make(map[string]int)
for _, t := range typeSlice {
if t.name[:5] != "type:" {
panic("expected type name to start with 'type:'")
}
num := c.getTypeCodeNum(t.name[5:], &fallbackIndex, namedTypes)
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.
// AVR).
panic("compiler: could not store type code number inside interface type code")
}
t.num = num.Uint64()
}
}
// getTypeCodeNum returns the typecode for a given type as expected by the
// reflect package. Also see getTypeCodeName, which serializes types to a string
// based on a types.Type value for this function.
func (c *Compiler) getTypeCodeNum(id string, fallbackIndex *int, namedTypes map[string]int) *big.Int {
// Note: see src/reflect/type.go for bit allocations.
// A type can be named or unnamed. Example of both:
// basic:~foo:uint64
// basic:uint64
// Extract the class (basic, slice, pointer, etc.), the name, and the
// contents of this type ID string. Allocate bits based on that, as
// src/runtime/types.go expects.
class := id[:strings.IndexByte(id, ':')]
value := id[len(class)+1:]
name := ""
if value[0] == '~' {
name = value[1:strings.IndexByte(value, ':')]
value = value[len(name)+2:]
}
if class == "basic" {
// Basic types follow the following bit pattern:
// ...xxxxx0
// where xxxxx is allocated for the 18 possible basic types and all the
// upper bits are used to indicate the named type.
num, ok := basicTypes[value]
if !ok {
panic("invalid basic type: " + id)
}
if name != "" {
// This type is named, set the upper bits to the name ID.
num |= int64(getNamedTypeNum(namedTypes, name)) << 5
}
return big.NewInt(num << 1)
} else {
// Complex types use the following bit pattern:
// ...nxxx1
// where xxx indicates the complex type (any non-basic type). The upper
// bits contain whatever the type contains. Types that wrap a single
// other type (channel, interface, pointer, slice) just contain the bits
// of the wrapped type. Other types (like struct) have a different
// method of encoding the contents of the type.
var num *big.Int
var classNumber int64
switch class {
case "chan":
num = c.getTypeCodeNum(value, fallbackIndex, namedTypes)
classNumber = 0
case "interface":
num = big.NewInt(int64(*fallbackIndex))
*fallbackIndex++
classNumber = 1
case "pointer":
num = c.getTypeCodeNum(value, fallbackIndex, namedTypes)
classNumber = 2
case "slice":
num = c.getTypeCodeNum(value, fallbackIndex, namedTypes)
classNumber = 3
case "array":
num = big.NewInt(int64(*fallbackIndex))
*fallbackIndex++
classNumber = 4
case "func":
num = big.NewInt(int64(*fallbackIndex))
*fallbackIndex++
classNumber = 5
case "map":
num = big.NewInt(int64(*fallbackIndex))
*fallbackIndex++
classNumber = 6
case "struct":
num = big.NewInt(int64(*fallbackIndex))
*fallbackIndex++
classNumber = 7
default:
panic("unknown type kind: " + id)
}
if name == "" {
num.Lsh(num, 5).Or(num, big.NewInt((classNumber<<1)+1))
} else {
// TODO: store num in a sidetable
num = big.NewInt(int64(getNamedTypeNum(namedTypes, name))<<1 | 1)
num.Lsh(num, 4).Or(num, big.NewInt((classNumber<<1)+1))
}
return num
}
}
// getNamedTypeNum returns an appropriate (unique) number for the given named
// type. If the name already has a number that number is returned, else a new
// number is returned. The number is always non-zero.
func getNamedTypeNum(namedTypes map[string]int, name string) int {
if num, ok := namedTypes[name]; ok {
return num
} else {
num = len(namedTypes) + 1
namedTypes[name] = num
return num
}
}
-377
View File
@@ -1,377 +0,0 @@
// Autogenerated by mkruntimetypes.go, DO NOT EDIT.
package compiler
// This file contains definitions for runtime types and functions, so that the
// runtime package can be compiled independently of other packages.
import (
"go/token"
"go/types"
"strconv"
)
// getRuntimeType constructs a new runtime type with the given name. The types
// constructed here must match the types in the runtime package.
func (c *compilerContext) getRuntimeType(name string) types.Type {
if c.program != nil {
return c.program.ImportedPackage("runtime").Type(name).Type()
}
if typ, ok := c.runtimeTypes[name]; ok {
// This type was already created.
return typ
}
typeName := types.NewTypeName(token.NoPos, c.runtimePkg, name, nil)
named := types.NewNamed(typeName, nil, nil)
// Make sure recursive types are only defined once.
c.runtimeTypes[name] = named
var fieldTypes []types.Type
switch name {
case "_defer":
fieldTypes = []types.Type{
types.Typ[types.Uintptr], // callback
types.NewPointer(named), // next
}
case "_string":
fieldTypes = []types.Type{
types.NewPointer(types.Typ[types.Byte]), // ptr
types.Typ[types.Uintptr], // length
}
case "stringIterator":
fieldTypes = []types.Type{
types.Typ[types.Uintptr], // byteindex
}
case "hashmap":
fieldTypes = []types.Type{
types.NewPointer(named), // next
types.Typ[types.UnsafePointer], // buckets
types.Typ[types.Uintptr], // count
types.Typ[types.Uint8], // keySize
types.Typ[types.Uint8], // valueSize
types.Typ[types.Uint8], // bucketBits
}
case "hashmapBucket":
fieldTypes = []types.Type{
types.NewArray(types.Typ[types.Uint8], 8), // tophash
types.NewPointer(named), // next
}
case "hashmapIterator":
fieldTypes = []types.Type{
types.Typ[types.Uintptr], // bucketNumber
types.NewPointer(c.getRuntimeType("hashmapBucket")), // bucket
types.Typ[types.Uint8], // bucketIndex
}
case "channel":
fieldTypes = []types.Type{
types.Typ[types.Uintptr], // elementSize
types.Typ[types.Uintptr], // bufSize
types.Typ[types.Uint8], // state
types.NewPointer(c.getRuntimeType("channelBlockedList")), // blocked
types.Typ[types.Uintptr], // bufHead
types.Typ[types.Uintptr], // bufTail
types.Typ[types.Uintptr], // bufUsed
types.Typ[types.UnsafePointer], // buf
}
case "channelBlockedList":
taskType := types.NewNamed(types.NewTypeName(token.NoPos, c.taskPkg, "Task", nil), nil, nil)
fieldTypes = []types.Type{
types.NewPointer(named), // next
types.NewPointer(taskType), // t
types.NewPointer(c.getRuntimeType("chanSelectState")), // s
types.NewSlice(named), // allSelectOps
}
case "chanSelectState":
fieldTypes = []types.Type{
types.NewPointer(c.getRuntimeType("channel")), // ch
types.Typ[types.UnsafePointer], // value
}
case "_interface":
fieldTypes = []types.Type{
types.Typ[types.Uintptr], // typecode
types.Typ[types.UnsafePointer], // value
}
case "interfaceMethodInfo":
fieldTypes = []types.Type{
types.NewPointer(types.Typ[types.Uint8]), // signature
types.Typ[types.Uintptr], // funcptr
}
case "typecodeID":
fieldTypes = []types.Type{
types.NewPointer(named), // references
types.Typ[types.Uintptr], // length
}
case "structField":
fieldTypes = []types.Type{
types.NewPointer(c.getRuntimeType("typecodeID")), // typecode
types.NewPointer(types.Typ[types.Uint8]), // name
types.NewPointer(types.Typ[types.Uint8]), // tag
types.Typ[types.Bool], // embedded
}
case "typeInInterface":
fieldTypes = []types.Type{
types.NewPointer(c.getRuntimeType("typecodeID")), // typecode
types.NewPointer(c.getRuntimeType("interfaceMethodInfo")), // methodSet
}
case "funcValue":
fieldTypes = []types.Type{
types.Typ[types.UnsafePointer], // context
types.Typ[types.Uintptr], // id
}
case "funcValueWithSignature":
fieldTypes = []types.Type{
types.Typ[types.Uintptr], // funcPtr
types.NewPointer(c.getRuntimeType("typecodeID")), // signature
}
default:
panic("could not find runtime type: runtime." + name)
}
// Create the named struct type.
var fields []*types.Var
for i, t := range fieldTypes {
// Field name doesn't matter: this type is only used to create a LLVM
// struct type which don't have field names.
fields = append(fields, types.NewField(token.NoPos, nil, "field"+strconv.Itoa(i), t, false))
}
named.SetUnderlying(types.NewStruct(fields, nil))
return named
}
// getRuntimeFuncType constructs a new runtime function signature with the given
// name. The function signatures constructed here must match the functions in
// the runtime package.
func (c *compilerContext) getRuntimeFuncType(name string) *types.Signature {
var params []*types.Var
addParam := func(name string, typ types.Type) {
params = append(params, types.NewParam(token.NoPos, c.runtimePkg, name, typ))
}
var results []*types.Var
addResult := func(typ types.Type) {
results = append(results, types.NewParam(token.NoPos, c.runtimePkg, "", typ))
}
switch name {
case "_panic":
addParam("message", types.NewInterfaceType(nil, nil))
case "_recover":
addResult(types.NewInterfaceType(nil, nil))
case "nilPanic":
case "lookupPanic":
case "slicePanic":
case "chanMakePanic":
case "negativeShiftPanic":
case "stringEqual":
addParam("x", types.Typ[types.String])
addParam("y", types.Typ[types.String])
addResult(types.Typ[types.Bool])
case "stringLess":
addParam("x", types.Typ[types.String])
addParam("y", types.Typ[types.String])
addResult(types.Typ[types.Bool])
case "stringConcat":
addParam("x", c.getRuntimeType("_string"))
addParam("y", c.getRuntimeType("_string"))
addResult(c.getRuntimeType("_string"))
case "stringFromBytes":
addParam("x", types.NewStruct([]*types.Var{
types.NewField(token.NoPos, nil, "ptr", types.NewPointer(types.Typ[types.Byte]), false),
types.NewField(token.NoPos, nil, "len", types.Typ[types.Uintptr], false),
types.NewField(token.NoPos, nil, "cap", types.Typ[types.Uintptr], false),
}, nil))
addResult(c.getRuntimeType("_string"))
case "stringToBytes":
addParam("x", c.getRuntimeType("_string"))
addResult(types.NewStruct([]*types.Var{
types.NewField(token.NoPos, nil, "ptr", types.NewPointer(types.Typ[types.Byte]), false),
types.NewField(token.NoPos, nil, "len", types.Typ[types.Uintptr], false),
types.NewField(token.NoPos, nil, "cap", types.Typ[types.Uintptr], false),
}, nil))
case "stringFromRunes":
addParam("runeSlice", types.NewSlice(types.Typ[types.Rune]))
addResult(c.getRuntimeType("_string"))
case "stringToRunes":
addParam("s", types.Typ[types.String])
addResult(types.NewSlice(types.Typ[types.Rune]))
case "stringFromUnicode":
addParam("x", types.Typ[types.Rune])
addResult(c.getRuntimeType("_string"))
case "stringNext":
addParam("s", types.Typ[types.String])
addParam("it", types.NewPointer(c.getRuntimeType("stringIterator")))
addResult(types.Typ[types.Bool])
addResult(types.Typ[types.Int])
addResult(types.Typ[types.Rune])
case "complex64div":
addParam("n", types.Typ[types.Complex64])
addParam("m", types.Typ[types.Complex64])
addResult(types.Typ[types.Complex64])
case "complex128div":
addParam("n", types.Typ[types.Complex128])
addParam("m", types.Typ[types.Complex128])
addResult(types.Typ[types.Complex128])
case "sliceAppend":
addParam("srcBuf", types.Typ[types.UnsafePointer])
addParam("elemsBuf", types.Typ[types.UnsafePointer])
addParam("srcLen", types.Typ[types.Uintptr])
addParam("srcCap", types.Typ[types.Uintptr])
addParam("elemsLen", types.Typ[types.Uintptr])
addParam("elemSize", types.Typ[types.Uintptr])
addResult(types.Typ[types.UnsafePointer])
addResult(types.Typ[types.Uintptr])
addResult(types.Typ[types.Uintptr])
case "sliceCopy":
addParam("dst", types.Typ[types.UnsafePointer])
addParam("src", types.Typ[types.UnsafePointer])
addParam("dstLen", types.Typ[types.Uintptr])
addParam("srcLen", types.Typ[types.Uintptr])
addParam("elemSize", types.Typ[types.Uintptr])
addResult(types.Typ[types.Int])
case "alloc":
addParam("size", types.Typ[types.Uintptr])
addResult(types.Typ[types.UnsafePointer])
case "trackPointer":
addParam("ptr", types.Typ[types.UnsafePointer])
case "printbool":
addParam("b", types.Typ[types.Bool])
case "printint8":
addParam("n", types.Typ[types.Int8])
case "printuint8":
addParam("n", types.Typ[types.Uint8])
case "printint16":
addParam("n", types.Typ[types.Int16])
case "printuint16":
addParam("n", types.Typ[types.Uint16])
case "printint32":
addParam("n", types.Typ[types.Int32])
case "printuint32":
addParam("n", types.Typ[types.Uint32])
case "printint64":
addParam("n", types.Typ[types.Int64])
case "printuint64":
addParam("n", types.Typ[types.Uint64])
case "printfloat32":
addParam("v", types.Typ[types.Float32])
case "printfloat64":
addParam("v", types.Typ[types.Float64])
case "printcomplex64":
addParam("c", types.Typ[types.Complex64])
case "printcomplex128":
addParam("c", types.Typ[types.Complex128])
case "printstring":
addParam("s", types.Typ[types.String])
case "printspace":
case "printnl":
case "printptr":
addParam("ptr", types.Typ[types.Uintptr])
case "printmap":
addParam("m", types.NewPointer(c.getRuntimeType("hashmap")))
case "printitf":
addParam("msg", types.NewInterfaceType(nil, nil))
case "hashmapMake":
addParam("keySize", types.Typ[types.Uint8])
addParam("valueSize", types.Typ[types.Uint8])
addParam("sizeHint", types.Typ[types.Uintptr])
addResult(types.NewPointer(c.getRuntimeType("hashmap")))
case "hashmapLen":
addParam("m", types.NewPointer(c.getRuntimeType("hashmap")))
addResult(types.Typ[types.Int])
case "hashmapNext":
addParam("m", types.NewPointer(c.getRuntimeType("hashmap")))
addParam("it", types.NewPointer(c.getRuntimeType("hashmapIterator")))
addParam("key", types.Typ[types.UnsafePointer])
addParam("value", types.Typ[types.UnsafePointer])
addResult(types.Typ[types.Bool])
case "hashmapStringGet":
addParam("m", types.NewPointer(c.getRuntimeType("hashmap")))
addParam("key", types.Typ[types.String])
addParam("value", types.Typ[types.UnsafePointer])
addParam("valueSize", types.Typ[types.Uintptr])
addResult(types.Typ[types.Bool])
case "hashmapStringSet":
addParam("m", types.NewPointer(c.getRuntimeType("hashmap")))
addParam("key", types.Typ[types.String])
addParam("value", types.Typ[types.UnsafePointer])
case "hashmapStringDelete":
addParam("m", types.NewPointer(c.getRuntimeType("hashmap")))
addParam("key", types.Typ[types.String])
case "hashmapInterfaceGet":
addParam("m", types.NewPointer(c.getRuntimeType("hashmap")))
addParam("key", types.NewInterfaceType(nil, nil))
addParam("value", types.Typ[types.UnsafePointer])
addParam("valueSize", types.Typ[types.Uintptr])
addResult(types.Typ[types.Bool])
case "hashmapInterfaceSet":
addParam("m", types.NewPointer(c.getRuntimeType("hashmap")))
addParam("key", types.NewInterfaceType(nil, nil))
addParam("value", types.Typ[types.UnsafePointer])
case "hashmapInterfaceDelete":
addParam("m", types.NewPointer(c.getRuntimeType("hashmap")))
addParam("key", types.NewInterfaceType(nil, nil))
case "hashmapBinaryGet":
addParam("m", types.NewPointer(c.getRuntimeType("hashmap")))
addParam("key", types.Typ[types.UnsafePointer])
addParam("value", types.Typ[types.UnsafePointer])
addParam("valueSize", types.Typ[types.Uintptr])
addResult(types.Typ[types.Bool])
case "hashmapBinarySet":
addParam("m", types.NewPointer(c.getRuntimeType("hashmap")))
addParam("key", types.Typ[types.UnsafePointer])
addParam("value", types.Typ[types.UnsafePointer])
case "hashmapBinaryDelete":
addParam("m", types.NewPointer(c.getRuntimeType("hashmap")))
addParam("key", types.Typ[types.UnsafePointer])
case "tryChanSelect":
addParam("recvbuf", types.Typ[types.UnsafePointer])
addParam("states", types.NewSlice(c.getRuntimeType("chanSelectState")))
addResult(types.Typ[types.Uintptr])
addResult(types.Typ[types.Bool])
case "chanMake":
addParam("elementSize", types.Typ[types.Uintptr])
addParam("bufSize", types.Typ[types.Uintptr])
addResult(types.NewPointer(c.getRuntimeType("channel")))
case "chanSend":
addParam("ch", types.NewPointer(c.getRuntimeType("channel")))
addParam("value", types.Typ[types.UnsafePointer])
case "chanRecv":
addParam("ch", types.NewPointer(c.getRuntimeType("channel")))
addParam("value", types.Typ[types.UnsafePointer])
addResult(types.Typ[types.Bool])
case "chanClose":
addParam("ch", types.NewPointer(c.getRuntimeType("channel")))
case "chanSelect":
addParam("recvbuf", types.Typ[types.UnsafePointer])
addParam("states", types.NewSlice(c.getRuntimeType("chanSelectState")))
addParam("ops", types.NewSlice(c.getRuntimeType("channelBlockedList")))
addResult(types.Typ[types.Uintptr])
addResult(types.Typ[types.Bool])
case "deadlock":
case "interfaceEqual":
addParam("x", types.NewInterfaceType(nil, nil))
addParam("y", types.NewInterfaceType(nil, nil))
addResult(types.Typ[types.Bool])
case "interfaceImplements":
addParam("typecode", types.Typ[types.Uintptr])
addParam("interfaceMethodSet", types.NewPointer(types.NewPointer(types.Typ[types.Uint8])))
addResult(types.Typ[types.Bool])
case "interfaceMethod":
addParam("typecode", types.Typ[types.Uintptr])
addParam("interfaceMethodSet", types.NewPointer(types.NewPointer(types.Typ[types.Uint8])))
addParam("signature", types.NewPointer(types.Typ[types.Uint8]))
addResult(types.Typ[types.Uintptr])
case "typeAssert":
addParam("actualType", types.Typ[types.Uintptr])
addParam("assertedType", types.NewPointer(c.getRuntimeType("typecodeID")))
addResult(types.Typ[types.Bool])
case "interfaceTypeAssert":
addParam("ok", types.Typ[types.Bool])
case "getFuncPtr":
addParam("val", c.getRuntimeType("funcValue"))
addParam("signature", types.NewPointer(c.getRuntimeType("typecodeID")))
addResult(types.Typ[types.Uintptr])
default:
panic("unknown runtime call: runtime." + name)
}
return types.NewSignature(nil, types.NewTuple(params...), types.NewTuple(results...), false)
}
+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:
-369
View File
@@ -1,369 +0,0 @@
package compiler
// This file manages symbols, that is, functions and globals. It reads their
// pragmas, determines the link name, etc.
import (
"go/ast"
"go/token"
"go/types"
"strconv"
"strings"
"github.com/tinygo-org/tinygo/loader"
"golang.org/x/tools/go/ssa"
"tinygo.org/x/go-llvm"
)
type inlineType int
// How much to inline.
const (
// Default behavior. The compiler decides for itself whether any given
// function will be inlined. Whether any function is inlined depends on the
// optimization level.
inlineDefault inlineType = iota
// Inline hint, just like the C inline keyword (signalled using
// //go:inline). The compiler will be more likely to inline this function,
// but it is not a guarantee.
inlineHint
// Don't inline, just like the GCC noinline attribute. Signalled using
// //go:noinline.
inlineNone
)
// functionInfo contains some information about a function or method. In
// particular, it contains information obtained from pragmas.
//
// The linkName value contains a valid link name, even though //go:linkname is
// not present.
type functionInfo struct {
linkName string // go:linkname, go:export
module string // go:wasm-module
exported bool // go:export
nobounds bool // go:nobounds
inline inlineType // go:inline
}
// getFunction returns the LLVM function for the given *ssa.Function, creating
// it if needed. It can later be filled with compilerContext.createFunction().
func (c *compilerContext) getFunction(fn *ssa.Function) llvm.Value {
info := c.getFunctionInfo(fn)
return c.getFunctionRaw(fn.Signature, info)
}
func (c *compilerContext) getFunctionRaw(sig *types.Signature, info functionInfo) llvm.Value {
llvmFn := c.mod.NamedFunction(info.linkName)
if !llvmFn.IsNil() {
return llvmFn
}
var retType llvm.Type
if sig.Results() == nil {
retType = c.ctx.VoidType()
} else if sig.Results().Len() == 1 {
retType = c.getLLVMType(sig.Results().At(0).Type())
} else {
results := make([]llvm.Type, 0, sig.Results().Len())
for i := 0; i < sig.Results().Len(); i++ {
results = append(results, c.getLLVMType(sig.Results().At(i).Type()))
}
retType = c.ctx.StructType(results, false)
}
var paramInfos []paramInfo
params := []*types.Var{}
if sig.Recv() != nil {
params = append(params, sig.Recv())
}
for i := 0; i < sig.Params().Len(); i++ {
params = append(params, sig.Params().At(i))
}
for _, param := range params {
paramType := c.getLLVMType(param.Type())
paramFragmentInfos := expandFormalParamType(paramType, param.Name(), param.Type())
paramInfos = append(paramInfos, paramFragmentInfos...)
}
// Add an extra parameter as the function context. This context is used in
// closures and bound methods, but should be optimized away when not used.
if !info.exported {
paramInfos = append(paramInfos, paramInfo{llvmType: c.i8ptrType, name: "context", flags: 0})
paramInfos = append(paramInfos, paramInfo{llvmType: c.i8ptrType, name: "parentHandle", flags: 0})
}
var paramTypes []llvm.Type
for _, info := range paramInfos {
paramTypes = append(paramTypes, info.llvmType)
}
fnType := llvm.FunctionType(retType, paramTypes, false)
llvmFn = llvm.AddFunction(c.mod, info.linkName, fnType)
dereferenceableOrNullKind := llvm.AttributeKindID("dereferenceable_or_null")
for i, info := range paramInfos {
if info.flags&paramIsDeferenceableOrNull == 0 {
continue
}
if info.llvmType.TypeKind() == llvm.PointerTypeKind {
el := info.llvmType.ElementType()
size := c.targetData.TypeAllocSize(el)
if size == 0 {
// dereferenceable_or_null(0) appears to be illegal in LLVM.
continue
}
dereferenceableOrNull := c.ctx.CreateEnumAttribute(dereferenceableOrNullKind, size)
llvmFn.AddAttributeAtIndex(i+1, dereferenceableOrNull)
}
}
// External/exported functions may not retain pointer values.
// https://golang.org/cmd/cgo/#hdr-Passing_pointers
if info.exported {
// Set the wasm-import-module attribute if the function's module is set.
if info.module != "" {
wasmImportModuleAttr := c.ctx.CreateStringAttribute("wasm-import-module", info.module)
llvmFn.AddFunctionAttr(wasmImportModuleAttr)
}
nocaptureKind := llvm.AttributeKindID("nocapture")
nocapture := c.ctx.CreateEnumAttribute(nocaptureKind, 0)
for i, typ := range paramTypes {
if typ.TypeKind() == llvm.PointerTypeKind {
llvmFn.AddAttributeAtIndex(i+1, nocapture)
}
}
}
return llvmFn
}
// getFunctionInfo returns information about a function that is not directly
// present in *ssa.Function, such as the link name and whether it should be
// exported.
func (c *compilerContext) getFunctionInfo(f *ssa.Function) functionInfo {
info := functionInfo{}
if strings.HasPrefix(f.Name(), "C.") {
// Created by CGo: such a name cannot be created by regular C code.
info.linkName = f.Name()[2:]
info.exported = true
} else {
// Pick the default linkName.
info.linkName = f.RelString(nil)
// Check for //go: pragmas, which may change the link name (among
// others).
info.parsePragmas(f)
}
return info
}
// parsePragmas is used by getFunctionInfo to parse function pragmas such as
// //export or //go:noinline.
func (info *functionInfo) parsePragmas(f *ssa.Function) {
// Parse compiler directives in the preceding comments.
if f.Syntax() == nil {
return
}
if decl, ok := f.Syntax().(*ast.FuncDecl); ok && decl.Doc != nil {
for _, comment := range decl.Doc.List {
text := comment.Text
if strings.HasPrefix(text, "//export ") {
// Rewrite '//export' to '//go:export' for compatibility with
// gc.
text = "//go:" + text[2:]
}
if !strings.HasPrefix(text, "//go:") {
continue
}
parts := strings.Fields(text)
switch parts[0] {
case "//go:export":
if len(parts) != 2 {
continue
}
info.linkName = parts[1]
info.exported = true
case "//go:wasm-module":
// Alternative comment for setting the import module.
if len(parts) != 2 {
continue
}
info.module = parts[1]
case "//go:inline":
info.inline = inlineHint
case "//go:noinline":
info.inline = inlineNone
case "//go:linkname":
if len(parts) != 3 || parts[1] != f.Name() {
continue
}
// Only enable go:linkname when the package imports "unsafe".
// This is a slightly looser requirement than what gc uses: gc
// requires the file to import "unsafe", not the package as a
// whole.
if hasUnsafeImport(f.Pkg.Pkg) {
info.linkName = parts[2]
}
case "//go:nobounds":
// Skip bounds checking in this function. Useful for some
// runtime functions.
// This is somewhat dangerous and thus only imported in packages
// that import unsafe.
if hasUnsafeImport(f.Pkg.Pkg) {
info.nobounds = true
}
}
}
}
}
// globalInfo contains some information about a specific global. By default,
// linkName is equal to .RelString(nil) on a global and extern is false, but for
// some symbols this is different (due to //go:extern for example).
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) {
c.astComments = map[string]*ast.CommentGroup{}
for _, pkgInfo := range lprogram.Sorted() {
for _, file := range pkgInfo.Files {
for _, decl := range file.Decls {
switch decl := decl.(type) {
case *ast.GenDecl:
switch decl.Tok {
case token.VAR:
if len(decl.Specs) != 1 {
continue
}
for _, spec := range decl.Specs {
switch spec := spec.(type) {
case *ast.ValueSpec: // decl.Tok == token.VAR
for _, name := range spec.Names {
id := pkgInfo.Pkg.Path() + "." + name.Name
c.astComments[id] = decl.Doc
}
}
}
}
}
}
}
}
}
// 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 {
info := c.getGlobalInfo(g)
llvmGlobal := c.mod.NamedGlobal(info.linkName)
if llvmGlobal.IsNil() {
typ := g.Type().(*types.Pointer).Elem()
llvmType := c.getLLVMType(typ)
llvmGlobal = llvm.AddGlobal(c.mod, llvmType, info.linkName)
if !info.extern {
llvmGlobal.SetInitializer(llvm.ConstNull(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.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 {
info := globalInfo{}
if strings.HasPrefix(g.Name(), "C.") {
// Created by CGo: such a name cannot be created by regular C code.
info.linkName = g.Name()[2:]
info.extern = true
} else {
// Pick the default linkName.
info.linkName = g.RelString(nil)
// Check for //go: pragmas, which may change the link name (among
// others).
doc := c.astComments[info.linkName]
if doc != nil {
info.parsePragmas(doc)
}
}
return info
}
// Parse //go: pragma comments from the source. In particular, it parses the
// //go:extern pragma on globals.
func (info *globalInfo) parsePragmas(doc *ast.CommentGroup) {
for _, comment := range doc.List {
if !strings.HasPrefix(comment.Text, "//go:") {
continue
}
parts := strings.Fields(comment.Text)
switch parts[0] {
case "//go:extern":
info.extern = true
if len(parts) == 2 {
info.linkName = parts[1]
}
case "//go:align":
align, err := strconv.Atoi(parts[1])
if err == nil {
info.align = align
}
}
}
}
// Get all methods of a type.
func getAllMethods(prog *ssa.Program, typ types.Type) []*types.Selection {
ms := prog.MethodSets.MethodSet(typ)
methods := make([]*types.Selection, ms.Len())
for i := 0; i < ms.Len(); i++ {
methods[i] = ms.At(i)
}
return methods
}
// Return true if this package imports "unsafe", false otherwise.
func hasUnsafeImport(pkg *types.Package) bool {
for _, imp := range pkg.Imports() {
if imp == types.Unsafe {
return true
}
}
return false
}
+45 -70
View File
@@ -4,20 +4,21 @@ package compiler
// compiler builtins.
import (
"go/constant"
"strconv"
"golang.org/x/tools/go/ssa"
"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, _ := constant.Uint64Val(call.Args[0].(*ssa.Const).Value)
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 +29,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 += 0x2000000
}
// 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}
args := []llvm.Value{llvm.ConstInt(c.uintptrType, num, false)}
argTypes := []llvm.Type{c.uintptrType}
// Constraints will look something like:
// "={rax},0,{rdi},{rsi},{rdx},{r10},{r8},{r9},~{rcx},~{r11}"
constraints := "={rax},0"
@@ -50,41 +51,15 @@ 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":
// 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}
// Constraints will look something like:
// "={eax},0,{ebx},{ecx},{edx},{esi},{edi},{ebp}"
constraints := "={eax},0"
for i, arg := range call.Args[1:] {
constraints += "," + [...]string{
"{ebx}",
"{ecx}",
"{edx}",
"{esi}",
"{edi}",
"{ebp}",
}[i]
llvmValue := b.getValue(arg)
args = append(args, llvmValue)
argTypes = append(argTypes, llvmValue.Type())
}
fnType := llvm.FunctionType(b.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 +77,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)
args = append(args, llvm.ConstInt(c.uintptrType, num, false))
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 +107,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)
args = append(args, llvm.ConstInt(c.uintptrType, num, false))
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 +120,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 +135,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(llvm.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 +153,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(llvm.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)
}
}
-13
View File
@@ -1,13 +0,0 @@
package main
func add(x, y int) int {
return x + y
}
func stringEqual(s string) bool {
return s == "s"
}
func closeChan(ch chan int) {
close(ch)
}
-41
View File
@@ -1,41 +0,0 @@
target datalayout = "e-m:e-p:32:32-Fi8-i64:64-v128:64:128-a:0:32-n32-S64"
target triple = "armv7m-none-eabi"
%runtime.channel = type { i32, i32, i8, %runtime.channelBlockedList*, i32, i32, i32, i8* }
%runtime.channelBlockedList = type { %runtime.channelBlockedList*, %"internal/task.Task"*, %runtime.chanSelectState*, { %runtime.channelBlockedList*, i32, i32 } }
%"internal/task.Task" = type opaque
%runtime.chanSelectState = type { %runtime.channel*, i8* }
%runtime._string = type { i8*, i32 }
@"main.stringEqual$string" = internal unnamed_addr constant [1 x i8] c"s"
define internal i32 @main.add(i32 %x, i32 %y, i8* %context, i8* %parentHandle) unnamed_addr {
entry:
%0 = add i32 %x, %y
ret i32 %0
}
define internal void @main.closeChan(%runtime.channel* dereferenceable_or_null(32) %ch, i8* %context, i8* %parentHandle) unnamed_addr {
entry:
call void @runtime.chanClose(%runtime.channel* %ch, i8* undef, i8* null)
ret void
}
declare void @runtime.chanClose(%runtime.channel* dereferenceable_or_null(32), i8*, i8*)
define internal void @main.init(i8* %context, i8* %parentHandle) unnamed_addr {
entry:
ret void
}
define internal i1 @main.stringEqual(i8* %s.data, i32 %s.len, i8* %context, i8* %parentHandle) unnamed_addr {
entry:
%0 = insertvalue %runtime._string zeroinitializer, i8* %s.data, 0
%1 = insertvalue %runtime._string %0, i32 %s.len, 1
%2 = extractvalue %runtime._string %1, 0
%3 = extractvalue %runtime._string %1, 1
%4 = call i1 @runtime.stringEqual(i8* %2, i32 %3, i8* getelementptr inbounds ([1 x i8], [1 x i8]* @"main.stringEqual$string", i32 0, i32 0), i32 1, i8* undef, i8* null)
ret i1 %4
}
declare i1 @runtime.stringEqual(i8*, i32, i8*, i32, i8*, i8*)
+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
}
+108
View File
@@ -0,0 +1,108 @@
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) && !c.gcIsPrecise() {
// 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.builder.CreateAlloca(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}, "")
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), "")
return c.builder.CreateLoad(packedAlloc, "")
} else {
// Get the original heap allocation pointer, which already is an *i8.
return c.builder.CreateBitCast(packedAlloc, c.i8ptrType, "")
}
}
// 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 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) && !c.gcIsPrecise() {
// 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.builder.CreateAlloca(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, "")
}
return values
}
-13
View File
@@ -1,13 +0,0 @@
module github.com/tinygo-org/tinygo
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
)
-59
View File
@@ -1,59 +0,0 @@
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
}
+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"
}
+52 -232
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
@@ -174,20 +176,6 @@ func (fr *frame) evalBasicBlock(bb, incoming llvm.BasicBlock, indent string) (re
continue // special case: bitcast of alloc
}
}
if _, ok := fr.getLocal(operand).(*MapValue); ok {
// Special case for runtime.trackPointer calls.
// Note: this might not be entirely sound in some rare cases
// where the map is stored in a dirty global.
uses := getUses(inst)
if len(uses) == 1 {
use := uses[0]
if !use.IsACallInst().IsNil() && !use.CalledValue().IsAFunction().IsNil() && use.CalledValue().Name() == "runtime.trackPointer" {
continue
}
}
// 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"))
}
value := fr.getLocal(operand).(*LocalValue)
fr.locals[inst] = &LocalValue{fr.Eval, fr.builder.CreateBitCast(value.Value(), inst.Type(), "")}
@@ -196,33 +184,6 @@ 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 ok1 && ok2 {
if lhsZero && rhsZero {
// Both are zero, so this icmp is always evaluated to true.
fr.locals[inst] = &LocalValue{fr.Eval, llvm.ConstInt(fr.Mod.Context().Int1Type(), 1, false)}
continue
}
if lhsZero != rhsZero {
// Only one of them is zero, so this comparison must return false.
fr.locals[inst] = &LocalValue{fr.Eval, llvm.ConstInt(fr.Mod.Context().Int1Type(), 0, false)}
continue
}
}
}
fr.locals[inst] = &LocalValue{fr.Eval, fr.builder.CreateICmp(predicate, lhs, rhs, "")}
case !inst.IsAFCmpInst().IsNil():
lhs := fr.getLocal(inst.Operand(0)).(*LocalValue).Underlying
@@ -253,13 +214,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 +229,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,56 +237,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),
MapType: inst.Type().ElementType(),
}
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
@@ -346,78 +271,18 @@ 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)
global.SetUnnamedAddr(true)
stringType := inst.Type()
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))
@@ -429,7 +294,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)
@@ -437,40 +302,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()
@@ -499,10 +349,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":
// This are all print instructions, which necessarily have side
// effects but no results.
@@ -542,10 +388,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
@@ -568,13 +411,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
}
}
@@ -583,7 +421,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
@@ -593,7 +431,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())}
}
@@ -606,22 +444,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
@@ -631,25 +457,21 @@ 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"))
}
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"))
}
switch cond {
case llvm.ConstInt(fr.Mod.Context().Int1Type(), 0, false): // false
return nil, []llvm.Value{thenBB}, nil // then
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"))
if !cond.IsConstant() {
return nil, nil, errors.New("interp: branch on a non-constant")
} else {
switch cond.ZExtValue() {
case 0: // false
return nil, []llvm.Value{thenBB}, nil // then
case 1: // true
return nil, []llvm.Value{elseBB}, nil // else
default:
panic("branch was not true or false")
}
}
case !inst.IsABranchInst().IsNil() && inst.OperandsCount() == 1:
// unconditional branch (goto)
@@ -657,11 +479,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}
}
}
@@ -675,7 +496,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
}

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