Compare commits

..

1 Commits

Author SHA1 Message Date
Ayke van Laethem f50758ca5a interp: support GEP on fixed (MMIO) addresses
GetElementPtr would not work on values that weren't pointers. Because
fixed addresses (often used in memory-mapped I/O) are integers rather
than pointers in interp, it would return an error.

This resulted in the teensy40 target not compiling correctly since the
interp package rewrite. This commit should fix that.
2021-03-05 21:24:28 +01:00
396 changed files with 5999 additions and 19750 deletions
+35 -53
View File
@@ -68,27 +68,24 @@ commands:
steps: steps:
- restore_cache: - restore_cache:
keys: keys:
- llvm-source-11-v2 - llvm-source-11-v1
- run: - run:
name: "Fetch LLVM source" name: "Fetch LLVM source"
command: make llvm-source command: make llvm-source
- save_cache: - save_cache:
key: llvm-source-11-v2 key: llvm-source-11-v1
paths: paths:
- llvm-project/clang/lib/Headers - llvm-project
- llvm-project/clang/include
- llvm-project/lld/include
- llvm-project/llvm/include
build-wasi-libc: build-wasi-libc:
steps: steps:
- restore_cache: - restore_cache:
keys: keys:
- wasi-libc-sysroot-v4 - wasi-libc-sysroot-v3
- run: - run:
name: "Build wasi-libc" name: "Build wasi-libc"
command: make wasi-libc command: make wasi-libc
- save_cache: - save_cache:
key: wasi-libc-sysroot-v4 key: wasi-libc-sysroot-v3
paths: paths:
- lib/wasi-libc/sysroot - lib/wasi-libc/sysroot
test-linux: test-linux:
@@ -111,13 +108,13 @@ commands:
- run: go install -tags=llvm<<parameters.llvm>> . - run: go install -tags=llvm<<parameters.llvm>> .
- restore_cache: - restore_cache:
keys: keys:
- wasi-libc-sysroot-systemclang-v3 - wasi-libc-sysroot-systemclang-v2
- run: make wasi-libc - run: make wasi-libc
- save_cache: - save_cache:
key: wasi-libc-sysroot-systemclang-v3 key: wasi-libc-sysroot-systemclang-v2
paths: paths:
- lib/wasi-libc/sysroot - lib/wasi-libc/sysroot
- run: go test -v -tags=llvm<<parameters.llvm>> ./cgo ./compileopts ./compiler ./interp ./transform . - run: go test -v -tags=llvm<<parameters.llvm>> ./cgo ./compileopts ./interp ./transform .
- run: make gen-device -j4 - run: make gen-device -j4
- run: make smoketest XTENSA=0 - run: make smoketest XTENSA=0
- run: make tinygo-test - run: make tinygo-test
@@ -135,7 +132,6 @@ commands:
- run: - run:
name: "Install apt dependencies" name: "Install apt dependencies"
command: | command: |
sudo apt-get update
sudo apt-get install \ sudo apt-get install \
gcc-arm-linux-gnueabihf \ gcc-arm-linux-gnueabihf \
libc6-dev-armel-cross \ libc6-dev-armel-cross \
@@ -157,15 +153,12 @@ commands:
- llvm-source-linux - llvm-source-linux
- restore_cache: - restore_cache:
keys: keys:
- llvm-build-11-linux-v3-assert - llvm-build-11-linux-v1-assert
- run: - run:
name: "Build LLVM" name: "Build LLVM"
command: | command: |
if [ ! -f llvm-build/lib/liblldELF.a ] if [ ! -f llvm-build/lib/liblldELF.a ]
then then
# fetch LLVM source
rm -rf llvm-project
make llvm-source
# install dependencies # install dependencies
sudo apt-get install cmake ninja-build sudo apt-get install cmake ninja-build
# hack ninja to use less jobs # hack ninja to use less jobs
@@ -173,10 +166,9 @@ commands:
chmod +x /go/bin/ninja chmod +x /go/bin/ninja
# build! # build!
make ASSERT=1 llvm-build make ASSERT=1 llvm-build
find llvm-build -name CMakeFiles -prune -exec rm -r '{}' \;
fi fi
- save_cache: - save_cache:
key: llvm-build-11-linux-v3-assert key: llvm-build-11-linux-v1-assert
paths: paths:
llvm-build llvm-build
- run: make ASSERT=1 - run: make ASSERT=1
@@ -184,11 +176,6 @@ commands:
- run: - run:
name: "Test TinyGo" name: "Test TinyGo"
command: make ASSERT=1 test command: make ASSERT=1 test
environment:
# Note: -p=2 limits parallelism to two jobs at a time, which is
# necessary to keep memory consumption down and avoid OOM (for a
# 2CPU/4GB executor).
GOFLAGS: -p=2
- save_cache: - save_cache:
key: go-cache-v2-{{ checksum "go.mod" }}-{{ .Environment.CIRCLE_BUILD_NUM }} key: go-cache-v2-{{ checksum "go.mod" }}-{{ .Environment.CIRCLE_BUILD_NUM }}
paths: paths:
@@ -203,7 +190,6 @@ commands:
- run: - run:
name: "Install apt dependencies" name: "Install apt dependencies"
command: | command: |
sudo apt-get update
sudo apt-get install \ sudo apt-get install \
gcc-arm-linux-gnueabihf \ gcc-arm-linux-gnueabihf \
libc6-dev-armel-cross \ libc6-dev-armel-cross \
@@ -225,15 +211,12 @@ commands:
- llvm-source-linux - llvm-source-linux
- restore_cache: - restore_cache:
keys: keys:
- llvm-build-11-linux-v3-noassert - llvm-build-11-linux-v1-noassert
- run: - run:
name: "Build LLVM" name: "Build LLVM"
command: | command: |
if [ ! -f llvm-build/lib/liblldELF.a ] if [ ! -f llvm-build/lib/liblldELF.a ]
then then
# fetch LLVM source
rm -rf llvm-project
make llvm-source
# install dependencies # install dependencies
sudo apt-get install cmake ninja-build sudo apt-get install cmake ninja-build
# hack ninja to use less jobs # hack ninja to use less jobs
@@ -241,10 +224,9 @@ commands:
chmod +x /go/bin/ninja chmod +x /go/bin/ninja
# build! # build!
make llvm-build make llvm-build
find llvm-build -name CMakeFiles -prune -exec rm -r '{}' \;
fi fi
- save_cache: - save_cache:
key: llvm-build-11-linux-v3-noassert key: llvm-build-11-linux-v1-noassert
paths: paths:
llvm-build llvm-build
- build-wasi-libc - build-wasi-libc
@@ -286,8 +268,8 @@ commands:
- run: - run:
name: "Install dependencies" name: "Install dependencies"
command: | command: |
curl https://dl.google.com/go/go1.16.darwin-amd64.tar.gz -o go1.16.darwin-amd64.tar.gz curl https://dl.google.com/go/go1.15.5.darwin-amd64.tar.gz -o go1.15.5.darwin-amd64.tar.gz
sudo tar -C /usr/local -xzf go1.16.darwin-amd64.tar.gz sudo tar -C /usr/local -xzf go1.15.5.darwin-amd64.tar.gz
ln -s /usr/local/go/bin/go /usr/local/bin/go ln -s /usr/local/go/bin/go /usr/local/bin/go
HOMEBREW_NO_AUTO_UPDATE=1 brew install qemu HOMEBREW_NO_AUTO_UPDATE=1 brew install qemu
- install-xtensa-toolchain: - install-xtensa-toolchain:
@@ -298,46 +280,39 @@ commands:
- go-cache-macos-v2-{{ checksum "go.mod" }} - go-cache-macos-v2-{{ checksum "go.mod" }}
- restore_cache: - restore_cache:
keys: keys:
- llvm-source-11-macos-v2 - llvm-source-11-macos-v1
- run: - run:
name: "Fetch LLVM source" name: "Fetch LLVM source"
command: make llvm-source command: make llvm-source
- save_cache: - save_cache:
key: llvm-source-11-macos-v2 key: llvm-source-11-macos-v1
paths: paths:
- llvm-project/clang/lib/Headers - llvm-project
- llvm-project/clang/include
- llvm-project/lld/include
- llvm-project/llvm/include
- restore_cache: - restore_cache:
keys: keys:
- llvm-build-11-macos-v3 - llvm-build-11-macos-v1
- run: - run:
name: "Build LLVM" name: "Build LLVM"
command: | command: |
if [ ! -f llvm-build/lib/liblldELF.a ] if [ ! -f llvm-build/lib/liblldELF.a ]
then then
# fetch LLVM source
rm -rf llvm-project
make llvm-source
# install dependencies # install dependencies
HOMEBREW_NO_AUTO_UPDATE=1 brew install cmake ninja HOMEBREW_NO_AUTO_UPDATE=1 brew install cmake ninja
# build! # build!
make llvm-build make llvm-build
find llvm-build -name CMakeFiles -prune -exec rm -r '{}' \;
fi fi
- save_cache: - save_cache:
key: llvm-build-11-macos-v3 key: llvm-build-11-macos-v1
paths: paths:
llvm-build llvm-build
- restore_cache: - restore_cache:
keys: keys:
- wasi-libc-sysroot-macos-v3 - wasi-libc-sysroot-macos-v2
- run: - run:
name: "Build wasi-libc" name: "Build wasi-libc"
command: make wasi-libc command: make wasi-libc
- save_cache: - save_cache:
key: wasi-libc-sysroot-macos-v3 key: wasi-libc-sysroot-macos-v2
paths: paths:
- lib/wasi-libc/sysroot - lib/wasi-libc/sysroot
- run: - run:
@@ -365,6 +340,18 @@ commands:
- /go/pkg/mod - /go/pkg/mod
jobs: jobs:
test-llvm9-go111:
docker:
- image: circleci/golang:1.11-buster
steps:
- test-linux:
llvm: "9"
test-llvm10-go112:
docker:
- image: circleci/golang:1.12-buster
steps:
- test-linux:
llvm: "10"
test-llvm10-go113: test-llvm10-go113:
docker: docker:
- image: circleci/golang:1.13-buster - image: circleci/golang:1.13-buster
@@ -383,12 +370,6 @@ jobs:
steps: steps:
- test-linux: - test-linux:
llvm: "11" llvm: "11"
test-llvm11-go116:
docker:
- image: circleci/golang:1.16-buster
steps:
- test-linux:
llvm: "11"
assert-test-linux: assert-test-linux:
docker: docker:
- image: circleci/golang:1.14-stretch - image: circleci/golang:1.14-stretch
@@ -410,10 +391,11 @@ jobs:
workflows: workflows:
test-all: test-all:
jobs: jobs:
- test-llvm9-go111
- test-llvm10-go112
- test-llvm10-go113 - test-llvm10-go113
- test-llvm10-go114 - test-llvm10-go114
- test-llvm11-go115 - test-llvm11-go115
- test-llvm11-go116
- build-linux - build-linux
- build-macos - build-macos
- assert-test-linux - assert-test-linux
-7
View File
@@ -19,10 +19,3 @@ src/device/kendryte/*.s
vendor vendor
llvm-build llvm-build
llvm-project llvm-project
# Ignore files generated by smoketest
test.gba
test.hex
test.nro
test.wasm
wasm.wasm
-3
View File
@@ -20,6 +20,3 @@
[submodule "lib/picolibc"] [submodule "lib/picolibc"]
path = lib/picolibc path = lib/picolibc
url = https://github.com/keith-packard/picolibc.git url = https://github.com/keith-packard/picolibc.git
[submodule "lib/stm32-svd"]
path = lib/stm32-svd
url = https://github.com/tinygo-org/stm32-svd
+6 -1
View File
@@ -23,7 +23,7 @@ different guide:
LLVM, Clang and LLD are quite light on dependencies, requiring only standard LLVM, Clang and LLD are quite light on dependencies, requiring only standard
build tools to be built. Go is of course necessary to build TinyGo itself. build tools to be built. Go is of course necessary to build TinyGo itself.
* Go (1.13+) * Go (1.11+)
* Standard build tools (gcc/clang) * Standard build tools (gcc/clang)
* git * git
* CMake * CMake
@@ -43,6 +43,11 @@ 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 `LLVM_BUILDDIR`, `CLANG_SRC` and `LLD_SRC` make variables, but that is not
covered by this guide. 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 ## Build LLVM, Clang, LLD
Before starting the build, you may want to set the following environment Before starting the build, you may want to set the following environment
-90
View File
@@ -1,93 +1,3 @@
0.17.0
---
* **command line**
- switch to LLVM 11 for static builds
- support gdb debugging with AVR
- add support for additional openocd commands
- add `-x` flag to print commands
- use LLVM 11 by default when linking LLVM dynamically
- update go-llvm to use LLVM 11 on macOS
- bump go.bug.st/serial to version 1.1.2
- do not build LLVM with libxml to work around a bugo on macOS
- add support for Go 1.16
- support gdb daemonization on Windows
- remove support for LLVM 9, to fix CI
- kill OpenOCD if it does not exit with a regular quit signal
- support `-ocd-output` on Windows
* **compiler**
- `builder`: parallelize most of the build
- `builder`: remove unused cacheKey parameter
- `builder`: add -mcpu flag while building libraries
- `builder`: wait for running jobs to finish
- `cgo`: add support for variadic functions
- `compiler`: fix undefined behavior in wordpack
- `compiler`: fix incorrect "exported function" panic
- `compiler`: fix non-int integer constants (fixing a crash)
- `compiler`: refactor and add tests
- `compiler`: emit a nil check when slicing an array pointer
- `compiler`: saturate float-to-int conversions
- `compiler`: test float to int conversions and fix upper-bound calculation
- `compiler`: support all kinds of deferred builtins
- `compiler`: remove ir package
- `compiler`: remove unnecessary main.main call workaround
- `compiler`: move the setting of attributes to getFunction
- `compiler`: create runtime types lazily when needed
- `compiler`: move settings to a separate Config struct
- `compiler`: work around an ARM backend bug in LLVM
- `interp`: rewrite entire package
- `interp`: fix alignment of untyped globals
- `loader`: use name "main" for the main package
- `loader`: support imports from vendor directories
- `stacksize`: add support for DW_CFA_offset_extended
- `transform`: show better error message in coroutines lowering
* **standard library**
- `machine`: accept configuration struct for ADC parameters
- `machine`: make I2C.Configure signature consistent
- `reflect`: implement PtrTo
- `runtime`: refactor to simplify stack switching
- `runtime`: put metadata at the top end of the heap
* **targets**
- `atsam`: add a length check to findPinPadMapping
- `atsam`: improve USBCDC
- `atsam`: avoid infinite loop when USBCDC is disconnected
- `avr`: add SPI support for Atmega based chips
- `avr`: use Clang for compiling C and assembly files
- `esp32`: implement task based scheduler
- `esp32`: enable the FPU
- `esp8266`: implement task based scheduler
- `esp`: add compiler-rt library
- `esp`: add picolibc
- `nrf`: refactor code a bit to reduce duplication
- `nrf`: use SPIM peripheral instead of the legacy SPI peripheral
- `nrf`: update nrfx submodule to latest commit
- `nrf52840`: ensure that USB CDC interface is only initialized once
- `nrf52840`: improve USBCDC
- `stm32`: use stm32-rs SVDs which are of much higher quality
- `stm32`: harmonization of UART logic
- `stm32`: replace I2C addressable interface with simpler type
- `stm32`: fix i2c and add stm32f407 i2c
- `stm32`: revert change that adds support for channels in interrupts
- `wasm`: implement a growable heap
- `wasm`: fix typo in wasm_exec.js, syscall/js.valueLoadString()
- `wasm`: Namespaced Wasm Imports so they don't conflict across modules, or reserved LLVM IR
- `wasi`: support env variables based on libc
- `wasi`: specify wasi-libc in a different way, to improve error message
* **boards**
- `matrixportal-m4`: add support for board Adafruit Matrix Portal M4
- `mkr1000`: add this board
- `nucleo-f722ze`: add this board
- `clue`: correct volume name and add alias for release version of Adafruit Clue board
- `p1am-100`: add support for the P1AM-100 (similar to Arduino MKR)
- `microbit-v2`: add initial support based on work done by @alankrantas thank you!
- `lgt92`: support for STM32L0 MCUs and Dragino LGT92 device
- `nicenano`: nice!nano board support
- `circuitplay-bluefruit`: correct internal I2C pin mapping
- `clue`: correct for lack of low frequency crystal
- `digispark`: split off attiny85 target
- `nucleo-l552ze`: implementation with CLOCK, LED, and UART
- `nrf52840-mdk-usb-dongle`: add this board
0.16.0 0.16.0
--- ---
+6 -6
View File
@@ -1,10 +1,10 @@
# TinyGo base stage installs the most recent Go 1.15.x, LLVM 11 and the TinyGo compiler itself. # TinyGo base stage installs the most recent Go 1.15.x, LLVM 10 and the TinyGo compiler itself.
FROM golang:1.15 AS tinygo-base FROM golang:1.15 AS tinygo-base
RUN wget -O- https://apt.llvm.org/llvm-snapshot.gpg.key| apt-key add - && \ RUN wget -O- https://apt.llvm.org/llvm-snapshot.gpg.key| apt-key add - && \
echo "deb http://apt.llvm.org/buster/ llvm-toolchain-buster-11 main" >> /etc/apt/sources.list && \ echo "deb http://apt.llvm.org/buster/ llvm-toolchain-buster-10 main" >> /etc/apt/sources.list && \
apt-get update && \ apt-get update && \
apt-get install -y llvm-11-dev libclang-11-dev lld-11 git apt-get install -y llvm-10-dev libclang-10-dev lld-10 git
COPY . /tinygo COPY . /tinygo
@@ -29,7 +29,7 @@ COPY --from=tinygo-base /tinygo/targets /tinygo/targets
RUN cd /tinygo/ && \ RUN cd /tinygo/ && \
apt-get update && \ apt-get update && \
apt-get install -y make clang-11 libllvm11 lld-11 && \ apt-get install -y make clang-10 libllvm10 lld-10 && \
make wasi-libc make wasi-libc
# tinygo-avr stage installs the needed dependencies to compile TinyGo programs for AVR microcontrollers. # tinygo-avr stage installs the needed dependencies to compile TinyGo programs for AVR microcontrollers.
@@ -61,7 +61,7 @@ COPY --from=tinygo-base /tinygo/lib /tinygo/lib
RUN cd /tinygo/ && \ RUN cd /tinygo/ && \
apt-get update && \ apt-get update && \
apt-get install -y apt-utils make clang-11 && \ apt-get install -y apt-utils make clang-10 && \
make gen-device-nrf && make gen-device-stm32 make gen-device-nrf && make gen-device-stm32
# tinygo-all stage installs the needed dependencies to compile TinyGo programs for all platforms. # tinygo-all stage installs the needed dependencies to compile TinyGo programs for all platforms.
@@ -73,7 +73,7 @@ COPY --from=tinygo-base /tinygo/lib /tinygo/lib
RUN cd /tinygo/ && \ RUN cd /tinygo/ && \
apt-get update && \ apt-get update && \
apt-get install -y apt-utils make clang-11 binutils-avr gcc-avr avr-libc && \ apt-get install -y apt-utils make clang-10 binutils-avr gcc-avr avr-libc && \
make gen-device make gen-device
CMD ["tinygo"] CMD ["tinygo"]
+2 -2
View File
@@ -1,7 +1,7 @@
Copyright (c) 2018-2021 TinyGo Authors. All rights reserved. Copyright (c) 2018-2020 TinyGo Authors. All rights reserved.
TinyGo includes portions of the Go standard library. TinyGo includes portions of the Go standard library.
Copyright (c) 2009-2021 The Go Authors. All rights reserved. Copyright (c) 2009-2020 The Go Authors. All rights reserved.
TinyGo includes portions of LLVM, which is under the Apache License v2.0 with 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. LLVM Exceptions. See https://llvm.org/LICENSE.txt for license information.
+29 -77
View File
@@ -54,63 +54,46 @@ ifeq ($(OS),Windows_NT)
CGO_LDFLAGS += -static -static-libgcc -static-libstdc++ CGO_LDFLAGS += -static -static-libgcc -static-libstdc++
CGO_LDFLAGS_EXTRA += -lversion CGO_LDFLAGS_EXTRA += -lversion
LIBCLANG_NAME = libclang LIBCLANG_PATH = $(abspath $(LLVM_BUILDDIR))/lib/liblibclang.a
else ifeq ($(shell uname -s),Darwin) else ifeq ($(shell uname -s),Darwin)
MD5SUM = md5 MD5SUM = md5
LIBCLANG_NAME = clang LIBCLANG_PATH = $(abspath $(LLVM_BUILDDIR))/lib/libclang.a
else ifeq ($(shell uname -s),FreeBSD) else ifeq ($(shell uname -s),FreeBSD)
MD5SUM = md5 MD5SUM = md5
LIBCLANG_NAME = clang LIBCLANG_PATH = $(abspath $(LLVM_BUILDDIR))/lib/libclang.a
START_GROUP = -Wl,--start-group START_GROUP = -Wl,--start-group
END_GROUP = -Wl,--end-group END_GROUP = -Wl,--end-group
else else
LIBCLANG_NAME = clang LIBCLANG_PATH = $(abspath $(LLVM_BUILDDIR))/lib/libclang.a
START_GROUP = -Wl,--start-group START_GROUP = -Wl,--start-group
END_GROUP = -Wl,--end-group END_GROUP = -Wl,--end-group
endif endif
# Libraries that should be linked in for the statically linked Clang. 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_LIB_NAMES = clangAnalysis clangARCMigrate clangAST clangASTMatchers clangBasic clangCodeGen clangCrossTU clangDriver clangDynamicASTMatchers clangEdit clangFormat clangFrontend clangFrontendTool clangHandleCXX clangHandleLLVM clangIndex clangLex clangParse clangRewrite clangRewriteFrontend clangSema clangSerialization clangStaticAnalyzerCheckers clangStaticAnalyzerCore clangStaticAnalyzerFrontend clangTooling clangToolingASTDiff clangToolingCore clangToolingInclusions
CLANG_LIBS = $(START_GROUP) $(addprefix -l,$(CLANG_LIB_NAMES)) $(END_GROUP) -lstdc++
# Libraries that should be linked in for the statically linked LLD. LLD_LIBS = $(START_GROUP) -llldCOFF -llldCommon -llldCore -llldDriver -llldELF -llldMachO -llldMinGW -llldReaderWriter -llldWasm -llldYAML $(END_GROUP)
LLD_LIB_NAMES = lldCOFF lldCommon lldCore lldDriver lldELF lldMachO lldMinGW lldReaderWriter lldWasm lldYAML
LLD_LIBS = $(START_GROUP) $(addprefix -l,$(LLD_LIB_NAMES)) $(END_GROUP)
# Other libraries that are needed to link TinyGo.
EXTRA_LIB_NAMES = LLVMInterpreter
# These build targets appear to be the only ones necessary to build all TinyGo
# dependencies. Only building a subset significantly speeds up rebuilding LLVM.
# The Makefile rules convert a name like lldELF to lib/liblldELF.a to match the
# library path (for ninja).
# This list also includes a few tools that are necessary as part of the full
# TinyGo build.
NINJA_BUILD_TARGETS = clang llvm-config llvm-ar llvm-nm $(addprefix lib/lib,$(addsuffix .a,$(LIBCLANG_NAME) $(CLANG_LIB_NAMES) $(LLD_LIB_NAMES) $(EXTRA_LIB_NAMES)))
# For static linking. # For static linking.
ifneq ("$(wildcard $(LLVM_BUILDDIR)/bin/llvm-config*)","") 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_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_CXXFLAGS=-std=c++14
CGO_LDFLAGS+=$(abspath $(LLVM_BUILDDIR))/lib/lib$(LIBCLANG_NAME).a -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) CGO_LDFLAGS+=$(LIBCLANG_PATH) -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 endif
clean: clean:
@rm -rf build @rm -rf build
FMT_PATHS = ./*.go builder cgo compiler interp loader src/device/arm src/examples src/machine src/os src/reflect src/runtime src/sync src/syscall src/internal/reflectlite transform FMT_PATHS = ./*.go builder cgo compiler interp ir loader src/device/arm src/examples src/machine src/os src/reflect src/runtime src/sync src/syscall src/internal/reflectlite transform
fmt: fmt:
@gofmt -l -w $(FMT_PATHS) @gofmt -l -w $(FMT_PATHS)
fmt-check: fmt-check:
@unformatted=$$(gofmt -l $(FMT_PATHS)); [ -z "$$unformatted" ] && exit 0; echo "Unformatted:"; for fn in $$unformatted; do echo " $$fn"; done; exit 1 @unformatted=$$(gofmt -l $(FMT_PATHS)); [ -z "$$unformatted" ] && exit 0; echo "Unformatted:"; for fn in $$unformatted; do echo " $$fn"; done; exit 1
gen-device: gen-device-avr gen-device-esp gen-device-nrf gen-device-sam gen-device-sifive gen-device-kendryte gen-device-nxp gen-device: gen-device-avr gen-device-esp gen-device-nrf gen-device-sam gen-device-sifive gen-device-stm32 gen-device-kendryte gen-device-nxp
ifneq ($(STM32), 0)
gen-device: gen-device-stm32
endif
gen-device-avr: gen-device-avr:
@if [ ! -e lib/avr/README.md ]; then echo "Submodules have not been downloaded. Please download them using:\n git submodule update --init"; exit 1; fi @if [ ! -e lib/avr/README.md ]; then echo "Submodules have not been downloaded. Please download them using:\n git submodule update --init"; exit 1; fi
@@ -147,23 +130,23 @@ gen-device-kendryte: build/gen-device-svd
GO111MODULE=off $(GO) fmt ./src/device/kendryte GO111MODULE=off $(GO) fmt ./src/device/kendryte
gen-device-stm32: build/gen-device-svd gen-device-stm32: build/gen-device-svd
./build/gen-device-svd -source=https://github.com/tinygo-org/stm32-svd lib/stm32-svd/svd src/device/stm32/ ./build/gen-device-svd -source=https://github.com/posborne/cmsis-svd/tree/master/data/STMicro lib/cmsis-svd/data/STMicro/ src/device/stm32/
GO111MODULE=off $(GO) fmt ./src/device/stm32 GO111MODULE=off $(GO) fmt ./src/device/stm32
# Get LLVM sources. # Get LLVM sources.
$(LLVM_PROJECTDIR)/llvm: $(LLVM_PROJECTDIR)/README.md:
git clone -b xtensa_release_11.0.0 --depth=1 https://github.com/tinygo-org/llvm-project $(LLVM_PROJECTDIR) git clone -b xtensa_release_11.0.0 --depth=1 https://github.com/tinygo-org/llvm-project $(LLVM_PROJECTDIR)
llvm-source: $(LLVM_PROJECTDIR)/llvm llvm-source: $(LLVM_PROJECTDIR)/README.md
# Configure LLVM. # Configure LLVM.
TINYGO_SOURCE_DIR=$(shell pwd) TINYGO_SOURCE_DIR=$(shell pwd)
$(LLVM_BUILDDIR)/build.ninja: llvm-source $(LLVM_BUILDDIR)/build.ninja: llvm-source
mkdir -p $(LLVM_BUILDDIR); cd $(LLVM_BUILDDIR); cmake -G Ninja $(TINYGO_SOURCE_DIR)/$(LLVM_PROJECTDIR)/llvm "-DLLVM_TARGETS_TO_BUILD=X86;ARM;AArch64;RISCV;WebAssembly" "-DLLVM_EXPERIMENTAL_TARGETS_TO_BUILD=AVR;Xtensa" -DCMAKE_BUILD_TYPE=Release -DLIBCLANG_BUILD_STATIC=ON -DLLVM_ENABLE_TERMINFO=OFF -DLLVM_ENABLE_ZLIB=OFF -DLLVM_ENABLE_LIBEDIT=OFF -DLLVM_ENABLE_Z3_SOLVER=OFF -DLLVM_ENABLE_OCAMLDOC=OFF -DLLVM_ENABLE_LIBXML2=OFF -DLLVM_ENABLE_PROJECTS="clang;lld" -DLLVM_TOOL_CLANG_TOOLS_EXTRA_BUILD=OFF $(LLVM_OPTION) mkdir -p $(LLVM_BUILDDIR); cd $(LLVM_BUILDDIR); cmake -G Ninja $(TINYGO_SOURCE_DIR)/$(LLVM_PROJECTDIR)/llvm "-DLLVM_TARGETS_TO_BUILD=X86;ARM;AArch64;RISCV;WebAssembly" "-DLLVM_EXPERIMENTAL_TARGETS_TO_BUILD=AVR;Xtensa" -DCMAKE_BUILD_TYPE=Release -DLIBCLANG_BUILD_STATIC=ON -DLLVM_ENABLE_TERMINFO=OFF -DLLVM_ENABLE_ZLIB=OFF -DLLVM_ENABLE_LIBEDIT=OFF -DLLVM_ENABLE_Z3_SOLVER=OFF -DLLVM_ENABLE_OCAMLDOC=OFF -DLLVM_ENABLE_PROJECTS="clang;lld" -DLLVM_TOOL_CLANG_TOOLS_EXTRA_BUILD=OFF $(LLVM_OPTION)
# Build LLVM. # Build LLVM.
$(LLVM_BUILDDIR): $(LLVM_BUILDDIR)/build.ninja $(LLVM_BUILDDIR): $(LLVM_BUILDDIR)/build.ninja
cd $(LLVM_BUILDDIR); ninja $(NINJA_BUILD_TARGETS) cd $(LLVM_BUILDDIR); ninja
# Build wasi-libc sysroot # Build wasi-libc sysroot
@@ -180,7 +163,7 @@ tinygo:
CGO_CPPFLAGS="$(CGO_CPPFLAGS)" CGO_CXXFLAGS="$(CGO_CXXFLAGS)" CGO_LDFLAGS="$(CGO_LDFLAGS)" $(GO) build -buildmode exe -o build/tinygo$(EXE) -tags byollvm -ldflags="-X main.gitSha1=`git rev-parse --short HEAD`" . CGO_CPPFLAGS="$(CGO_CPPFLAGS)" CGO_CXXFLAGS="$(CGO_CXXFLAGS)" CGO_LDFLAGS="$(CGO_LDFLAGS)" $(GO) build -buildmode exe -o build/tinygo$(EXE) -tags byollvm -ldflags="-X main.gitSha1=`git rev-parse --short HEAD`" .
test: wasi-libc test: wasi-libc
CGO_CPPFLAGS="$(CGO_CPPFLAGS)" CGO_CXXFLAGS="$(CGO_CXXFLAGS)" CGO_LDFLAGS="$(CGO_LDFLAGS)" $(GO) test -v -buildmode exe -tags byollvm ./builder ./cgo ./compileopts ./compiler ./interp ./transform . CGO_CPPFLAGS="$(CGO_CPPFLAGS)" CGO_CXXFLAGS="$(CGO_CXXFLAGS)" CGO_LDFLAGS="$(CGO_LDFLAGS)" $(GO) test -v -buildmode exe -tags byollvm ./cgo ./compileopts ./interp ./transform .
# Test known-working standard library packages. # Test known-working standard library packages.
# TODO: do this in one command, parallelize, and only show failing tests (no # TODO: do this in one command, parallelize, and only show failing tests (no
@@ -254,14 +237,12 @@ smoketest:
@$(MD5SUM) test.hex @$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=microbit-s110v8 examples/echo $(TINYGO) build -size short -o test.hex -target=microbit-s110v8 examples/echo
@$(MD5SUM) test.hex @$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=microbit-v2 examples/microbit-blink
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=microbit-v2-s113v7 examples/microbit-blink
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=nrf52840-mdk examples/blinky1 $(TINYGO) build -size short -o test.hex -target=nrf52840-mdk examples/blinky1
@$(MD5SUM) test.hex @$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=pca10031 examples/blinky1 $(TINYGO) build -size short -o test.hex -target=pca10031 examples/blinky1
@$(MD5SUM) test.hex @$(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 $(TINYGO) build -size short -o test.hex -target=reelboard examples/blinky1
@$(MD5SUM) test.hex @$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=reelboard examples/blinky2 $(TINYGO) build -size short -o test.hex -target=reelboard examples/blinky2
@@ -270,10 +251,6 @@ smoketest:
@$(MD5SUM) test.hex @$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=pca10056 examples/blinky2 $(TINYGO) build -size short -o test.hex -target=pca10056 examples/blinky2
@$(MD5SUM) test.hex @$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=pca10059 examples/blinky1
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=pca10059 examples/blinky2
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=itsybitsy-m0 examples/blinky1 $(TINYGO) build -size short -o test.hex -target=itsybitsy-m0 examples/blinky1
@$(MD5SUM) test.hex @$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=feather-m0 examples/blinky1 $(TINYGO) build -size short -o test.hex -target=feather-m0 examples/blinky1
@@ -282,6 +259,14 @@ smoketest:
@$(MD5SUM) test.hex @$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=circuitplay-express examples/blinky1 $(TINYGO) build -size short -o test.hex -target=circuitplay-express examples/blinky1
@$(MD5SUM) test.hex @$(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=stm32f4disco-1 examples/blinky1
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=feather-stm32f405 examples/blinky1
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=circuitplay-bluefruit examples/blinky1 $(TINYGO) build -size short -o test.hex -target=circuitplay-bluefruit examples/blinky1
@$(MD5SUM) test.hex @$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=circuitplay-express examples/i2s $(TINYGO) build -size short -o test.hex -target=circuitplay-express examples/i2s
@@ -290,8 +275,6 @@ smoketest:
@$(MD5SUM) test.hex @$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.gba -target=gameboy-advance examples/gba-display $(TINYGO) build -size short -o test.gba -target=gameboy-advance examples/gba-display
@$(MD5SUM) test.gba @$(MD5SUM) test.gba
$(TINYGO) build -size short -o test.hex -target=grandcentral-m4 examples/blinky1
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=itsybitsy-m4 examples/blinky1 $(TINYGO) build -size short -o test.hex -target=itsybitsy-m4 examples/blinky1
@$(MD5SUM) test.hex @$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=feather-m4 examples/blinky1 $(TINYGO) build -size short -o test.hex -target=feather-m4 examples/blinky1
@@ -308,6 +291,8 @@ smoketest:
@$(MD5SUM) test.hex @$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=particle-xenon examples/blinky1 $(TINYGO) build -size short -o test.hex -target=particle-xenon examples/blinky1
@$(MD5SUM) test.hex @$(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 $(TINYGO) build -size short -o test.hex -target=pinetime-devkit0 examples/blinky1
@$(MD5SUM) test.hex @$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=x9pro examples/blinky1 $(TINYGO) build -size short -o test.hex -target=x9pro examples/blinky1
@@ -336,11 +321,7 @@ smoketest:
@$(MD5SUM) test.hex @$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=teensy36 examples/blinky1 $(TINYGO) build -size short -o test.hex -target=teensy36 examples/blinky1
@$(MD5SUM) test.hex @$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=p1am-100 examples/blinky1 $(TINYGO) build -size short -o test.hex -target=nucleo-f722ze examples/blinky1
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=atsame54-xpro examples/blinky1
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=feather-m4-can examples/blinky1
@$(MD5SUM) test.hex @$(MD5SUM) test.hex
# test pwm # test pwm
$(TINYGO) build -size short -o test.hex -target=itsybitsy-m0 examples/pwm $(TINYGO) build -size short -o test.hex -target=itsybitsy-m0 examples/pwm
@@ -349,30 +330,8 @@ smoketest:
@$(MD5SUM) test.hex @$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=feather-m4 examples/pwm $(TINYGO) build -size short -o test.hex -target=feather-m4 examples/pwm
@$(MD5SUM) test.hex @$(MD5SUM) test.hex
ifneq ($(STM32), 0) $(TINYGO) build -size short -o test.hex -target=pyportal examples/pwm
$(TINYGO) build -size short -o test.hex -target=bluepill examples/blinky1
@$(MD5SUM) test.hex @$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=feather-stm32f405 examples/blinky1
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=lgt92 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=nucleo-f722ze examples/blinky1
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=nucleo-l031k6 examples/blinky1
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=nucleo-l432kc examples/blinky1
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=nucleo-l552ze 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=stm32f4disco-1 examples/blinky1
@$(MD5SUM) test.hex
endif
ifneq ($(AVR), 0) ifneq ($(AVR), 0)
$(TINYGO) build -size short -o test.hex -target=atmega1284p examples/serial $(TINYGO) build -size short -o test.hex -target=atmega1284p examples/serial
@$(MD5SUM) test.hex @$(MD5SUM) test.hex
@@ -382,10 +341,6 @@ ifneq ($(AVR), 0)
@$(MD5SUM) test.hex @$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=arduino -scheduler=tasks examples/blinky1 $(TINYGO) build -size short -o test.hex -target=arduino -scheduler=tasks examples/blinky1
@$(MD5SUM) test.hex @$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=arduino-mega1280 examples/blinky1
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=arduino-mega1280 examples/pwm
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=arduino-nano examples/blinky1 $(TINYGO) build -size short -o test.hex -target=arduino-nano examples/blinky1
@$(MD5SUM) test.hex @$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=digispark examples/blinky1 $(TINYGO) build -size short -o test.hex -target=digispark examples/blinky1
@@ -414,9 +369,6 @@ endif
@$(MD5SUM) test.hex @$(MD5SUM) test.hex
$(TINYGO) build -o test.nro -target=nintendoswitch examples/serial $(TINYGO) build -o test.nro -target=nintendoswitch examples/serial
@$(MD5SUM) test.nro @$(MD5SUM) test.nro
$(TINYGO) build -size short -o test.hex -target=pca10040 -opt=0 ./testdata/stdlib.go
@$(MD5SUM) test.hex
wasmtest: wasmtest:
$(GO) test ./tests/wasm $(GO) test ./tests/wasm
+4 -17
View File
@@ -43,43 +43,34 @@ See the [getting started instructions](https://tinygo.org/getting-started/) for
You can compile TinyGo programs for microcontrollers, WebAssembly and Linux. You can compile TinyGo programs for microcontrollers, WebAssembly and Linux.
The following 57 microcontroller boards are currently supported: The following 44 microcontroller boards are currently supported:
* [Adafruit Circuit Playground Bluefruit](https://www.adafruit.com/product/4333) * [Adafruit Circuit Playground Bluefruit](https://www.adafruit.com/product/4333)
* [Adafruit Circuit Playground Express](https://www.adafruit.com/product/3333) * [Adafruit Circuit Playground Express](https://www.adafruit.com/product/3333)
* [Adafruit CLUE](https://www.adafruit.com/product/4500) * [Adafruit CLUE Alpha](https://www.adafruit.com/product/4500)
* [Adafruit Feather M0](https://www.adafruit.com/product/2772) * [Adafruit Feather M0](https://www.adafruit.com/product/2772)
* [Adafruit Feather M4](https://www.adafruit.com/product/3857) * [Adafruit Feather M4](https://www.adafruit.com/product/3857)
* [Adafruit Feather M4 CAN](https://www.adafruit.com/product/4759)
* [Adafruit Feather nRF52840 Express](https://www.adafruit.com/product/4062) * [Adafruit Feather nRF52840 Express](https://www.adafruit.com/product/4062)
* [Adafruit Feather STM32F405 Express](https://www.adafruit.com/product/4382) * [Adafruit Feather STM32F405 Express](https://www.adafruit.com/product/4382)
* [Adafruit ItsyBitsy M0](https://www.adafruit.com/product/3727) * [Adafruit ItsyBitsy M0](https://www.adafruit.com/product/3727)
* [Adafruit ItsyBitsy M4](https://www.adafruit.com/product/3800) * [Adafruit ItsyBitsy M4](https://www.adafruit.com/product/3800)
* [Adafruit ItsyBitsy nRF52840](https://www.adafruit.com/product/4481) * [Adafruit ItsyBitsy nRF52840](https://www.adafruit.com/product/4481)
* [Adafruit Matrix Portal M4](https://www.adafruit.com/product/4745)
* [Adafruit Metro M4 Express Airlift](https://www.adafruit.com/product/4000) * [Adafruit Metro M4 Express Airlift](https://www.adafruit.com/product/4000)
* [Adafruit PyBadge](https://www.adafruit.com/product/4200) * [Adafruit PyBadge](https://www.adafruit.com/product/4200)
* [Adafruit PyGamer](https://www.adafruit.com/product/4242) * [Adafruit PyGamer](https://www.adafruit.com/product/4242)
* [Adafruit PyPortal](https://www.adafruit.com/product/4116) * [Adafruit PyPortal](https://www.adafruit.com/product/4116)
* [Adafruit QT Py](https://www.adafruit.com/product/4600)
* [Adafruit Trinket M0](https://www.adafruit.com/product/3500) * [Adafruit Trinket M0](https://www.adafruit.com/product/3500)
* [Arduino Mega 2560](https://store.arduino.cc/arduino-mega-2560-rev3) * [Arduino Mega 2560](https://store.arduino.cc/arduino-mega-2560-rev3)
* [Arduino MKR1000](https://store.arduino.cc/arduino-mkr1000-wifi)
* [Arduino Nano](https://store.arduino.cc/arduino-nano) * [Arduino Nano](https://store.arduino.cc/arduino-nano)
* [Arduino Nano33 IoT](https://store.arduino.cc/nano-33-iot) * [Arduino Nano33 IoT](https://store.arduino.cc/nano-33-iot)
* [Arduino Uno](https://store.arduino.cc/arduino-uno-rev3) * [Arduino Uno](https://store.arduino.cc/arduino-uno-rev3)
* [Arduino Zero](https://store.arduino.cc/usa/arduino-zero) * [Arduino Zero](https://store.arduino.cc/usa/arduino-zero)
* [BBC micro:bit](https://microbit.org/) * [BBC micro:bit](https://microbit.org/)
* [BBC micro:bit v2](https://microbit.org/new-microbit/)
* [Digispark](http://digistump.com/products/1) * [Digispark](http://digistump.com/products/1)
* [Dragino LoRaWAN GPS Tracker LGT-92](http://www.dragino.com/products/lora-lorawan-end-node/item/142-lgt-92.html)
* [ESP32](https://www.espressif.com/en/products/socs/esp32) * [ESP32](https://www.espressif.com/en/products/socs/esp32)
* [ESP8266](https://www.espressif.com/en/products/socs/esp8266) * [ESP8266](https://www.espressif.com/en/products/socs/esp8266)
* [Game Boy Advance](https://en.wikipedia.org/wiki/Game_Boy_Advance) * [Game Boy Advance](https://en.wikipedia.org/wiki/Game_Boy_Advance)
* [Makerdiary nRF52840-MDK](https://wiki.makerdiary.com/nrf52840-mdk/) * [Makerdiary nRF52840-MDK](https://wiki.makerdiary.com/nrf52840-mdk/)
* [Makerdiary nRF52840-MDK USB Dongle](https://wiki.makerdiary.com/nrf52840-mdk-usb-dongle/)
* [Microchip SAM E54 Xplained Pro](https://www.microchip.com/developmenttools/productdetails/atsame54-xpro)
* [nice!nano](https://docs.nicekeyboards.com/#/nice!nano/)
* [Nintendo Switch](https://www.nintendo.com/switch/) * [Nintendo Switch](https://www.nintendo.com/switch/)
* [Nordic Semiconductor PCA10031](https://www.nordicsemi.com/eng/Products/nRF51-Dongle) * [Nordic Semiconductor PCA10031](https://www.nordicsemi.com/eng/Products/nRF51-Dongle)
* [Nordic Semiconductor PCA10040](https://www.nordicsemi.com/eng/Products/Bluetooth-low-energy/nRF52-DK) * [Nordic Semiconductor PCA10040](https://www.nordicsemi.com/eng/Products/Bluetooth-low-energy/nRF52-DK)
@@ -90,16 +81,12 @@ The following 57 microcontroller boards are currently supported:
* [Phytec reel board](https://www.phytec.eu/product-eu/internet-of-things/reelboard/) * [Phytec reel board](https://www.phytec.eu/product-eu/internet-of-things/reelboard/)
* [PineTime DevKit](https://www.pine64.org/pinetime/) * [PineTime DevKit](https://www.pine64.org/pinetime/)
* [PJRC Teensy 3.6](https://www.pjrc.com/store/teensy36.html) * [PJRC Teensy 3.6](https://www.pjrc.com/store/teensy36.html)
* [PJRC Teensy 4.0](https://www.pjrc.com/store/teensy40.html)
* [ProductivityOpen P1AM-100](https://facts-engineering.github.io/modules/P1AM-100/P1AM-100.html)
* [Seeed Wio Terminal](https://www.seeedstudio.com/Wio-Terminal-p-4509.html) * [Seeed Wio Terminal](https://www.seeedstudio.com/Wio-Terminal-p-4509.html)
* [Seeed Seeeduino XIAO](https://www.seeedstudio.com/Seeeduino-XIAO-Arduino-Microcontroller-SAMD21-Cortex-M0+-p-4426.html) * [Seeed Seeeduino XIAO](https://www.seeedstudio.com/Seeeduino-XIAO-Arduino-Microcontroller-SAMD21-Cortex-M0+-p-4426.html)
* [Seeed Sipeed MAix BiT](https://www.seeedstudio.com/Sipeed-MAix-BiT-for-RISC-V-AI-IoT-p-2872.html) * [Seeed Sipeed MAix BiT](https://www.seeedstudio.com/Sipeed-MAix-BiT-for-RISC-V-AI-IoT-p-2872.html)
* [SiFIve HiFive1](https://www.sifive.com/boards/hifive1) * [SiFIve HiFive1](https://www.sifive.com/boards/hifive1)
* [ST Micro "Nucleo" F103RB](https://www.st.com/en/evaluation-tools/nucleo-f103rb.html) * [ST Micro "Nucleo F103RB"](https://www.st.com/en/evaluation-tools/nucleo-f103rb.html)
* [ST Micro "Nucleo" F722ZE](https://www.st.com/en/evaluation-tools/nucleo-f722ze.html) * [ST Micro STM32F103XX "Bluepill"](http://wiki.stm32duino.com/index.php?title=Blue_Pill)
* [ST Micro "Nucleo" L552ZE](https://www.st.com/en/evaluation-tools/nucleo-l552ze-q.html)
* [ST Micro STM32F103XX "Bluepill"](https://stm32-base.org/boards/STM32F103C8T6-Blue-Pill)
* [ST Micro STM32F407 "Discovery"](https://www.st.com/en/evaluation-tools/stm32f4discovery.html) * [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/) * [X9 Pro smartwatch](https://github.com/curtpw/nRF5x-device-reverse-engineering/tree/master/X9-nrf52832-activity-tracker/)
+8 -4
View File
@@ -12,7 +12,7 @@ jobs:
steps: steps:
- task: GoTool@0 - task: GoTool@0
inputs: inputs:
version: '1.16' version: '1.15'
- checkout: self - checkout: self
fetchDepth: 1 fetchDepth: 1
- task: Cache@2 - task: Cache@2
@@ -24,11 +24,15 @@ jobs:
displayName: Download LLVM source displayName: Download LLVM source
inputs: inputs:
targetType: inline targetType: inline
script: make llvm-source script: |
make llvm-source
# Workaround for bad symlinks:
# https://github.com/microsoft/azure-pipelines-tasks/issues/13418
rm -f llvm-project/libcxx/test/std/input.output/filesystems/Inputs/static_test_env/bad_symlink
- task: CacheBeta@0 - task: CacheBeta@0
displayName: Cache LLVM build displayName: Cache LLVM build
inputs: inputs:
key: llvm-build-11-windows-v4 key: llvm-build-11-windows-v3
path: llvm-build path: llvm-build
- task: Bash@3 - task: Bash@3
displayName: Build LLVM displayName: Build LLVM
@@ -52,7 +56,7 @@ jobs:
- task: CacheBeta@0 - task: CacheBeta@0
displayName: Cache wasi-libc sysroot displayName: Cache wasi-libc sysroot
inputs: inputs:
key: wasi-libc-sysroot-v4 key: wasi-libc-sysroot-v3
path: lib/wasi-libc/sysroot path: lib/wasi-libc/sysroot
- task: Bash@3 - task: Bash@3
displayName: Build wasi-libc displayName: Build wasi-libc
+213 -685
View File
@@ -4,26 +4,21 @@
package builder package builder
import ( import (
"crypto/sha512"
"debug/elf" "debug/elf"
"encoding/binary" "encoding/binary"
"encoding/hex"
"encoding/json"
"errors" "errors"
"fmt" "fmt"
"go/types"
"io/ioutil" "io/ioutil"
"os" "os"
"path/filepath" "path/filepath"
"runtime"
"sort" "sort"
"strconv"
"strings" "strings"
"github.com/tinygo-org/tinygo/compileopts" "github.com/tinygo-org/tinygo/compileopts"
"github.com/tinygo-org/tinygo/compiler" "github.com/tinygo-org/tinygo/compiler"
"github.com/tinygo-org/tinygo/goenv" "github.com/tinygo-org/tinygo/goenv"
"github.com/tinygo-org/tinygo/interp" "github.com/tinygo-org/tinygo/interp"
"github.com/tinygo-org/tinygo/loader"
"github.com/tinygo-org/tinygo/stacksize" "github.com/tinygo-org/tinygo/stacksize"
"github.com/tinygo-org/tinygo/transform" "github.com/tinygo-org/tinygo/transform"
"tinygo.org/x/go-llvm" "tinygo.org/x/go-llvm"
@@ -41,30 +36,6 @@ type BuildResult struct {
MainDir string MainDir string
} }
// packageAction is the struct that is serialized to JSON and hashed, to work as
// a cache key of compiled packages. It should contain all the information that
// goes into a compiled package to avoid using stale data.
//
// Right now it's still important to include a hash of every import, because a
// dependency might have a public constant that this package uses and thus this
// package will need to be recompiled if that constant changes. In the future,
// the type data should be serialized to disk which can then be used as cache
// key, avoiding the need for recompiling all dependencies when only the
// implementation of an imported package changes.
type packageAction struct {
ImportPath string
CompilerVersion int // compiler.Version
InterpVersion int // interp.Version
LLVMVersion string
Config *compiler.Config
CFlags []string
FileHashes map[string]string // hash of every file that's part of the package
Imports map[string]string // map from imported package to action ID hash
OptLevel int // LLVM optimization level (0-3)
SizeLevel int // LLVM optimization for size level (0-2)
UndefinedGlobals []string // globals that are left as external globals (no initializer)
}
// Build performs a single package to executable Go build. It takes in a package // Build performs a single package to executable Go build. It takes in a package
// name, an output path, and set of compile options and from that it manages the // name, an output path, and set of compile options and from that it manages the
// whole compilation process. // whole compilation process.
@@ -72,613 +43,37 @@ type packageAction struct {
// The error value may be of type *MultiError. Callers will likely want to check // The error value may be of type *MultiError. Callers will likely want to check
// for this case and print such errors individually. // for this case and print such errors individually.
func Build(pkgName, outpath string, config *compileopts.Config, action func(BuildResult) error) error { func Build(pkgName, outpath string, config *compileopts.Config, action func(BuildResult) error) error {
// Create a temporary directory for intermediary files. // Compile Go code to IR.
dir, err := ioutil.TempDir("", "tinygo") machine, err := compiler.NewTargetMachine(config)
if err != nil { if err != nil {
return err return err
} }
defer os.RemoveAll(dir) buildOutput, errs := compiler.Compile(pkgName, machine, config)
if errs != nil {
return newMultiError(errs)
}
mod := buildOutput.Mod
compilerConfig := &compiler.Config{ if config.Options.PrintIR {
Triple: config.Triple(), fmt.Println("; Generated LLVM IR:")
CPU: config.CPU(), fmt.Println(mod.String())
Features: config.Features(), }
GOOS: config.GOOS(), if err := llvm.VerifyModule(mod, llvm.PrintMessageAction); err != nil {
GOARCH: config.GOARCH(), return errors.New("verification error after IR construction")
CodeModel: config.CodeModel(),
RelocationModel: config.RelocationModel(),
Scheduler: config.Scheduler(),
FuncImplementation: config.FuncImplementation(),
AutomaticStackSize: config.AutomaticStackSize(),
DefaultStackSize: config.Target.DefaultStackSize,
NeedsStackObjects: config.NeedsStackObjects(),
Debug: config.Debug(),
} }
// Load the target machine, which is the LLVM object that contains all err = interp.Run(mod, config.DumpSSA())
// details of a target (alignment restrictions, pointer size, default
// address spaces, etc).
machine, err := compiler.NewTargetMachine(compilerConfig)
if err != nil { if err != nil {
return err return err
} }
if err := llvm.VerifyModule(mod, llvm.PrintMessageAction); err != nil {
// Load entire program AST into memory. return errors.New("verification error after interpreting runtime.initAll")
lprogram, err := loader.Load(config, []string{pkgName}, config.ClangHeaders, types.Config{
Sizes: compiler.Sizes(machine),
})
if err != nil {
return err
}
err = lprogram.Parse()
if err != nil {
return err
}
// The slice of jobs that orchestrates most of the build.
// This is somewhat like an in-memory Makefile with each job being a
// Makefile target.
var jobs []*compileJob
// Create the *ssa.Program. This does not yet build the entire SSA of the
// program so it's pretty fast and doesn't need to be parallelized.
program := lprogram.LoadSSA()
// Add jobs to compile each package.
// Packages that have a cache hit will not be compiled again.
var packageJobs []*compileJob
packageBitcodePaths := make(map[string]string)
packageActionIDs := make(map[string]string)
optLevel, sizeLevel, _ := config.OptLevels()
for _, pkg := range lprogram.Sorted() {
pkg := pkg // necessary to avoid a race condition
var undefinedGlobals []string
for name := range config.Options.GlobalValues[pkg.Pkg.Path()] {
undefinedGlobals = append(undefinedGlobals, name)
}
sort.Strings(undefinedGlobals)
// Create a cache key: a hash from the action ID below that contains all
// the parameters for the build.
actionID := packageAction{
ImportPath: pkg.ImportPath,
CompilerVersion: compiler.Version,
InterpVersion: interp.Version,
LLVMVersion: llvm.Version,
Config: compilerConfig,
CFlags: pkg.CFlags,
FileHashes: make(map[string]string, len(pkg.FileHashes)),
Imports: make(map[string]string, len(pkg.Pkg.Imports())),
OptLevel: optLevel,
SizeLevel: sizeLevel,
UndefinedGlobals: undefinedGlobals,
}
for filePath, hash := range pkg.FileHashes {
actionID.FileHashes[filePath] = hex.EncodeToString(hash)
}
for _, imported := range pkg.Pkg.Imports() {
hash, ok := packageActionIDs[imported.Path()]
if !ok {
return fmt.Errorf("package %s imports %s but couldn't find dependency", pkg.ImportPath, imported.Path())
}
actionID.Imports[imported.Path()] = hash
}
buf, err := json.Marshal(actionID)
if err != nil {
panic(err) // shouldn't happen
}
hash := sha512.Sum512_224(buf)
packageActionIDs[pkg.ImportPath] = hex.EncodeToString(hash[:])
// Determine the path of the bitcode file (which is a serialized version
// of a LLVM module).
cacheDir := goenv.Get("GOCACHE")
if cacheDir == "off" {
// Use temporary build directory instead, effectively disabling the
// build cache.
cacheDir = dir
}
bitcodePath := filepath.Join(cacheDir, "pkg-"+hex.EncodeToString(hash[:])+".bc")
packageBitcodePaths[pkg.ImportPath] = bitcodePath
// Check whether this package has been compiled before, and if so don't
// compile it again.
if _, err := os.Stat(bitcodePath); err == nil {
// Already cached, don't recreate this package.
continue
}
// The package has not yet been compiled, so create a job to do so.
job := &compileJob{
description: "compile package " + pkg.ImportPath,
run: func(*compileJob) error {
// Compile AST to IR. The compiler.CompilePackage function will
// build the SSA as needed.
mod, errs := compiler.CompilePackage(pkg.ImportPath, pkg, program.Package(pkg.Pkg), machine, compilerConfig, config.DumpSSA())
if errs != nil {
return newMultiError(errs)
}
if err := llvm.VerifyModule(mod, llvm.PrintMessageAction); err != nil {
return errors.New("verification error after compiling package " + pkg.ImportPath)
}
// Erase all globals that are part of the undefinedGlobals list.
// This list comes from the -ldflags="-X pkg.foo=val" option.
// Instead of setting the value directly in the AST (which would
// mean the value, which may be a secret, is stored in the build
// cache), the global itself is left external (undefined) and is
// only set at the end of the compilation.
for _, name := range undefinedGlobals {
globalName := pkg.Pkg.Path() + "." + name
global := mod.NamedGlobal(globalName)
if global.IsNil() {
return errors.New("global not found: " + globalName)
}
name := global.Name()
newGlobal := llvm.AddGlobal(mod, global.Type().ElementType(), name+".tmp")
global.ReplaceAllUsesWith(newGlobal)
global.EraseFromParentAsGlobal()
newGlobal.SetName(name)
}
// Try to interpret package initializers at compile time.
// It may only be possible to do this partially, in which case
// it is completed after all IR files are linked.
pkgInit := mod.NamedFunction(pkg.Pkg.Path() + ".init")
if pkgInit.IsNil() {
panic("init not found for " + pkg.Pkg.Path())
}
err := interp.RunFunc(pkgInit, config.DumpSSA())
if err != nil {
return err
}
if err := llvm.VerifyModule(mod, llvm.PrintMessageAction); err != nil {
return errors.New("verification error after interpreting " + pkgInit.Name())
}
if sizeLevel >= 2 {
// Set the "optsize" attribute to make slightly smaller
// binaries at the cost of some performance.
kind := llvm.AttributeKindID("optsize")
attr := mod.Context().CreateEnumAttribute(kind, 0)
for fn := mod.FirstFunction(); !fn.IsNil(); fn = llvm.NextFunction(fn) {
fn.AddFunctionAttr(attr)
}
}
// Run function passes for each function in the module.
// These passes are intended to be run on each function right
// after they're created to reduce IR size (and maybe also for
// cache locality to improve performance), but for now they're
// run here for each function in turn. Maybe this can be
// improved in the future.
builder := llvm.NewPassManagerBuilder()
defer builder.Dispose()
builder.SetOptLevel(optLevel)
builder.SetSizeLevel(sizeLevel)
funcPasses := llvm.NewFunctionPassManagerForModule(mod)
defer funcPasses.Dispose()
builder.PopulateFunc(funcPasses)
funcPasses.InitializeFunc()
for fn := mod.FirstFunction(); !fn.IsNil(); fn = llvm.NextFunction(fn) {
if fn.IsDeclaration() {
continue
}
funcPasses.RunFunc(fn)
}
funcPasses.FinalizeFunc()
// Serialize the LLVM module as a bitcode file.
// Write to a temporary path that is renamed to the destination
// file to avoid race conditions with other TinyGo invocatiosn
// that might also be compiling this package at the same time.
f, err := ioutil.TempFile(filepath.Dir(bitcodePath), filepath.Base(bitcodePath))
if err != nil {
return err
}
if runtime.GOOS == "windows" {
// Work around a problem on Windows.
// For some reason, WriteBitcodeToFile causes TinyGo to
// exit with the following message:
// LLVM ERROR: IO failure on output stream: Bad file descriptor
buf := llvm.WriteBitcodeToMemoryBuffer(mod)
defer buf.Dispose()
_, err = f.Write(buf.Bytes())
} else {
// Otherwise, write bitcode directly to the file (probably
// faster).
err = llvm.WriteBitcodeToFile(mod, f)
}
if err != nil {
// WriteBitcodeToFile doesn't produce a useful error on its
// own, so create a somewhat useful error message here.
return fmt.Errorf("failed to write bitcode for package %s to file %s", pkg.ImportPath, bitcodePath)
}
err = f.Close()
if err != nil {
return err
}
// Rename may fail if another process is trying to write to
// the same file. However, in this case, the failure is
// acceptable because the result of the other process can be
// used.
os.Rename(f.Name(), bitcodePath)
return nil
},
}
jobs = append(jobs, job)
packageJobs = append(packageJobs, job)
}
// Add job that links and optimizes all packages together.
var mod llvm.Module
var stackSizeLoads []string
programJob := &compileJob{
description: "link+optimize packages (LTO)",
dependencies: packageJobs,
run: func(*compileJob) error {
// Load and link all the bitcode files. This does not yet optimize
// anything, it only links the bitcode files together.
ctx := llvm.NewContext()
mod = ctx.NewModule("")
for _, pkg := range lprogram.Sorted() {
pkgMod, err := ctx.ParseBitcodeFile(packageBitcodePaths[pkg.ImportPath])
if err != nil {
return fmt.Errorf("failed to load bitcode file: %w", err)
}
err = llvm.LinkModules(mod, pkgMod)
if err != nil {
return fmt.Errorf("failed to link module: %w", err)
}
}
// Create runtime.initAll function that calls the runtime
// initializer of each package.
llvmInitFn := mod.NamedFunction("runtime.initAll")
llvmInitFn.SetLinkage(llvm.InternalLinkage)
llvmInitFn.SetUnnamedAddr(true)
llvmInitFn.Param(0).SetName("context")
llvmInitFn.Param(1).SetName("parentHandle")
block := mod.Context().AddBasicBlock(llvmInitFn, "entry")
irbuilder := mod.Context().NewBuilder()
defer irbuilder.Dispose()
irbuilder.SetInsertPointAtEnd(block)
i8ptrType := llvm.PointerType(mod.Context().Int8Type(), 0)
for _, pkg := range lprogram.Sorted() {
pkgInit := mod.NamedFunction(pkg.Pkg.Path() + ".init")
if pkgInit.IsNil() {
panic("init not found for " + pkg.Pkg.Path())
}
irbuilder.CreateCall(pkgInit, []llvm.Value{llvm.Undef(i8ptrType), llvm.Undef(i8ptrType)}, "")
}
irbuilder.CreateRetVoid()
// After linking, functions should (as far as possible) be set to
// private linkage or internal linkage. The compiler package marks
// non-exported functions by setting the visibility to hidden or
// (for thunks) to linkonce_odr linkage. Change the linkage here to
// internal to benefit much more from interprocedural optimizations.
for fn := mod.FirstFunction(); !fn.IsNil(); fn = llvm.NextFunction(fn) {
if fn.Visibility() == llvm.HiddenVisibility {
fn.SetVisibility(llvm.DefaultVisibility)
fn.SetLinkage(llvm.InternalLinkage)
} else if fn.Linkage() == llvm.LinkOnceODRLinkage {
fn.SetLinkage(llvm.InternalLinkage)
}
}
// Do the same for globals.
for global := mod.FirstGlobal(); !global.IsNil(); global = llvm.NextGlobal(global) {
if global.Visibility() == llvm.HiddenVisibility {
global.SetVisibility(llvm.DefaultVisibility)
global.SetLinkage(llvm.InternalLinkage)
} else if global.Linkage() == llvm.LinkOnceODRLinkage {
global.SetLinkage(llvm.InternalLinkage)
}
}
if config.Options.PrintIR {
fmt.Println("; Generated LLVM IR:")
fmt.Println(mod.String())
}
// Run all optimization passes, which are much more effective now
// that the optimizer can see the whole program at once.
err := optimizeProgram(mod, config)
if err != nil {
return err
}
// Make sure stack sizes are loaded from a separate section so they can be
// modified after linking.
if config.AutomaticStackSize() {
stackSizeLoads = transform.CreateStackSizeLoads(mod, config)
}
return nil
},
}
jobs = append(jobs, programJob)
// Check whether we only need to create an object file.
// If so, we don't need to link anything and will be finished quickly.
outext := filepath.Ext(outpath)
if outext == ".o" || outext == ".bc" || outext == ".ll" {
// Run jobs to produce the LLVM module.
err := runJobs(jobs)
if err != nil {
return err
}
// Generate output.
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:
panic("unreachable")
}
}
// Act as a compiler driver, as we need to produce a complete executable.
// First add all jobs necessary to build this object file, then afterwards
// run all jobs in parallel as far as possible.
// Add job to write the output object file.
objfile := filepath.Join(dir, "main.o")
outputObjectFileJob := &compileJob{
description: "generate output file",
dependencies: []*compileJob{programJob},
result: objfile,
run: func(*compileJob) error {
llvmBuf, err := machine.EmitToMemoryBuffer(mod, llvm.ObjectFile)
if err != nil {
return err
}
return ioutil.WriteFile(objfile, llvmBuf.Bytes(), 0666)
},
}
jobs = append(jobs, outputObjectFileJob)
// Prepare link command.
linkerDependencies := []*compileJob{outputObjectFileJob}
executable := filepath.Join(dir, "main")
tmppath := executable // final file
ldflags := append(config.LDFlags(), "-o", executable)
// Add compiler-rt dependency if needed. Usually this is a simple load from
// a cache.
if config.Target.RTLib == "compiler-rt" {
job, err := CompilerRT.load(config.Triple(), config.CPU(), dir)
if err != nil {
return err
}
jobs = append(jobs, job.dependencies...)
jobs = append(jobs, job)
linkerDependencies = append(linkerDependencies, job)
}
// Add libc dependency if needed.
root := goenv.Get("TINYGOROOT")
switch config.Target.Libc {
case "picolibc":
job, err := Picolibc.load(config.Triple(), config.CPU(), dir)
if err != nil {
return err
}
// The library needs to be compiled (cache miss).
jobs = append(jobs, job.dependencies...)
jobs = append(jobs, job)
linkerDependencies = append(linkerDependencies, job)
case "wasi-libc":
path := filepath.Join(root, "lib/wasi-libc/sysroot/lib/wasm32-wasi/libc.a")
if _, err := os.Stat(path); os.IsNotExist(err) {
return errors.New("could not find wasi-libc, perhaps you need to run `make wasi-libc`?")
}
ldflags = append(ldflags, path)
case "":
// no library specified, so nothing to do
default:
return fmt.Errorf("unknown libc: %s", config.Target.Libc)
}
// Add jobs to compile extra files. These files are in C or assembly and
// contain things like the interrupt vector table and low level operations
// such as stack switching.
for _, path := range config.ExtraFiles() {
abspath := filepath.Join(root, path)
job := &compileJob{
description: "compile extra file " + path,
run: func(job *compileJob) error {
result, err := compileAndCacheCFile(abspath, dir, config.CFlags(), config.Options.PrintCommands)
job.result = result
return err
},
}
jobs = append(jobs, job)
linkerDependencies = append(linkerDependencies, job)
}
// Add jobs to compile C files in all packages. This is part of CGo.
// TODO: do this as part of building the package to be able to link the
// bitcode files together.
for _, pkg := range lprogram.Sorted() {
pkg := pkg
for _, filename := range pkg.CFiles {
abspath := filepath.Join(pkg.Dir, filename)
job := &compileJob{
description: "compile CGo file " + abspath,
run: func(job *compileJob) error {
result, err := compileAndCacheCFile(abspath, dir, pkg.CFlags, config.Options.PrintCommands)
job.result = result
return err
},
}
jobs = append(jobs, job)
linkerDependencies = append(linkerDependencies, job)
}
}
// Linker flags from CGo lines:
// #cgo LDFLAGS: foo
if len(lprogram.LDFlags) > 0 {
ldflags = append(ldflags, lprogram.LDFlags...)
}
// Create a linker job, which links all object files together and does some
// extra stuff that can only be done after linking.
jobs = append(jobs, &compileJob{
description: "link",
dependencies: linkerDependencies,
run: func(job *compileJob) error {
for _, dependency := range job.dependencies {
if dependency.result == "" {
return errors.New("dependency without result: " + dependency.description)
}
ldflags = append(ldflags, dependency.result)
}
if config.Options.PrintCommands {
fmt.Printf("%s %s\n", config.Target.Linker, strings.Join(ldflags, " "))
}
err = link(config.Target.Linker, ldflags...)
if err != nil {
return &commandError{"failed to link", executable, err}
}
var calculatedStacks []string
var stackSizes map[string]functionStackSize
if config.Options.PrintStacks || config.AutomaticStackSize() {
// Try to determine stack sizes at compile time.
// Don't do this by default as it usually doesn't work on
// unsupported architectures.
calculatedStacks, stackSizes, err = determineStackSizes(mod, executable)
if err != nil {
return err
}
}
if config.AutomaticStackSize() {
// Modify the .tinygo_stacksizes section that contains a stack size
// for each goroutine.
err = modifyStackSizes(executable, stackSizeLoads, stackSizes)
if err != nil {
return fmt.Errorf("could not modify stack sizes: %w", err)
}
}
if config.Options.PrintSizes == "short" || config.Options.PrintSizes == "full" {
sizes, err := loadProgramSize(executable)
if err != nil {
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)
}
}
// Print goroutine stack sizes, as far as possible.
if config.Options.PrintStacks {
printStacks(calculatedStacks, stackSizes)
}
return nil
},
})
// Run all jobs to compile and link the program.
// Do this now (instead of after elf-to-hex and similar conversions) as it
// is simpler and cannot be parallelized.
err = runJobs(jobs)
if err != nil {
return err
}
// Get an Intel .hex file or .bin file from the .elf file.
outputBinaryFormat := config.BinaryFormat(outext)
switch outputBinaryFormat {
case "elf":
// do nothing, file is already in ELF format
case "hex", "bin":
// Extract raw binary, either encoding it as a hex file or as a raw
// firmware file.
tmppath = filepath.Join(dir, "main"+outext)
err := objcopy(executable, tmppath, outputBinaryFormat)
if err != nil {
return err
}
case "uf2":
// Get UF2 from the .elf file.
tmppath = filepath.Join(dir, "main"+outext)
err := convertELFFileToUF2File(executable, tmppath, config.Target.UF2FamilyID)
if err != nil {
return err
}
case "esp32", "esp8266":
// Special format for the ESP family of chips (parsed by the ROM
// bootloader).
tmppath = filepath.Join(dir, "main"+outext)
err := makeESPFirmareImage(executable, tmppath, outputBinaryFormat)
if err != nil {
return err
}
default:
return fmt.Errorf("unknown output binary format: %s", outputBinaryFormat)
}
return action(BuildResult{
Binary: tmppath,
MainDir: lprogram.MainPkg().Dir,
})
}
// optimizeProgram runs a series of optimizations and transformations that are
// needed to convert a program to its final form. Some transformations are not
// optional and must be run as the compiler expects them to run.
func optimizeProgram(mod llvm.Module, config *compileopts.Config) error {
err := interp.Run(mod, config.DumpSSA())
if err != nil {
return err
}
if config.VerifyIR() {
// Only verify if we really need it.
// The IR has already been verified before writing the bitcode to disk
// and the interp function above doesn't need to do a lot as most of the
// package initializers have already run. Additionally, verifying this
// linked IR is _expensive_ because dead code hasn't been removed yet,
// easily costing a few hundred milliseconds. Therefore, only do it when
// specifically requested.
if err := llvm.VerifyModule(mod, llvm.PrintMessageAction); err != nil {
return errors.New("verification error after interpreting runtime.initAll")
}
} }
if config.GOOS() != "darwin" { if config.GOOS() != "darwin" {
transform.ApplyFunctionSections(mod) // -ffunction-sections transform.ApplyFunctionSections(mod) // -ffunction-sections
} }
// Insert values from -ldflags="-X ..." into the IR.
err = setGlobalValues(mod, config.Options.GlobalValues)
if err != nil {
return err
}
// Browsers cannot handle external functions that have type i64 because it // Browsers cannot handle external functions that have type i64 because it
// cannot be represented exactly in JavaScript (JS only has doubles). To // cannot be represented exactly in JavaScript (JS only has doubles). To
// keep functions interoperable, pass int64 types as pointers to // keep functions interoperable, pass int64 types as pointers to
@@ -693,8 +88,28 @@ func optimizeProgram(mod llvm.Module, config *compileopts.Config) error {
// Optimization levels here are roughly the same as Clang, but probably not // Optimization levels here are roughly the same as Clang, but probably not
// exactly. // exactly.
optLevel, sizeLevel, inlinerThreshold := config.OptLevels() errs = nil
errs := transform.Optimize(mod, config, optLevel, sizeLevel, inlinerThreshold) switch config.Options.Opt {
/*
Currently, turning optimizations off causes compile failures.
We rely on the optimizer removing some dead symbols.
Avoid providing an option that does not work right now.
In the future once everything has been fixed we can re-enable this.
case "none", "0":
errs = transform.Optimize(mod, config, 0, 0, 0) // -O0
*/
case "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 { if len(errs) > 0 {
return newMultiError(errs) return newMultiError(errs)
} }
@@ -713,72 +128,185 @@ func optimizeProgram(mod llvm.Module, config *compileopts.Config) error {
transform.DisableTailCalls(mod) transform.DisableTailCalls(mod)
} }
return nil // Make sure stack sizes are loaded from a separate section so they can be
} // modified after linking.
var stackSizeLoads []string
// setGlobalValues sets the global values from the -ldflags="-X ..." compiler if config.AutomaticStackSize() {
// option in the given module. An error may be returned if the global is not of stackSizeLoads = transform.CreateStackSizeLoads(mod, config)
// the expected type.
func setGlobalValues(mod llvm.Module, globals map[string]map[string]string) error {
var pkgPaths []string
for pkgPath := range globals {
pkgPaths = append(pkgPaths, pkgPath)
} }
sort.Strings(pkgPaths)
for _, pkgPath := range pkgPaths { // Generate output.
pkg := globals[pkgPath] outext := filepath.Ext(outpath)
var names []string switch outext {
for name := range pkg { case ".o":
names = append(names, name) llvmBuf, err := machine.EmitToMemoryBuffer(mod, llvm.ObjectFile)
if err != nil {
return err
} }
sort.Strings(names) return ioutil.WriteFile(outpath, llvmBuf.Bytes(), 0666)
for _, name := range names { case ".bc":
value := pkg[name] data := llvm.WriteBitcodeToMemoryBuffer(mod).Bytes()
globalName := pkgPath + "." + name return ioutil.WriteFile(outpath, data, 0666)
global := mod.NamedGlobal(globalName) case ".ll":
if global.IsNil() || !global.Initializer().IsNil() { data := []byte(mod.String())
// The global either does not exist (optimized away?) or has return ioutil.WriteFile(outpath, data, 0666)
// some value, in which case it has already been initialized at default:
// package init time. // Act as a compiler driver.
continue
}
// A strin is a {ptr, len} pair. We need these types to build the // Create a temporary directory for intermediary files.
// initializer. dir, err := ioutil.TempDir("", "tinygo")
initializerType := global.Type().ElementType() if err != nil {
if initializerType.TypeKind() != llvm.StructTypeKind || initializerType.StructName() == "" { return err
return fmt.Errorf("%s: not a string", globalName)
}
elementTypes := initializerType.StructElementTypes()
if len(elementTypes) != 2 {
return fmt.Errorf("%s: not a string", globalName)
}
// Create a buffer for the string contents.
bufInitializer := mod.Context().ConstString(value, false)
buf := llvm.AddGlobal(mod, bufInitializer.Type(), ".string")
buf.SetInitializer(bufInitializer)
buf.SetAlignment(1)
buf.SetUnnamedAddr(true)
buf.SetLinkage(llvm.PrivateLinkage)
// Create the string value, which is a {ptr, len} pair.
zero := llvm.ConstInt(mod.Context().Int32Type(), 0, false)
ptr := llvm.ConstGEP(buf, []llvm.Value{zero, zero})
if ptr.Type() != elementTypes[0] {
return fmt.Errorf("%s: not a string", globalName)
}
length := llvm.ConstInt(elementTypes[1], uint64(len(value)), false)
initializer := llvm.ConstNamedStruct(initializerType, []llvm.Value{
ptr,
length,
})
// Set the initializer. No initializer should be set at this point.
global.SetInitializer(initializer)
} }
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 buildOutput.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)
}
if len(buildOutput.ExtraLDFlags) > 0 {
ldflags = append(ldflags, buildOutput.ExtraLDFlags...)
}
// Link the object files together.
err = link(config.Target.Linker, ldflags...)
if err != nil {
return &commandError{"failed to link", executable, err}
}
var calculatedStacks []string
var stackSizes map[string]functionStackSize
if config.Options.PrintStacks || config.AutomaticStackSize() {
// Try to determine stack sizes at compile time.
// Don't do this by default as it usually doesn't work on
// unsupported architectures.
calculatedStacks, stackSizes, err = determineStackSizes(mod, executable)
if err != nil {
return err
}
}
if config.AutomaticStackSize() {
// Modify the .tinygo_stacksizes section that contains a stack size
// for each goroutine.
err = modifyStackSizes(executable, stackSizeLoads, stackSizes)
if err != nil {
return fmt.Errorf("could not modify stack sizes: %w", err)
}
}
if config.Options.PrintSizes == "short" || config.Options.PrintSizes == "full" {
sizes, err := loadProgramSize(executable)
if err != nil {
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)
}
}
// Print goroutine stack sizes, as far as possible.
if config.Options.PrintStacks {
printStacks(calculatedStacks, stackSizes)
}
// Get an Intel .hex file or .bin file from the .elf file.
outputBinaryFormat := config.BinaryFormat(outext)
switch outputBinaryFormat {
case "elf":
// do nothing, file is already in ELF format
case "hex", "bin":
// Extract raw binary, either encoding it as a hex file or as a raw
// firmware file.
tmppath = filepath.Join(dir, "main"+outext)
err := objcopy(executable, tmppath, outputBinaryFormat)
if err != nil {
return err
}
case "uf2":
// Get UF2 from the .elf file.
tmppath = filepath.Join(dir, "main"+outext)
err := convertELFFileToUF2File(executable, tmppath, config.Target.UF2FamilyID)
if err != nil {
return err
}
case "esp32", "esp8266":
// Special format for the ESP family of chips (parsed by the ROM
// bootloader).
tmppath = filepath.Join(dir, "main"+outext)
err := makeESPFirmareImage(executable, tmppath, outputBinaryFormat)
if err != nil {
return err
}
default:
return fmt.Errorf("unknown output binary format: %s", outputBinaryFormat)
}
return action(BuildResult{
Binary: tmppath,
MainDir: buildOutput.MainDir,
})
} }
return nil
} }
// functionStackSizes keeps stack size information about a single function // functionStackSizes keeps stack size information about a single function
+19 -6
View File
@@ -30,7 +30,10 @@ func cacheTimestamp(paths []string) (time.Time, error) {
// Try to load a given file from the cache. Return "", nil if no cached file can // Try to load a given file from the cache. Return "", nil if no cached file can
// be found (or the file is stale), return the absolute path if there is a cache // be found (or the file is stale), return the absolute path if there is a cache
// and return an error on I/O errors. // and return an error on I/O errors.
func cacheLoad(name string, sourceFiles []string) (string, 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) cachepath := filepath.Join(goenv.Get("GOCACHE"), name)
cacheStat, err := os.Stat(cachepath) cacheStat, err := os.Stat(cachepath)
if os.IsNotExist(err) { if os.IsNotExist(err) {
@@ -55,7 +58,9 @@ func cacheLoad(name string, sourceFiles []string) (string, error) {
// Store the file located at tmppath in the cache with the given name. The // Store the file located at tmppath in the cache with the given name. The
// tmppath may or may not be gone afterwards. // tmppath may or may not be gone afterwards.
func cacheStore(tmppath, name string, sourceFiles []string) (string, error) { //
// Note: the configKey is ignored, see cacheLoad.
func cacheStore(tmppath, name, configKey string, sourceFiles []string) (string, error) {
// get the last modified time // get the last modified time
if len(sourceFiles) == 0 { if len(sourceFiles) == 0 {
panic("cache: no source files") panic("cache: no source files")
@@ -69,16 +74,24 @@ func cacheStore(tmppath, name string, sourceFiles []string) (string, error) {
return "", err return "", err
} }
cachepath := filepath.Join(dir, name) cachepath := filepath.Join(dir, name)
err = copyFile(tmppath, cachepath) err = moveFile(tmppath, cachepath)
if err != nil { if err != nil {
return "", err return "", err
} }
return cachepath, nil return cachepath, nil
} }
// copyFile copies the given file from src to dst. It can copy over // moveFile renames the file from src to dst. If renaming doesn't work (for
// a possibly already existing file at the destination. // example, the rename crosses a filesystem boundary), the file is copied and
func copyFile(src, dst string) error { // the old file is removed.
func moveFile(src, dst string) error {
err := os.Rename(src, dst)
if err == nil {
// Success!
return nil
}
// Failed to move, probably a different filesystem.
// Do a copy + remove.
inf, err := os.Open(src) inf, err := os.Open(src)
if err != nil { if err != nil {
return err return err
-309
View File
@@ -1,309 +0,0 @@
package builder
// This file implements a wrapper around the C compiler (Clang) which uses a
// build cache.
import (
"crypto/sha512"
"encoding/hex"
"encoding/json"
"errors"
"fmt"
"io"
"io/ioutil"
"os"
"path/filepath"
"sort"
"strings"
"unicode"
"github.com/tinygo-org/tinygo/goenv"
"tinygo.org/x/go-llvm"
)
// compileAndCacheCFile compiles a C or assembly file using a build cache.
// Compiling the same file again (if nothing changed, including included header
// files) the output is loaded from the build cache instead.
//
// Its operation is a bit complex (more complex than Go package build caching)
// because the list of file dependencies is only known after the file is
// compiled. However, luckily compilers have a flag to write a list of file
// dependencies in Makefile syntax which can be used for caching.
//
// Because of this complexity, every file has in fact two cached build outputs:
// the file itself, and the list of dependencies. Its operation is as follows:
//
// depfile = hash(path, compiler, cflags, ...)
// if depfile exists:
// outfile = hash of all files and depfile name
// if outfile exists:
// # cache hit
// return outfile
// # cache miss
// tmpfile = compile file
// read dependencies (side effect of compile)
// write depfile
// outfile = hash of all files and depfile name
// rename tmpfile to outfile
//
// There are a few edge cases that are not handled:
// - If a file is added to an include path, that file may be included instead of
// some other file. This would be fixed by also including lookup failures in the
// dependencies file, but I'm not aware of a compiler which does that.
// - The Makefile syntax that compilers output has issues, see readDepFile for
// details.
// - A header file may be changed to add/remove an include. This invalidates the
// depfile but without invalidating its name. For this reason, the depfile is
// written on each new compilation (even when it seems unnecessary). However, it
// could in rare cases lead to a stale file fetched from the cache.
func compileAndCacheCFile(abspath, tmpdir string, cflags []string, printCommands bool) (string, error) {
// Hash input file.
fileHash, err := hashFile(abspath)
if err != nil {
return "", err
}
// Create cache key for the dependencies file.
buf, err := json.Marshal(struct {
Path string
Hash string
Flags []string
LLVMVersion string
}{
Path: abspath,
Hash: fileHash,
Flags: cflags,
LLVMVersion: llvm.Version,
})
if err != nil {
panic(err) // shouldn't happen
}
depfileNameHashBuf := sha512.Sum512_224(buf)
depfileNameHash := hex.EncodeToString(depfileNameHashBuf[:])
// Load dependencies file, if possible.
depfileName := "dep-" + depfileNameHash + ".json"
depfileCachePath := filepath.Join(goenv.Get("GOCACHE"), depfileName)
depfileBuf, err := ioutil.ReadFile(depfileCachePath)
var dependencies []string // sorted list of dependency paths
if err == nil {
// There is a dependency file, that's great!
// Parse it first.
err := json.Unmarshal(depfileBuf, &dependencies)
if err != nil {
return "", fmt.Errorf("could not parse dependencies JSON: %w", err)
}
// Obtain hashes of all the files listed as a dependency.
outpath, err := makeCFileCachePath(dependencies, depfileNameHash)
if err == nil {
if _, err := os.Stat(outpath); err == nil {
return outpath, nil
} else if !os.IsNotExist(err) {
return "", err
}
}
} else if !os.IsNotExist(err) {
// expected either nil or IsNotExist
return "", err
}
objTmpFile, err := ioutil.TempFile(goenv.Get("GOCACHE"), "tmp-*.o")
if err != nil {
return "", err
}
objTmpFile.Close()
depTmpFile, err := ioutil.TempFile(tmpdir, "dep-*.d")
if err != nil {
return "", err
}
depTmpFile.Close()
flags := append([]string{}, cflags...) // copy cflags
flags = append(flags, "-MD", "-MV", "-MTdeps", "-MF", depTmpFile.Name()) // autogenerate dependencies
flags = append(flags, "-c", "-o", objTmpFile.Name(), abspath)
if strings.ToLower(filepath.Ext(abspath)) == ".s" {
// If this is an assembly file (.s or .S, lowercase or uppercase), then
// we'll need to add -Qunused-arguments because many parameters are
// relevant to C, not assembly. And with -Werror, having meaningless
// flags (for the assembler) is a compiler error.
flags = append(flags, "-Qunused-arguments")
}
if printCommands {
fmt.Printf("clang %s\n", strings.Join(flags, " "))
}
err = runCCompiler(flags...)
if err != nil {
return "", &commandError{"failed to build", abspath, err}
}
// Create sorted and uniqued slice of dependencies.
dependencyPaths, err := readDepFile(depTmpFile.Name())
if err != nil {
return "", err
}
dependencyPaths = append(dependencyPaths, abspath) // necessary for .s files
dependencySet := make(map[string]struct{}, len(dependencyPaths))
var dependencySlice []string
for _, path := range dependencyPaths {
if _, ok := dependencySet[path]; ok {
continue
}
dependencySet[path] = struct{}{}
dependencySlice = append(dependencySlice, path)
}
sort.Strings(dependencySlice)
// Write dependencies file.
f, err := ioutil.TempFile(filepath.Dir(depfileCachePath), depfileName)
buf, err = json.MarshalIndent(dependencySlice, "", "\t")
if err != nil {
panic(err) // shouldn't happen
}
_, err = f.Write(buf)
if err != nil {
return "", err
}
err = f.Close()
if err != nil {
return "", err
}
err = os.Rename(f.Name(), depfileCachePath)
if err != nil {
return "", err
}
// Move temporary object file to final location.
outpath, err := makeCFileCachePath(dependencySlice, depfileNameHash)
if err != nil {
return "", err
}
err = os.Rename(objTmpFile.Name(), outpath)
if err != nil {
return "", err
}
return outpath, nil
}
// Create a cache path (a path in GOCACHE) to store the output of a compiler
// job. This path is based on the dep file name (which is a hash of metadata
// including compiler flags) and the hash of all input files in the paths slice.
func makeCFileCachePath(paths []string, depfileNameHash string) (string, error) {
// Hash all input files.
fileHashes := make(map[string]string, len(paths))
for _, path := range paths {
hash, err := hashFile(path)
if err != nil {
return "", err
}
fileHashes[path] = hash
}
// Calculate a cache key based on the above hashes.
buf, err := json.Marshal(struct {
DepfileHash string
FileHashes map[string]string
}{
DepfileHash: depfileNameHash,
FileHashes: fileHashes,
})
if err != nil {
panic(err) // shouldn't happen
}
outFileNameBuf := sha512.Sum512_224(buf)
cacheKey := hex.EncodeToString(outFileNameBuf[:])
outpath := filepath.Join(goenv.Get("GOCACHE"), "obj-"+cacheKey+".o")
return outpath, nil
}
// hashFile hashes the given file path and returns the hash as a hex string.
func hashFile(path string) (string, error) {
f, err := os.Open(path)
if err != nil {
return "", fmt.Errorf("failed to hash file: %w", err)
}
defer f.Close()
fileHasher := sha512.New512_224()
_, err = io.Copy(fileHasher, f)
if err != nil {
return "", fmt.Errorf("failed to hash file: %w", err)
}
return hex.EncodeToString(fileHasher.Sum(nil)), nil
}
// readDepFile reads a dependency file in NMake (Visual Studio make) format. The
// file is assumed to have a single target named deps.
//
// There are roughly three make syntax variants:
// - BSD make, which doesn't support any escaping. This means that many special
// characters are not supported in file names.
// - GNU make, which supports escaping using a backslash but when it fails to
// find a file it tries to fall back with the literal path name (to match BSD
// make).
// - NMake (Visual Studio) and Jom, which simply quote the string if there are
// any weird characters.
// Clang supports two variants: a format that's a compromise between BSD and GNU
// make (and is buggy to match GCC which is equally buggy), and NMake/Jom, which
// is at least somewhat sane. This last format isn't perfect either: it does not
// correctly handle filenames with quote marks in them. Those are generally not
// allowed on Windows, but of course can be used on POSIX like systems. Still,
// it's the most sane of any of the formats so readDepFile will use that format.
func readDepFile(filename string) ([]string, error) {
buf, err := ioutil.ReadFile(filename)
if err != nil {
return nil, err
}
if len(buf) == 0 {
return nil, nil
}
return parseDepFile(string(buf))
}
func parseDepFile(s string) ([]string, error) {
// This function makes no attempt at parsing anything other than Clang -MD
// -MV output.
// For Windows: replace CRLF with LF to make the logic below simpler.
s = strings.ReplaceAll(s, "\r\n", "\n")
// Collapse all lines ending in a backslash. These backslashes are really
// just a way to continue a line without making very long lines.
s = strings.ReplaceAll(s, "\\\n", " ")
// Only use the first line, which is expected to begin with "deps:".
line := strings.SplitN(s, "\n", 2)[0]
if !strings.HasPrefix(line, "deps:") {
return nil, errors.New("readDepFile: expected 'deps:' prefix")
}
line = strings.TrimSpace(line[len("deps:"):])
var deps []string
for line != "" {
if line[0] == '"' {
// File path is quoted. Path ends with double quote.
// This does not handle double quotes in path names, which is a
// problem on non-Windows systems.
line = line[1:]
end := strings.IndexByte(line, '"')
if end < 0 {
return nil, errors.New("readDepFile: path is incorrectly quoted")
}
dep := line[:end]
line = strings.TrimSpace(line[end+1:])
deps = append(deps, dep)
} else {
// File path is not quoted. Path ends in space or EOL.
end := strings.IndexFunc(line, unicode.IsSpace)
if end < 0 {
// last dependency
deps = append(deps, line)
break
}
dep := line[:end]
line = strings.TrimSpace(line[end:])
deps = append(deps, dep)
}
}
return deps, nil
}
-33
View File
@@ -1,33 +0,0 @@
package builder
import (
"reflect"
"testing"
)
func TestSplitDepFile(t *testing.T) {
for i, tc := range []struct {
in string
out []string
}{
{`deps: foo bar`, []string{"foo", "bar"}},
{`deps: foo "bar"`, []string{"foo", "bar"}},
{`deps: "foo" bar`, []string{"foo", "bar"}},
{`deps: "foo bar"`, []string{"foo bar"}},
{`deps: "foo bar" `, []string{"foo bar"}},
{"deps: foo\nbar", []string{"foo"}},
{"deps: foo \\\nbar", []string{"foo", "bar"}},
{"deps: foo\\bar \\\nbaz", []string{"foo\\bar", "baz"}},
{"deps: foo\\bar \\\r\n baz", []string{"foo\\bar", "baz"}}, // Windows uses CRLF line endings
} {
out, err := parseDepFile(tc.in)
if err != nil {
t.Errorf("test #%d failed: %v", i, err)
continue
}
if !reflect.DeepEqual(out, tc.out) {
t.Errorf("test #%d failed: expected %#v but got %#v", i, tc.out, out)
continue
}
}
}
+2 -2
View File
@@ -25,8 +25,8 @@ func NewConfig(options *compileopts.Options) (*compileopts.Config, error) {
if err != nil { if err != nil {
return nil, fmt.Errorf("could not read version from GOROOT (%v): %v", goroot, err) return nil, fmt.Errorf("could not read version from GOROOT (%v): %v", goroot, err)
} }
if major != 1 || minor < 13 || minor > 16 { if major != 1 || minor < 11 || minor > 15 {
return nil, fmt.Errorf("requires go version 1.13 through 1.16, got go%d.%d", major, minor) return nil, fmt.Errorf("requires go version 1.11 through 1.15, got go%d.%d", major, minor)
} }
clangHeaderPath := getClangHeaderPath(goenv.Get("TINYGOROOT")) clangHeaderPath := getClangHeaderPath(goenv.Get("TINYGOROOT"))
return &compileopts.Config{ return &compileopts.Config{
-173
View File
@@ -1,173 +0,0 @@
package builder
// This file implements a job runner for the compiler, which runs jobs in
// parallel while taking care of dependencies.
import (
"fmt"
"runtime"
"time"
)
// Set to true to enable logging in the job runner. This may help to debug
// concurrency or performance issues.
const jobRunnerDebug = false
type jobState uint8
const (
jobStateQueued jobState = iota // not yet running
jobStateRunning // running
jobStateFinished // finished running
)
// compileJob is a single compiler job, comparable to a single Makefile target.
// It is used to orchestrate various compiler tasks that can be run in parallel
// but that have dependencies and thus have limitations in how they can be run.
type compileJob struct {
description string // description, only used for logging
dependencies []*compileJob
result string // result (path)
run func(*compileJob) (err error)
state jobState
err error // error if finished
duration time.Duration // how long it took to run this job (only set after finishing)
}
// dummyCompileJob returns a new *compileJob that produces an output without
// doing anything. This can be useful where a *compileJob producing an output is
// expected but nothing needs to be done, for example for a load from a cache.
func dummyCompileJob(result string) *compileJob {
return &compileJob{
description: "<dummy>",
result: result,
}
}
// readyToRun returns whether this job is ready to run: it is itself not yet
// started and all dependencies are finished.
func (job *compileJob) readyToRun() bool {
if job.state != jobStateQueued {
// Already running or finished, so shouldn't be run again.
return false
}
// Check dependencies.
for _, dep := range job.dependencies {
if dep.state != jobStateFinished {
// A dependency is not finished, so this job has to wait until it
// is.
return false
}
}
// All conditions are satisfied.
return true
}
// runJobs runs all the jobs indicated in the jobs slice and returns the error
// of the first job that fails to run.
// It runs all jobs in the order of the slice, as long as all dependencies have
// already run. Therefore, if some jobs are preferred to run before others, they
// should be ordered as such in this slice.
func runJobs(jobs []*compileJob) error {
// Create channels to communicate with the workers.
doneChan := make(chan *compileJob)
workerChan := make(chan *compileJob)
defer close(workerChan)
// Start a number of workers.
for i := 0; i < runtime.NumCPU(); i++ {
if jobRunnerDebug {
fmt.Println("## starting worker", i)
}
go jobWorker(workerChan, doneChan)
}
// Send each job in the jobs slice to a worker, taking care of job
// dependencies.
numRunningJobs := 0
var totalTime time.Duration
start := time.Now()
for {
// If there are free workers, try starting a new job (if one is
// available). If it succeeds, try again to fill the entire worker pool.
if numRunningJobs < runtime.NumCPU() {
jobToRun := nextJob(jobs)
if jobToRun != nil {
// Start job.
if jobRunnerDebug {
fmt.Println("## start: ", jobToRun.description)
}
jobToRun.state = jobStateRunning
workerChan <- jobToRun
numRunningJobs++
continue
}
}
// When there are no jobs running, all jobs in the jobs slice must have
// been finished. Therefore, the work is done.
if numRunningJobs == 0 {
break
}
// Wait until a job is finished.
job := <-doneChan
job.state = jobStateFinished
numRunningJobs--
totalTime += job.duration
if jobRunnerDebug {
fmt.Println("## finished:", job.description, "(time "+job.duration.String()+")")
}
if job.err != nil {
// Wait for running jobs to finish.
for numRunningJobs != 0 {
<-doneChan
numRunningJobs--
}
// Return error of first failing job.
return job.err
}
}
// Some statistics, if debugging.
if jobRunnerDebug {
// Total duration of running all jobs.
duration := time.Since(start)
fmt.Println("## total: ", duration)
// The individual time of each job combined. On a multicore system, this
// should be lower than the total above.
fmt.Println("## job sum: ", totalTime)
}
return nil
}
// nextJob returns the first ready-to-run job.
// This is an implementation detail of runJobs.
func nextJob(jobs []*compileJob) *compileJob {
for _, job := range jobs {
if job.readyToRun() {
return job
}
}
return nil
}
// jobWorker is the goroutine that runs received jobs.
// This is an implementation detail of runJobs.
func jobWorker(workerChan, doneChan chan *compileJob) {
for job := range workerChan {
start := time.Now()
if job.run != nil {
err := job.run(job)
if err != nil {
job.err = err
}
}
job.duration = time.Since(start)
doneChan <- job
}
}
+29 -73
View File
@@ -1,6 +1,7 @@
package builder package builder
import ( import (
"io/ioutil"
"os" "os"
"path/filepath" "path/filepath"
"strings" "strings"
@@ -39,62 +40,34 @@ func (l *Library) sourcePaths(target string) []string {
} }
// Load the library archive, possibly generating and caching it if needed. // Load the library archive, possibly generating and caching it if needed.
// The resulting file is stored in the provided tmpdir, which is expected to be func (l *Library) Load(target string) (path string, err error) {
// removed after the Load call.
func (l *Library) Load(target, tmpdir string) (path string, err error) {
job, err := l.load(target, "", tmpdir)
if err != nil {
return "", err
}
jobs := append([]*compileJob{job}, job.dependencies...)
err = runJobs(jobs)
return job.result, err
}
// load returns a compile job to build this library file for the given target
// and CPU. It may return a dummy compileJob if the library build is already
// cached. The path is stored as job.result but is only valid if the job and
// job.dependencies have been run.
// The provided tmpdir will be used to store intermediary files and possibly the
// output archive file, it is expected to be removed after use.
func (l *Library) load(target, cpu, tmpdir string) (job *compileJob, err error) {
// Try to load a precompiled library. // Try to load a precompiled library.
precompiledPath := filepath.Join(goenv.Get("TINYGOROOT"), "pkg", target, l.name+".a") precompiledPath := filepath.Join(goenv.Get("TINYGOROOT"), "pkg", target, l.name+".a")
if _, err := os.Stat(precompiledPath); err == nil { if _, err := os.Stat(precompiledPath); err == nil {
// Found a precompiled library for this OS/architecture. Return the path // Found a precompiled library for this OS/architecture. Return the path
// directly. // directly.
return dummyCompileJob(precompiledPath), nil return precompiledPath, nil
} }
var outfile string outfile := l.name + "-" + target + ".a"
if cpu != "" {
outfile = l.name + "-" + target + "-" + cpu + ".a"
} else {
outfile = l.name + "-" + target + ".a"
}
// Try to fetch this library from the cache. // Try to fetch this library from the cache.
if path, err := cacheLoad(outfile, l.sourcePaths(target)); path != "" || err != nil { if path, err := cacheLoad(outfile, commands["clang"][0], l.sourcePaths(target)); path != "" || err != nil {
// Cache hit. // Cache hit.
return dummyCompileJob(path), nil return path, err
} }
// Cache miss, build it now. // Cache miss, build it now.
remapDir := filepath.Join(os.TempDir(), "tinygo-"+l.name) dirPrefix := "tinygo-" + l.name
dir := filepath.Join(tmpdir, "build-lib-"+l.name) remapDir := filepath.Join(os.TempDir(), dirPrefix)
err = os.Mkdir(dir, 0777) dir, err := ioutil.TempDir(os.TempDir(), dirPrefix)
if err != nil { if err != nil {
return nil, err return "", err
} }
defer os.RemoveAll(dir)
// Precalculate the flags to the compiler invocation. // Precalculate the flags to the compiler invocation.
// 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.
args := append(l.cflags(), "-c", "-Oz", "-g", "-ffunction-sections", "-fdata-sections", "-Wno-macro-redefined", "--target="+target, "-fdebug-prefix-map="+dir+"="+remapDir) args := append(l.cflags(), "-c", "-Oz", "-g", "-ffunction-sections", "-fdata-sections", "-Wno-macro-redefined", "--target="+target, "-fdebug-prefix-map="+dir+"="+remapDir)
if cpu != "" {
args = append(args, "-mcpu="+cpu)
}
if strings.HasPrefix(target, "arm") || strings.HasPrefix(target, "thumb") { if strings.HasPrefix(target, "arm") || strings.HasPrefix(target, "thumb") {
args = append(args, "-fshort-enums", "-fomit-frame-pointer", "-mfloat-abi=soft") args = append(args, "-fshort-enums", "-fomit-frame-pointer", "-mfloat-abi=soft")
} }
@@ -105,45 +78,28 @@ func (l *Library) load(target, cpu, tmpdir string) (job *compileJob, err error)
args = append(args, "-march=rv64gc", "-mabi=lp64") args = append(args, "-march=rv64gc", "-mabi=lp64")
} }
// Create job to put all the object files in a single archive. This archive // Compile all sources.
// file is the (static) library file.
var objs []string var objs []string
arpath := filepath.Join(dir, l.name+".a")
job = &compileJob{
description: "ar " + l.name + ".a",
result: arpath,
run: func(*compileJob) error {
// Create an archive of all object files.
err := makeArchive(arpath, objs)
if err != nil {
return err
}
// Store this archive in the cache.
_, err = cacheStore(arpath, outfile, l.sourcePaths(target))
return err
},
}
// Create jobs to compile all sources. These jobs are depended upon by the
// archive job above, so must be run first.
for _, srcpath := range l.sourcePaths(target) { for _, srcpath := range l.sourcePaths(target) {
srcpath := srcpath // avoid concurrency issues by redefining inside the loop
objpath := filepath.Join(dir, filepath.Base(srcpath)+".o") objpath := filepath.Join(dir, filepath.Base(srcpath)+".o")
objs = append(objs, objpath) objs = append(objs, objpath)
job.dependencies = append(job.dependencies, &compileJob{ // Note: -fdebug-prefix-map is necessary to make the output archive
description: "compile " + srcpath, // reproducible. Otherwise the temporary directory is stored in the
run: func(*compileJob) error { // archive itself, which varies each run.
var compileArgs []string err := runCCompiler("clang", append(args, "-o", objpath, srcpath)...)
compileArgs = append(compileArgs, args...) if err != nil {
compileArgs = append(compileArgs, "-o", objpath, srcpath) return "", &commandError{"failed to build", srcpath, err}
err := runCCompiler(compileArgs...) }
if err != nil {
return &commandError{"failed to build", srcpath, err}
}
return nil
},
})
} }
return job, nil // 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))
} }
+13 -4
View File
@@ -9,8 +9,8 @@ import (
) )
// runCCompiler invokes a C compiler with the given arguments. // runCCompiler invokes a C compiler with the given arguments.
func runCCompiler(flags ...string) error { func runCCompiler(command string, flags ...string) error {
if hasBuiltinTools { if hasBuiltinTools && command == "clang" {
// Compile this with the internal Clang compiler. // Compile this with the internal Clang compiler.
headerPath := getClangHeaderPath(goenv.Get("TINYGOROOT")) headerPath := getClangHeaderPath(goenv.Get("TINYGOROOT"))
if headerPath == "" { if headerPath == "" {
@@ -23,8 +23,17 @@ func runCCompiler(flags ...string) error {
return cmd.Run() return cmd.Run()
} }
// Compile this with an external invocation of the Clang compiler. // Running some other compiler. Maybe it has been defined in the
return execCommand(commands["clang"], flags...) // commands map (unlikely).
if cmdNames, ok := commands[command]; ok {
return execCommand(cmdNames, flags...)
}
// Alternatively, run the compiler directly.
cmd := exec.Command(command, flags...)
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
return cmd.Run()
} }
// link invokes a linker with the given name and flags. // link invokes a linker with the given name and flags.
+16 -33
View File
@@ -41,9 +41,7 @@ type cgoPackage struct {
elaboratedTypes map[string]*elaboratedTypeInfo elaboratedTypes map[string]*elaboratedTypeInfo
enums map[string]enumInfo enums map[string]enumInfo
anonStructNum int anonStructNum int
cflags []string // CFlags from #cgo lines ldflags []string
ldflags []string // LDFlags from #cgo lines
visitedFiles map[string][]byte
} }
// constantInfo stores some information about a CGo constant found by libclang // constantInfo stores some information about a CGo constant found by libclang
@@ -56,10 +54,9 @@ type constantInfo struct {
// functionInfo stores some information about a CGo function found by libclang // functionInfo stores some information about a CGo function found by libclang
// and declared in the AST. // and declared in the AST.
type functionInfo struct { type functionInfo struct {
args []paramInfo args []paramInfo
results *ast.FieldList results *ast.FieldList
pos token.Pos pos token.Pos
variadic bool
} }
// paramInfo is a parameter of a CGo function (see functionInfo). // paramInfo is a parameter of a CGo function (see functionInfo).
@@ -158,10 +155,9 @@ typedef unsigned long long _Cgo_ulonglong;
// Process extracts `import "C"` statements from the AST, parses the comment // Process extracts `import "C"` statements from the AST, parses the comment
// with libclang, and modifies the AST to use this information. It returns a // with libclang, and modifies the AST to use this information. It returns a
// newly created *ast.File that should be added to the list of to-be-parsed // newly created *ast.File that should be added to the list of to-be-parsed
// files, the CFLAGS and LDFLAGS found in #cgo lines, and a map of file hashes // files. If there is one or more error, it returns these in the []error slice
// of the accessed C header files. If there is one or more error, it returns // but still modifies the AST.
// these in the []error slice but still modifies the AST. func Process(files []*ast.File, dir string, fset *token.FileSet, cflags []string) (*ast.File, []string, []error) {
func Process(files []*ast.File, dir string, fset *token.FileSet, cflags []string) (*ast.File, []string, []string, map[string][]byte, []error) {
p := &cgoPackage{ p := &cgoPackage{
dir: dir, dir: dir,
fset: fset, fset: fset,
@@ -173,9 +169,13 @@ func Process(files []*ast.File, dir string, fset *token.FileSet, cflags []string
typedefs: map[string]*typedefInfo{}, typedefs: map[string]*typedefInfo{},
elaboratedTypes: map[string]*elaboratedTypeInfo{}, elaboratedTypes: map[string]*elaboratedTypeInfo{},
enums: map[string]enumInfo{}, enums: map[string]enumInfo{},
visitedFiles: map[string][]byte{},
} }
// Disable _FORTIFY_SOURCE as it causes problems on macOS.
// Note that it is only disabled for memcpy (etc) calls made from Go, which
// have better alternatives anyway.
cflags = append(cflags, "-D_FORTIFY_SOURCE=0")
// Add a new location for the following file. // Add a new location for the following file.
generatedTokenPos := p.fset.AddFile(dir+"/!cgo.go", -1, 0) generatedTokenPos := p.fset.AddFile(dir+"/!cgo.go", -1, 0)
generatedTokenPos.SetLines([]int{0}) generatedTokenPos.SetLines([]int{0})
@@ -184,7 +184,7 @@ func Process(files []*ast.File, dir string, fset *token.FileSet, cflags []string
// Find the absolute path for this package. // Find the absolute path for this package.
packagePath, err := filepath.Abs(fset.File(files[0].Pos()).Name()) packagePath, err := filepath.Abs(fset.File(files[0].Pos()).Name())
if err != nil { if err != nil {
return nil, nil, nil, nil, []error{ return nil, nil, []error{
scanner.Error{ scanner.Error{
Pos: fset.Position(files[0].Pos()), Pos: fset.Position(files[0].Pos()),
Msg: "cgo: cannot find absolute path: " + err.Error(), // TODO: wrap this error Msg: "cgo: cannot find absolute path: " + err.Error(), // TODO: wrap this error
@@ -359,7 +359,7 @@ func Process(files []*ast.File, dir string, fset *token.FileSet, cflags []string
continue continue
} }
makePathsAbsolute(flags, packagePath) makePathsAbsolute(flags, packagePath)
p.cflags = append(p.cflags, flags...) cflags = append(cflags, flags...)
case "LDFLAGS": case "LDFLAGS":
flags, err := shlex.Split(value) flags, err := shlex.Split(value)
if err != nil { if err != nil {
@@ -382,13 +382,6 @@ func Process(files []*ast.File, dir string, fset *token.FileSet, cflags []string
} }
} }
// Define CFlags that will be used while parsing the package.
// Disable _FORTIFY_SOURCE as it causes problems on macOS.
// Note that it is only disabled for memcpy (etc) calls made from Go, which
// have better alternatives anyway.
cflagsForCGo := append([]string{"-D_FORTIFY_SOURCE=0"}, cflags...)
cflagsForCGo = append(cflagsForCGo, p.cflags...)
// Process all CGo imports. // Process all CGo imports.
for _, genDecl := range statements { for _, genDecl := range statements {
cgoComment := genDecl.Doc.Text() cgoComment := genDecl.Doc.Text()
@@ -398,7 +391,7 @@ func Process(files []*ast.File, dir string, fset *token.FileSet, cflags []string
pos = genDecl.Doc.Pos() pos = genDecl.Doc.Pos()
} }
position := fset.PositionFor(pos, true) position := fset.PositionFor(pos, true)
p.parseFragment(cgoComment+cgoTypes, cflagsForCGo, position.Filename, position.Line) p.parseFragment(cgoComment+cgoTypes, cflags, position.Filename, position.Line)
} }
// Declare functions found by libclang. // Declare functions found by libclang.
@@ -433,7 +426,7 @@ func Process(files []*ast.File, dir string, fset *token.FileSet, cflags []string
// Print the newly generated in-memory AST, for debugging. // Print the newly generated in-memory AST, for debugging.
//ast.Print(fset, p.generated) //ast.Print(fset, p.generated)
return p.generated, p.cflags, p.ldflags, p.visitedFiles, p.errors return p.generated, p.ldflags, p.errors
} }
// makePathsAbsolute converts some common path compiler flags (-I, -L) from // makePathsAbsolute converts some common path compiler flags (-I, -L) from
@@ -491,16 +484,6 @@ func (p *cgoPackage) addFuncDecls() {
Results: fn.results, Results: fn.results,
}, },
} }
if fn.variadic {
decl.Doc = &ast.CommentGroup{
List: []*ast.Comment{
&ast.Comment{
Slash: fn.pos,
Text: "//go:variadic",
},
},
}
}
obj.Decl = decl obj.Decl = decl
for i, arg := range fn.args { for i, arg := range fn.args {
args[i] = &ast.Field{ args[i] = &ast.Field{
+4 -19
View File
@@ -5,7 +5,6 @@ import (
"flag" "flag"
"fmt" "fmt"
"go/ast" "go/ast"
"go/build"
"go/format" "go/format"
"go/parser" "go/parser"
"go/token" "go/token"
@@ -24,7 +23,7 @@ var flagUpdate = flag.Bool("update", false, "Update images based on test output.
// normalizeResult normalizes Go source code that comes out of tests across // normalizeResult normalizes Go source code that comes out of tests across
// platforms and Go versions. // platforms and Go versions.
func normalizeResult(result string) string { func normalizeResult(result string) string {
actual := strings.ReplaceAll(result, "\r\n", "\n") actual := strings.Replace(result, "\r\n", "\n", -1)
// Make sure all functions are wrapped, even those that would otherwise be // 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 // single-line functions. This is necessary because Go 1.14 changed the way
@@ -42,20 +41,6 @@ func TestCGo(t *testing.T) {
for _, name := range []string{"basic", "errors", "types", "flags", "const"} { for _, name := range []string{"basic", "errors", "types", "flags", "const"} {
name := name // avoid a race condition name := name // avoid a race condition
t.Run(name, func(t *testing.T) { t.Run(name, func(t *testing.T) {
// Skip tests that require specific Go version.
if name == "errors" {
ok := false
for _, version := range build.Default.ReleaseTags {
if version == "go1.16" {
ok = true
break
}
}
if !ok {
t.Skip("Results for errors test are only valid for Go 1.16+")
}
}
// Read the AST in memory. // Read the AST in memory.
path := filepath.Join("testdata", name+".go") path := filepath.Join("testdata", name+".go")
fset := token.NewFileSet() fset := token.NewFileSet()
@@ -65,7 +50,7 @@ func TestCGo(t *testing.T) {
} }
// Process the AST with CGo. // Process the AST with CGo.
cgoAST, _, _, _, cgoErrors := Process([]*ast.File{f}, "testdata", fset, cflags) cgoAST, _, cgoErrors := Process([]*ast.File{f}, "testdata", fset, cflags)
// Check the AST for type errors. // Check the AST for type errors.
var typecheckErrors []error var typecheckErrors []error
@@ -113,7 +98,7 @@ func TestCGo(t *testing.T) {
if err != nil { if err != nil {
t.Fatalf("could not read expected output: %v", err) t.Fatalf("could not read expected output: %v", err)
} }
expected := strings.ReplaceAll(string(expectedBytes), "\r\n", "\n") expected := strings.Replace(string(expectedBytes), "\r\n", "\n", -1)
// Check whether the output is as expected. // Check whether the output is as expected.
if expected != actual { if expected != actual {
@@ -154,7 +139,7 @@ func formatDiagnostic(err error) string {
msg := err.Error() msg := err.Error()
if runtime.GOOS == "windows" { if runtime.GOOS == "windows" {
// Fix Windows path slashes. // Fix Windows path slashes.
msg = strings.ReplaceAll(msg, "testdata\\", "testdata/") msg = strings.Replace(msg, "testdata\\", "testdata/", -1)
} }
return "// " + msg + "\n" return "// " + msg + "\n"
} }
+4 -37
View File
@@ -4,7 +4,6 @@ package cgo
// modification. It does not touch the AST itself. // modification. It does not touch the AST itself.
import ( import (
"crypto/sha512"
"fmt" "fmt"
"go/ast" "go/ast"
"go/scanner" "go/scanner"
@@ -57,7 +56,6 @@ unsigned tinygo_clang_Cursor_isBitField(GoCXCursor c);
int tinygo_clang_globals_visitor(GoCXCursor c, GoCXCursor parent, CXClientData client_data); 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_struct_visitor(GoCXCursor c, GoCXCursor parent, CXClientData client_data);
int tinygo_clang_enum_visitor(GoCXCursor c, GoCXCursor parent, CXClientData client_data); int tinygo_clang_enum_visitor(GoCXCursor c, GoCXCursor parent, CXClientData client_data);
void tinygo_clang_inclusion_visitor(CXFile included_file, CXSourceLocation *inclusion_stack, unsigned include_len, CXClientData client_data);
*/ */
import "C" import "C"
@@ -116,7 +114,6 @@ func (p *cgoPackage) parseFragment(fragment string, cflags []string, posFilename
} }
defer C.clang_disposeTranslationUnit(unit) defer C.clang_disposeTranslationUnit(unit)
// Report parser and type errors.
if numDiagnostics := int(C.clang_getNumDiagnostics(unit)); numDiagnostics != 0 { if numDiagnostics := int(C.clang_getNumDiagnostics(unit)); numDiagnostics != 0 {
addDiagnostic := func(diagnostic C.CXDiagnostic) { addDiagnostic := func(diagnostic C.CXDiagnostic) {
spelling := getString(C.clang_getDiagnosticSpelling(diagnostic)) spelling := getString(C.clang_getDiagnosticSpelling(diagnostic))
@@ -137,36 +134,10 @@ func (p *cgoPackage) parseFragment(fragment string, cflags []string, posFilename
} }
} }
// Extract information required by CGo.
ref := storedRefs.Put(p) ref := storedRefs.Put(p)
defer storedRefs.Remove(ref) defer storedRefs.Remove(ref)
cursor := C.tinygo_clang_getTranslationUnitCursor(unit) cursor := C.tinygo_clang_getTranslationUnitCursor(unit)
C.tinygo_clang_visitChildren(cursor, C.CXCursorVisitor(C.tinygo_clang_globals_visitor), C.CXClientData(ref)) C.tinygo_clang_visitChildren(cursor, C.CXCursorVisitor(C.tinygo_clang_globals_visitor), C.CXClientData(ref))
// Determine files read during CGo processing, for caching.
inclusionCallback := func(includedFile C.CXFile) {
// Get full file path.
path := getString(C.clang_getFileName(includedFile))
// Get contents of file (that should be in-memory).
size := C.size_t(0)
rawData := C.clang_getFileContents(unit, includedFile, &size)
if rawData == nil {
// Sanity check. This should (hopefully) never trigger.
panic("libclang: file contents was not loaded")
}
data := (*[1 << 24]byte)(unsafe.Pointer(rawData))[:size]
// Hash the contents if it isn't hashed yet.
if _, ok := p.visitedFiles[path]; !ok {
// already stored
sum := sha512.Sum512_224(data)
p.visitedFiles[path] = sum[:]
}
}
inclusionCallbackRef := storedRefs.Put(inclusionCallback)
defer storedRefs.Remove(inclusionCallbackRef)
C.clang_getInclusions(unit, C.CXInclusionVisitor(C.tinygo_clang_inclusion_visitor), C.CXClientData(inclusionCallbackRef))
} }
//export tinygo_clang_globals_visitor //export tinygo_clang_globals_visitor
@@ -181,10 +152,12 @@ func tinygo_clang_globals_visitor(c, parent C.GoCXCursor, client_data C.CXClient
return C.CXChildVisit_Continue return C.CXChildVisit_Continue
} }
cursorType := C.tinygo_clang_getCursorType(c) cursorType := C.tinygo_clang_getCursorType(c)
if C.clang_isFunctionTypeVariadic(cursorType) != 0 {
return C.CXChildVisit_Continue // not supported
}
numArgs := int(C.tinygo_clang_Cursor_getNumArguments(c)) numArgs := int(C.tinygo_clang_Cursor_getNumArguments(c))
fn := &functionInfo{ fn := &functionInfo{
pos: pos, pos: pos,
variadic: C.clang_isFunctionTypeVariadic(cursorType) != 0,
} }
p.functions[name] = fn p.functions[name] = fn
for i := 0; i < numArgs; i++ { for i := 0; i < numArgs; i++ {
@@ -801,9 +774,3 @@ func tinygo_clang_enum_visitor(c, parent C.GoCXCursor, client_data C.CXClientDat
} }
return C.CXChildVisit_Continue return C.CXChildVisit_Continue
} }
//export tinygo_clang_inclusion_visitor
func tinygo_clang_inclusion_visitor(includedFile C.CXFile, inclusionStack *C.CXSourceLocation, includeLen C.unsigned, clientData C.CXClientData) {
callback := storedRefs.Get(unsafe.Pointer(clientData)).(func(C.CXFile))
callback(includedFile)
}
+7 -7
View File
@@ -1,14 +1,14 @@
// +build !byollvm // +build !byollvm
// +build !llvm10 // +build !llvm9,!llvm11
package cgo package cgo
/* /*
#cgo linux CFLAGS: -I/usr/lib/llvm-11/include #cgo linux CFLAGS: -I/usr/lib/llvm-10/include
#cgo darwin CFLAGS: -I/usr/local/opt/llvm@11/include #cgo darwin CFLAGS: -I/usr/local/opt/llvm@10/include
#cgo freebsd CFLAGS: -I/usr/local/llvm11/include #cgo freebsd CFLAGS: -I/usr/local/llvm10/include
#cgo linux LDFLAGS: -L/usr/lib/llvm-11/lib -lclang #cgo linux LDFLAGS: -L/usr/lib/llvm-10/lib -lclang
#cgo darwin LDFLAGS: -L/usr/local/opt/llvm@11/lib -lclang -lffi #cgo darwin LDFLAGS: -L/usr/local/opt/llvm@10/lib -lclang -lffi
#cgo freebsd LDFLAGS: -L/usr/local/llvm11/lib -lclang #cgo freebsd LDFLAGS: -L/usr/local/llvm10/lib -lclang
*/ */
import "C" import "C"
-14
View File
@@ -1,14 +0,0 @@
// +build !byollvm
// +build llvm10
package cgo
/*
#cgo linux CFLAGS: -I/usr/lib/llvm-10/include
#cgo darwin CFLAGS: -I/usr/local/opt/llvm@10/include
#cgo freebsd CFLAGS: -I/usr/local/llvm10/include
#cgo linux LDFLAGS: -L/usr/lib/llvm-10/lib -lclang
#cgo darwin LDFLAGS: -L/usr/local/opt/llvm@10/lib -lclang -lffi
#cgo freebsd LDFLAGS: -L/usr/local/llvm10/lib -lclang
*/
import "C"
+14
View File
@@ -0,0 +1,14 @@
// +build !byollvm
// +build llvm11
package cgo
/*
#cgo linux CFLAGS: -I/usr/lib/llvm-11/include
#cgo darwin CFLAGS: -I/usr/local/opt/llvm@11/include
#cgo freebsd CFLAGS: -I/usr/local/llvm11/include
#cgo linux LDFLAGS: -L/usr/lib/llvm-11/lib -lclang
#cgo darwin LDFLAGS: -L/usr/local/opt/llvm@11/lib -lclang -lffi
#cgo freebsd LDFLAGS: -L/usr/local/llvm11/lib -lclang
*/
import "C"
+14
View File
@@ -0,0 +1,14 @@
// +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"
+2 -2
View File
@@ -4,10 +4,10 @@
// testdata/errors.go:13:23: unexpected token ) // testdata/errors.go:13:23: unexpected token )
// Type checking errors after CGo processing: // Type checking errors after CGo processing:
// testdata/errors.go:102: cannot use 2 << 10 (untyped int constant 2048) as uint8 value in variable declaration (overflows) // testdata/errors.go:102: 2 << 10 (untyped int constant 2048) overflows uint8
// testdata/errors.go:105: unknown field z in struct literal // testdata/errors.go:105: unknown field z in struct literal
// testdata/errors.go:108: undeclared name: C.SOME_CONST_1 // testdata/errors.go:108: undeclared name: C.SOME_CONST_1
// testdata/errors.go:110: cannot use C.SOME_CONST_3 (untyped int constant 1234) as byte value in variable declaration (overflows) // testdata/errors.go:110: C.SOME_CONST_3 (untyped int constant 1234) overflows byte
package main package main
-10
View File
@@ -104,10 +104,6 @@ typedef struct {
unsigned char e : 3; unsigned char e : 3;
// Note that C++ allows bitfields bigger than the underlying type. // Note that C++ allows bitfields bigger than the underlying type.
} bitfield_t; } bitfield_t;
// Function signatures.
void variadic0();
void variadic2(int x, int y, ...);
*/ */
import "C" import "C"
@@ -167,9 +163,3 @@ func accessUnion() {
var _ *C.int = union2d.unionfield_i() var _ *C.int = union2d.unionfield_i()
var _ *[2]float64 = union2d.unionfield_d() var _ *[2]float64 = union2d.unionfield_d()
} }
// Test function signatures.
func accessFunctions() {
C.variadic0()
C.variadic2(3, 5)
}
-5
View File
@@ -4,11 +4,6 @@ import "unsafe"
var _ unsafe.Pointer var _ unsafe.Pointer
func C.variadic0() //go:variadic
func C.variadic2(x C.int, y C.int) //go:variadic
var C.variadic0$funcaddr unsafe.Pointer
var C.variadic2$funcaddr unsafe.Pointer
const C.option2A = 20 const C.option2A = 20
const C.optionA = 0 const C.optionA = 0
const C.optionB = 1 const C.optionB = 1
+40 -44
View File
@@ -7,6 +7,7 @@ import (
"fmt" "fmt"
"path/filepath" "path/filepath"
"regexp" "regexp"
"strconv"
"strings" "strings"
"github.com/tinygo-org/tinygo/goenv" "github.com/tinygo-org/tinygo/goenv"
@@ -21,6 +22,29 @@ type Config struct {
TestConfig TestConfig 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. // Triple returns the LLVM target triple, like armv6m-none-eabi.
func (c *Config) Triple() string { func (c *Config) Triple() string {
return c.Target.Triple return c.Target.Triple
@@ -118,47 +142,16 @@ func (c *Config) Scheduler() string {
return "coroutines" return "coroutines"
} }
// OptLevels returns the optimization level (0-2), size level (0-2), and inliner
// threshold as used in the LLVM optimization pipeline.
func (c *Config) OptLevels() (optLevel, sizeLevel int, inlinerThreshold uint) {
switch c.Options.Opt {
case "none", "0":
return 0, 0, 0 // -O0
case "1":
return 1, 0, 0 // -O1
case "2":
return 2, 0, 225 // -O2
case "s":
return 2, 1, 225 // -Os
case "z":
return 2, 2, 5 // -Oz, default
default:
// This is not shown to the user: valid choices are already checked as
// part of Options.Verify(). It is here as a sanity check.
panic("unknown optimization level: -opt=" + c.Options.Opt)
}
}
// FuncImplementation picks an appropriate func value implementation for the // FuncImplementation picks an appropriate func value implementation for the
// target. // target.
func (c *Config) FuncImplementation() string { func (c *Config) FuncImplementation() 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() { switch c.Scheduler() {
case "tasks":
// 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.
return "doubleword"
case "none", "coroutines": case "none", "coroutines":
// As "doubleword", but with the function pointer replaced by a unique return FuncValueSwitch
// ID per function signature. Function values are called by using a case "tasks":
// switch statement and choosing which function to call. return FuncValueDoubleword
// Pick the switch implementation with the coroutines scheduler, as it
// allows the use of blocking inside a function that is used as a func
// value.
return "switch"
default: default:
panic("unknown scheduler type") panic("unknown scheduler type")
} }
@@ -184,9 +177,9 @@ func (c *Config) AutomaticStackSize() bool {
// CFlags returns the flags to pass to the C compiler. This is necessary for CGo // CFlags returns the flags to pass to the C compiler. This is necessary for CGo
// preprocessing. // preprocessing.
func (c *Config) CFlags() []string { func (c *Config) CFlags() []string {
var cflags []string cflags := append([]string{}, c.Options.CFlags...)
for _, flag := range c.Target.CFlags { for _, flag := range c.Target.CFlags {
cflags = append(cflags, strings.ReplaceAll(flag, "{root}", goenv.Get("TINYGOROOT"))) cflags = append(cflags, strings.Replace(flag, "{root}", goenv.Get("TINYGOROOT"), -1))
} }
if c.Target.Libc == "picolibc" { if c.Target.Libc == "picolibc" {
root := goenv.Get("TINYGOROOT") root := goenv.Get("TINYGOROOT")
@@ -205,11 +198,17 @@ func (c *Config) CFlags() []string {
func (c *Config) LDFlags() []string { func (c *Config) LDFlags() []string {
root := goenv.Get("TINYGOROOT") root := goenv.Get("TINYGOROOT")
// Merge and adjust LDFlags. // Merge and adjust LDFlags.
var ldflags []string ldflags := append([]string{}, c.Options.LDFlags...)
for _, flag := range c.Target.LDFlags { for _, flag := range c.Target.LDFlags {
ldflags = append(ldflags, strings.ReplaceAll(flag, "{root}", root)) ldflags = append(ldflags, strings.Replace(flag, "{root}", root, -1))
} }
ldflags = append(ldflags, "-L", root) 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 != "" { if c.Target.LinkerScript != "" {
ldflags = append(ldflags, "-T", c.Target.LinkerScript) ldflags = append(ldflags, "-T", c.Target.LinkerScript)
} }
@@ -304,9 +303,6 @@ func (c *Config) OpenOCDConfiguration() (args []string, err error) {
return nil, fmt.Errorf("unknown OpenOCD transport: %#v", c.Target.OpenOCDTransport) return nil, fmt.Errorf("unknown OpenOCD transport: %#v", c.Target.OpenOCDTransport)
} }
args = []string{"-f", "interface/" + openocdInterface + ".cfg"} args = []string{"-f", "interface/" + openocdInterface + ".cfg"}
for _, cmd := range c.Target.OpenOCDCommands {
args = append(args, "-c", cmd)
}
if c.Target.OpenOCDTransport != "" { if c.Target.OpenOCDTransport != "" {
args = append(args, "-c", "transport select "+c.Target.OpenOCDTransport) args = append(args, "-c", "transport select "+c.Target.OpenOCDTransport)
} }
+3 -11
View File
@@ -2,7 +2,6 @@ package compileopts
import ( import (
"fmt" "fmt"
"regexp"
"strings" "strings"
) )
@@ -11,7 +10,6 @@ var (
validSchedulerOptions = []string{"none", "tasks", "coroutines"} validSchedulerOptions = []string{"none", "tasks", "coroutines"}
validPrintSizeOptions = []string{"none", "short", "full"} validPrintSizeOptions = []string{"none", "short", "full"}
validPanicStrategyOptions = []string{"print", "trap"} validPanicStrategyOptions = []string{"print", "trap"}
validOptOptions = []string{"none", "0", "1", "2", "s", "z"}
) )
// Options contains extra options to give to the compiler. These options are // Options contains extra options to give to the compiler. These options are
@@ -25,14 +23,14 @@ type Options struct {
PrintIR bool PrintIR bool
DumpSSA bool DumpSSA bool
VerifyIR bool VerifyIR bool
PrintCommands bool
Debug bool Debug bool
PrintSizes string PrintSizes string
PrintAllocs *regexp.Regexp // regexp string
PrintStacks bool PrintStacks bool
CFlags []string
LDFlags []string
Tags string Tags string
WasmAbi string WasmAbi string
GlobalValues map[string]map[string]string // map[pkgpath]map[varname]value HeapSize int64
TestConfig TestConfig TestConfig TestConfig
Programmer string Programmer string
} }
@@ -75,12 +73,6 @@ func (o *Options) Verify() error {
} }
} }
if o.Opt != "" {
if !isInArray(validOptOptions, o.Opt) {
return fmt.Errorf("invalid -opt=%s: valid values are %s", o.Opt, strings.Join(validOptOptions, ", "))
}
}
return nil return nil
} }
+5 -25
View File
@@ -7,7 +7,6 @@ import (
"errors" "errors"
"io" "io"
"os" "os"
"os/exec"
"path/filepath" "path/filepath"
"reflect" "reflect"
"runtime" "runtime"
@@ -31,6 +30,7 @@ type TargetSpec struct {
BuildTags []string `json:"build-tags"` BuildTags []string `json:"build-tags"`
GC string `json:"gc"` GC string `json:"gc"`
Scheduler string `json:"scheduler"` Scheduler string `json:"scheduler"`
Compiler string `json:"compiler"`
Linker string `json:"linker"` Linker string `json:"linker"`
RTLib string `json:"rtlib"` // compiler runtime library (libgcc, compiler-rt) RTLib string `json:"rtlib"` // compiler runtime library (libgcc, compiler-rt)
Libc string `json:"libc"` Libc string `json:"libc"`
@@ -42,7 +42,7 @@ type TargetSpec struct {
ExtraFiles []string `json:"extra-files"` ExtraFiles []string `json:"extra-files"`
Emulator []string `json:"emulator" override:"copy"` // inherited Emulator must not be append Emulator []string `json:"emulator" override:"copy"` // inherited Emulator must not be append
FlashCommand string `json:"flash-command"` FlashCommand string `json:"flash-command"`
GDB []string `json:"gdb"` GDB string `json:"gdb"`
PortReset string `json:"flash-1200-bps-reset"` PortReset string `json:"flash-1200-bps-reset"`
FlashMethod string `json:"flash-method"` FlashMethod string `json:"flash-method"`
FlashVolume string `json:"msd-volume-name"` FlashVolume string `json:"msd-volume-name"`
@@ -52,7 +52,6 @@ type TargetSpec struct {
OpenOCDInterface string `json:"openocd-interface"` OpenOCDInterface string `json:"openocd-interface"`
OpenOCDTarget string `json:"openocd-target"` OpenOCDTarget string `json:"openocd-target"`
OpenOCDTransport string `json:"openocd-transport"` OpenOCDTransport string `json:"openocd-transport"`
OpenOCDCommands []string `json:"openocd-commands"`
JLinkDevice string `json:"jlink-device"` JLinkDevice string `json:"jlink-device"`
CodeModel string `json:"code-model"` CodeModel string `json:"code-model"`
RelocationModel string `json:"relocation-model"` RelocationModel string `json:"relocation-model"`
@@ -229,13 +228,7 @@ func LoadTarget(target string) (*TargetSpec, error) {
} }
} }
// WindowsBuildNotSupportedErr is being thrown, when goos is windows and no target has been specified.
var WindowsBuildNotSupportedErr = errors.New("Building Windows binaries is currently not supported. Try specifying a different target")
func defaultTarget(goos, goarch, triple string) (*TargetSpec, error) { func defaultTarget(goos, goarch, triple string) (*TargetSpec, error) {
if goos == "windows" {
return nil, WindowsBuildNotSupportedErr
}
// No target spec available. Use the default one, useful on most systems // No target spec available. Use the default one, useful on most systems
// with a regular OS. // with a regular OS.
spec := TargetSpec{ spec := TargetSpec{
@@ -243,9 +236,10 @@ func defaultTarget(goos, goarch, triple string) (*TargetSpec, error) {
GOOS: goos, GOOS: goos,
GOARCH: goarch, GOARCH: goarch,
BuildTags: []string{goos, goarch}, BuildTags: []string{goos, goarch},
Compiler: "clang",
Linker: "cc", Linker: "cc",
CFlags: []string{"--target=" + triple}, CFlags: []string{"--target=" + triple},
GDB: []string{"gdb"}, GDB: "gdb",
PortReset: "false", PortReset: "false",
} }
if goos == "darwin" { if goos == "darwin" {
@@ -258,7 +252,7 @@ func defaultTarget(goos, goarch, triple string) (*TargetSpec, error) {
} }
if goarch != runtime.GOARCH { if goarch != runtime.GOARCH {
// Some educated guesses as to how to invoke helper programs. // Some educated guesses as to how to invoke helper programs.
spec.GDB = []string{"gdb-multiarch"} spec.GDB = "gdb-multiarch"
if goarch == "arm" && goos == "linux" { if goarch == "arm" && goos == "linux" {
spec.CFlags = append(spec.CFlags, "--sysroot=/usr/arm-linux-gnueabihf") spec.CFlags = append(spec.CFlags, "--sysroot=/usr/arm-linux-gnueabihf")
spec.Linker = "arm-linux-gnueabihf-gcc" spec.Linker = "arm-linux-gnueabihf-gcc"
@@ -276,17 +270,3 @@ func defaultTarget(goos, goarch, triple string) (*TargetSpec, error) {
} }
return &spec, nil return &spec, nil
} }
// LookupGDB looks up a gdb executable.
func (spec *TargetSpec) LookupGDB() (string, error) {
if len(spec.GDB) == 0 {
return "", errors.New("gdb not configured in the target specification")
}
for _, d := range spec.GDB {
_, err := exec.LookPath(d)
if err == nil {
return d, nil
}
}
return "", errors.New("no gdb found configured in the target specification (" + strings.Join(spec.GDB, ", ") + ")")
}
+6 -9
View File
@@ -16,7 +16,7 @@ import (
// slice. This is required by the Go language spec: an index out of bounds must // slice. This is required by the Go language spec: an index out of bounds must
// cause a panic. // cause a panic.
func (b *builder) createLookupBoundsCheck(arrayLen, index llvm.Value, indexType types.Type) { func (b *builder) createLookupBoundsCheck(arrayLen, index llvm.Value, indexType types.Type) {
if b.info.nobounds { if b.fn.IsNoBounds() {
// The //go:nobounds pragma was added to the function to avoid bounds // The //go:nobounds pragma was added to the function to avoid bounds
// checking. // checking.
return return
@@ -48,7 +48,7 @@ func (b *builder) createLookupBoundsCheck(arrayLen, index llvm.Value, indexType
// biggest possible slice capacity, 'low' means len and 'high' means cap. The // biggest possible slice capacity, 'low' means len and 'high' means cap. The
// logic is the same in both cases. // logic is the same in both cases.
func (b *builder) createSliceBoundsCheck(capacity, low, high, max llvm.Value, lowType, highType, maxType *types.Basic) { func (b *builder) createSliceBoundsCheck(capacity, low, high, max llvm.Value, lowType, highType, maxType *types.Basic) {
if b.info.nobounds { if b.fn.IsNoBounds() {
// The //go:nobounds pragma was added to the function to avoid bounds // The //go:nobounds pragma was added to the function to avoid bounds
// checking. // checking.
return return
@@ -104,7 +104,7 @@ func (b *builder) createSliceBoundsCheck(capacity, low, high, max llvm.Value, lo
// createChanBoundsCheck creates a bounds check before creating a new channel to // createChanBoundsCheck creates a bounds check before creating a new channel to
// check that the value is not too big for runtime.chanMake. // check that the value is not too big for runtime.chanMake.
func (b *builder) createChanBoundsCheck(elementSize uint64, bufSize llvm.Value, bufSizeType *types.Basic, pos token.Pos) { func (b *builder) createChanBoundsCheck(elementSize uint64, bufSize llvm.Value, bufSizeType *types.Basic, pos token.Pos) {
if b.info.nobounds { if b.fn.IsNoBounds() {
// The //go:nobounds pragma was added to the function to avoid bounds // The //go:nobounds pragma was added to the function to avoid bounds
// checking. // checking.
return return
@@ -159,9 +159,6 @@ func (b *builder) createNilCheck(inst ssa.Value, ptr llvm.Value, blockPrefix str
} }
switch inst := inst.(type) { switch inst := inst.(type) {
case *ssa.Alloc:
// An alloc is never nil.
return
case *ssa.IndexAddr: case *ssa.IndexAddr:
// This pointer is the result of an index operation into a slice or // This pointer is the result of an index operation into a slice or
// array. Such slices/arrays are already bounds checked so the pointer // array. Such slices/arrays are already bounds checked so the pointer
@@ -192,7 +189,7 @@ func (b *builder) createNilCheck(inst ssa.Value, ptr llvm.Value, blockPrefix str
// createNegativeShiftCheck creates an assertion that panics if the given shift value is negative. // createNegativeShiftCheck creates an assertion that panics if the given shift value is negative.
// This function assumes that the shift value is signed. // This function assumes that the shift value is signed.
func (b *builder) createNegativeShiftCheck(shift llvm.Value) { func (b *builder) createNegativeShiftCheck(shift llvm.Value) {
if b.info.nobounds { if b.fn.IsNoBounds() {
// Function disabled bounds checking - skip shift check. // Function disabled bounds checking - skip shift check.
return return
} }
@@ -215,8 +212,8 @@ func (b *builder) createRuntimeAssert(assert llvm.Value, blockPrefix, assertFunc
} }
} }
faultBlock := b.ctx.AddBasicBlock(b.llvmFn, blockPrefix+".throw") faultBlock := b.ctx.AddBasicBlock(b.fn.LLVMFn, blockPrefix+".throw")
nextBlock := b.ctx.AddBasicBlock(b.llvmFn, blockPrefix+".next") nextBlock := b.ctx.AddBasicBlock(b.fn.LLVMFn, blockPrefix+".next")
b.blockExits[b.currentBlock] = nextBlock // adjust outgoing block for phi nodes b.blockExits[b.currentBlock] = nextBlock // adjust outgoing block for phi nodes
// Now branch to the out-of-bounds or the regular block. // Now branch to the out-of-bounds or the regular block.
-27
View File
@@ -1,8 +1,6 @@
package compiler package compiler
import ( import (
"strings"
"golang.org/x/tools/go/ssa" "golang.org/x/tools/go/ssa"
"tinygo.org/x/go-llvm" "tinygo.org/x/go-llvm"
) )
@@ -37,31 +35,6 @@ func (b *builder) createAtomicOp(call *ssa.CallCommon) (llvm.Value, bool) {
ptr := b.getValue(call.Args[0]) ptr := b.getValue(call.Args[0])
old := b.getValue(call.Args[1]) old := b.getValue(call.Args[1])
newVal := b.getValue(call.Args[2]) newVal := b.getValue(call.Args[2])
if strings.HasSuffix(name, "64") {
arch := strings.Split(b.Triple, "-")[0]
if strings.HasPrefix(arch, "arm") && strings.HasSuffix(arch, "m") {
// Work around a bug in LLVM, at least LLVM 11:
// https://reviews.llvm.org/D95891
// Check for armv6m, armv7, armv7em, and perhaps others.
// See also: https://gcc.gnu.org/onlinedocs/gcc/_005f_005fsync-Builtins.html
compareAndSwap := b.mod.NamedFunction("__sync_val_compare_and_swap_8")
if compareAndSwap.IsNil() {
// Declare the function if it isn't already declared.
i64Type := b.ctx.Int64Type()
fnType := llvm.FunctionType(i64Type, []llvm.Type{llvm.PointerType(i64Type, 0), i64Type, i64Type}, false)
compareAndSwap = llvm.AddFunction(b.mod, "__sync_val_compare_and_swap_8", fnType)
}
actualOldValue := b.CreateCall(compareAndSwap, []llvm.Value{ptr, old, newVal}, "")
// The __sync_val_compare_and_swap_8 function returns the old
// value. However, we shouldn't return the old value, we should
// return whether the compare/exchange was successful. This is
// easily done by comparing the returned (actual) old value with
// the expected old value passed to
// __sync_val_compare_and_swap_8.
swapped := b.CreateICmp(llvm.IntEQ, old, actualOldValue, "")
return swapped, true
}
}
tuple := b.CreateAtomicCmpXchg(ptr, old, newVal, llvm.AtomicOrderingSequentiallyConsistent, llvm.AtomicOrderingSequentiallyConsistent, true) tuple := b.CreateAtomicCmpXchg(ptr, old, newVal, llvm.AtomicOrderingSequentiallyConsistent, llvm.AtomicOrderingSequentiallyConsistent, true)
swapped := b.CreateExtractValue(tuple, 1, "") swapped := b.CreateExtractValue(tuple, 1, "")
return swapped, true return swapped, true
+15 -28
View File
@@ -4,7 +4,6 @@ import (
"go/types" "go/types"
"strconv" "strconv"
"golang.org/x/tools/go/ssa"
"tinygo.org/x/go-llvm" "tinygo.org/x/go-llvm"
) )
@@ -35,14 +34,14 @@ const (
// createCall creates a new call to runtime.<fnName> with the given arguments. // createCall creates a new call to runtime.<fnName> with the given arguments.
func (b *builder) createRuntimeCall(fnName string, args []llvm.Value, name string) llvm.Value { func (b *builder) createRuntimeCall(fnName string, args []llvm.Value, name string) llvm.Value {
fn := b.program.ImportedPackage("runtime").Members[fnName].(*ssa.Function) fullName := "runtime." + fnName
llvmFn := b.getFunction(fn) fn := b.mod.NamedFunction(fullName)
if llvmFn.IsNil() { if fn.IsNil() {
panic("trying to call non-existent function: " + fn.RelString(nil)) panic("trying to call non-existent function: " + fullName)
} }
args = append(args, llvm.Undef(b.i8ptrType)) // unused context parameter args = append(args, llvm.Undef(b.i8ptrType)) // unused context parameter
args = append(args, llvm.ConstPointerNull(b.i8ptrType)) // coroutine handle args = append(args, llvm.ConstPointerNull(b.i8ptrType)) // coroutine handle
return b.createCall(llvmFn, args, name) return b.createCall(fn, args, name)
} }
// createCall creates a call to the given function with the arguments possibly // createCall creates a call to the given function with the arguments possibly
@@ -58,10 +57,10 @@ func (b *builder) createCall(fn llvm.Value, args []llvm.Value, name string) llvm
// Expand an argument type to a list that can be used in a function call // Expand an argument type to a list that can be used in a function call
// parameter list. // parameter list.
func (c *compilerContext) expandFormalParamType(t llvm.Type, name string, goType types.Type) []paramInfo { func expandFormalParamType(t llvm.Type, name string, goType types.Type) []paramInfo {
switch t.TypeKind() { switch t.TypeKind() {
case llvm.StructTypeKind: case llvm.StructTypeKind:
fieldInfos := c.flattenAggregateType(t, name, goType) fieldInfos := flattenAggregateType(t, name, goType)
if len(fieldInfos) <= maxFieldsPerParam { if len(fieldInfos) <= maxFieldsPerParam {
return fieldInfos return fieldInfos
} else { } else {
@@ -105,7 +104,7 @@ func (b *builder) expandFormalParamOffsets(t llvm.Type) []uint64 {
func (b *builder) expandFormalParam(v llvm.Value) []llvm.Value { func (b *builder) expandFormalParam(v llvm.Value) []llvm.Value {
switch v.Type().TypeKind() { switch v.Type().TypeKind() {
case llvm.StructTypeKind: case llvm.StructTypeKind:
fieldInfos := b.flattenAggregateType(v.Type(), "", nil) fieldInfos := flattenAggregateType(v.Type(), "", nil)
if len(fieldInfos) <= maxFieldsPerParam { if len(fieldInfos) <= maxFieldsPerParam {
fields := b.flattenAggregate(v) fields := b.flattenAggregate(v)
if len(fields) != len(fieldInfos) { if len(fields) != len(fieldInfos) {
@@ -124,15 +123,12 @@ 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 // 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. // with the passed in type if this is not possible.
func (c *compilerContext) flattenAggregateType(t llvm.Type, name string, goType types.Type) []paramInfo { func flattenAggregateType(t llvm.Type, name string, goType types.Type) []paramInfo {
typeFlags := getTypeFlags(goType) typeFlags := getTypeFlags(goType)
switch t.TypeKind() { switch t.TypeKind() {
case llvm.StructTypeKind: case llvm.StructTypeKind:
var paramInfos []paramInfo paramInfos := make([]paramInfo, 0, t.StructElementTypesCount())
for i, subfield := range t.StructElementTypes() { for i, subfield := range t.StructElementTypes() {
if c.targetData.TypeAllocSize(subfield) == 0 {
continue
}
suffix := strconv.Itoa(i) suffix := strconv.Itoa(i)
if goType != nil { if goType != nil {
// Try to come up with a good suffix for this struct field, // Try to come up with a good suffix for this struct field,
@@ -155,7 +151,7 @@ func (c *compilerContext) flattenAggregateType(t llvm.Type, name string, goType
suffix = []string{"context", "funcptr"}[i] suffix = []string{"context", "funcptr"}[i]
} }
} }
subInfos := c.flattenAggregateType(subfield, name+"."+suffix, extractSubfield(goType, i)) subInfos := flattenAggregateType(subfield, name+"."+suffix, extractSubfield(goType, i))
for i := range subInfos { for i := range subInfos {
subInfos[i].flags |= typeFlags subInfos[i].flags |= typeFlags
} }
@@ -221,11 +217,8 @@ func extractSubfield(t types.Type, field int) types.Type {
func (c *compilerContext) flattenAggregateTypeOffsets(t llvm.Type) []uint64 { func (c *compilerContext) flattenAggregateTypeOffsets(t llvm.Type) []uint64 {
switch t.TypeKind() { switch t.TypeKind() {
case llvm.StructTypeKind: case llvm.StructTypeKind:
var fields []uint64 fields := make([]uint64, 0, t.StructElementTypesCount())
for fieldIndex, field := range t.StructElementTypes() { for fieldIndex, field := range t.StructElementTypes() {
if c.targetData.TypeAllocSize(field) == 0 {
continue
}
suboffsets := c.flattenAggregateTypeOffsets(field) suboffsets := c.flattenAggregateTypeOffsets(field)
offset := c.targetData.ElementOffset(t, fieldIndex) offset := c.targetData.ElementOffset(t, fieldIndex)
for i := range suboffsets { for i := range suboffsets {
@@ -244,11 +237,8 @@ func (c *compilerContext) flattenAggregateTypeOffsets(t llvm.Type) []uint64 {
func (b *builder) flattenAggregate(v llvm.Value) []llvm.Value { func (b *builder) flattenAggregate(v llvm.Value) []llvm.Value {
switch v.Type().TypeKind() { switch v.Type().TypeKind() {
case llvm.StructTypeKind: case llvm.StructTypeKind:
var fields []llvm.Value fields := make([]llvm.Value, 0, v.Type().StructElementTypesCount())
for i, field := range v.Type().StructElementTypes() { for i := range v.Type().StructElementTypes() {
if b.targetData.TypeAllocSize(field) == 0 {
continue
}
subfield := b.CreateExtractValue(v, i, "") subfield := b.CreateExtractValue(v, i, "")
subfields := b.flattenAggregate(subfield) subfields := b.flattenAggregate(subfield)
fields = append(fields, subfields...) fields = append(fields, subfields...)
@@ -275,13 +265,10 @@ func (b *builder) collapseFormalParam(t llvm.Type, fields []llvm.Value) llvm.Val
func (b *builder) collapseFormalParamInternal(t llvm.Type, fields []llvm.Value) (llvm.Value, []llvm.Value) { func (b *builder) collapseFormalParamInternal(t llvm.Type, fields []llvm.Value) (llvm.Value, []llvm.Value) {
switch t.TypeKind() { switch t.TypeKind() {
case llvm.StructTypeKind: case llvm.StructTypeKind:
flattened := b.flattenAggregateType(t, "", nil) flattened := flattenAggregateType(t, "", nil)
if len(flattened) <= maxFieldsPerParam { if len(flattened) <= maxFieldsPerParam {
value := llvm.ConstNull(t) value := llvm.ConstNull(t)
for i, subtyp := range t.StructElementTypes() { for i, subtyp := range t.StructElementTypes() {
if b.targetData.TypeAllocSize(subtyp) == 0 {
continue
}
structField, remaining := b.collapseFormalParamInternal(subtyp, fields) structField, remaining := b.collapseFormalParamInternal(subtyp, fields)
fields = remaining fields = remaining
value = b.CreateInsertValue(value, structField, i, "") value = b.CreateInsertValue(value, structField, i, "")
+2 -1
View File
@@ -79,7 +79,8 @@ func (b *builder) createChanRecv(unop *ssa.UnOp) llvm.Value {
} }
// createChanClose closes the given channel. // createChanClose closes the given channel.
func (b *builder) createChanClose(ch llvm.Value) { func (b *builder) createChanClose(param ssa.Value) {
ch := b.getValue(param)
b.createRuntimeCall("chanClose", []llvm.Value{ch}, "") b.createRuntimeCall("chanClose", []llvm.Value{ch}, "")
} }
+428 -451
View File
File diff suppressed because it is too large Load Diff
-167
View File
@@ -1,167 +0,0 @@
package compiler
import (
"flag"
"go/types"
"io/ioutil"
"strconv"
"strings"
"testing"
"github.com/tinygo-org/tinygo/compileopts"
"github.com/tinygo-org/tinygo/loader"
"tinygo.org/x/go-llvm"
)
// Pass -update to go test to update the output of the test files.
var flagUpdate = flag.Bool("update", false, "update tests based on test output")
// Basic tests for the compiler. Build some Go files and compare the output with
// the expected LLVM IR for regression testing.
func TestCompiler(t *testing.T) {
// Check LLVM version.
llvmMajor, err := strconv.Atoi(strings.SplitN(llvm.Version, ".", 2)[0])
if err != nil {
t.Fatal("could not parse LLVM version:", llvm.Version)
}
if llvmMajor < 11 {
// It is likely this version needs to be bumped in the future.
// The goal is to at least test the LLVM version that's used by default
// in TinyGo and (if possible without too many workarounds) also some
// previous versions.
t.Skip("compiler tests require LLVM 11 or above, got LLVM ", llvm.Version)
}
target, err := compileopts.LoadTarget("i686--linux")
if err != nil {
t.Fatal("failed to load target:", err)
}
config := &compileopts.Config{
Options: &compileopts.Options{},
Target: target,
}
compilerConfig := &Config{
Triple: config.Triple(),
GOOS: config.GOOS(),
GOARCH: config.GOARCH(),
CodeModel: config.CodeModel(),
RelocationModel: config.RelocationModel(),
Scheduler: config.Scheduler(),
FuncImplementation: config.FuncImplementation(),
AutomaticStackSize: config.AutomaticStackSize(),
}
machine, err := NewTargetMachine(compilerConfig)
if err != nil {
t.Fatal("failed to create target machine:", err)
}
tests := []string{
"basic.go",
"pointer.go",
"slice.go",
"string.go",
"float.go",
"interface.go",
"func.go",
}
for _, testCase := range tests {
t.Run(testCase, func(t *testing.T) {
// Load entire program AST into memory.
lprogram, err := loader.Load(config, []string{"./testdata/" + testCase}, config.ClangHeaders, types.Config{
Sizes: Sizes(machine),
})
if err != nil {
t.Fatal("failed to create target machine:", err)
}
err = lprogram.Parse()
if err != nil {
t.Fatalf("could not parse test case %s: %s", testCase, err)
}
// Compile AST to IR.
program := lprogram.LoadSSA()
pkg := lprogram.MainPkg()
mod, errs := CompilePackage(testCase, pkg, program.Package(pkg.Pkg), machine, compilerConfig, false)
if errs != nil {
for _, err := range errs {
t.Error(err)
}
return
}
err = llvm.VerifyModule(mod, llvm.PrintMessageAction)
if err != nil {
t.Error(err)
}
// Optimize IR a little.
funcPasses := llvm.NewFunctionPassManagerForModule(mod)
defer funcPasses.Dispose()
funcPasses.AddInstructionCombiningPass()
funcPasses.InitializeFunc()
for fn := mod.FirstFunction(); !fn.IsNil(); fn = llvm.NextFunction(fn) {
funcPasses.RunFunc(fn)
}
funcPasses.FinalizeFunc()
outfile := "./testdata/" + testCase[:len(testCase)-3] + ".ll"
// Update test if needed. Do not check the result.
if *flagUpdate {
err := ioutil.WriteFile(outfile, []byte(mod.String()), 0666)
if err != nil {
t.Error("failed to write updated output file:", err)
}
return
}
expected, err := ioutil.ReadFile(outfile)
if err != nil {
t.Fatal("failed to read golden file:", err)
}
if !fuzzyEqualIR(mod.String(), string(expected)) {
t.Errorf("output does not match expected output:\n%s", mod.String())
}
})
}
}
// 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, line1 := range lines1 {
line2 := lines2[i]
if line1 != line2 {
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.Split(line, ";")[0] // strip out comments/info
line = strings.TrimRight(line, "\r ") // drop '\r' on Windows and remove trailing spaces from comments
if line == "" {
continue
}
if strings.HasPrefix(line, "source_filename = ") {
continue
}
out = append(out, line)
}
return out
}
+40 -40
View File
@@ -14,9 +14,9 @@ package compiler
// frames. // frames.
import ( import (
"go/types"
"github.com/tinygo-org/tinygo/compiler/llvmutil" "github.com/tinygo-org/tinygo/compiler/llvmutil"
"github.com/tinygo-org/tinygo/ir"
"go/types"
"golang.org/x/tools/go/ssa" "golang.org/x/tools/go/ssa"
"tinygo.org/x/go-llvm" "tinygo.org/x/go-llvm"
) )
@@ -26,9 +26,9 @@ import (
// calls. // calls.
func (b *builder) deferInitFunc() { func (b *builder) deferInitFunc() {
// Some setup. // Some setup.
b.deferFuncs = make(map[*ssa.Function]int) b.deferFuncs = make(map[*ir.Function]int)
b.deferInvokeFuncs = make(map[string]int) b.deferInvokeFuncs = make(map[string]int)
b.deferClosureFuncs = make(map[*ssa.Function]int) b.deferClosureFuncs = make(map[*ir.Function]int)
b.deferExprFuncs = make(map[ssa.Value]int) b.deferExprFuncs = make(map[ssa.Value]int)
b.deferBuiltinFuncs = make(map[ssa.Value]deferBuiltin) b.deferBuiltinFuncs = make(map[ssa.Value]deferBuiltin)
@@ -107,11 +107,13 @@ func (b *builder) createDefer(instr *ssa.Defer) {
} else if callee, ok := instr.Call.Value.(*ssa.Function); ok { } else if callee, ok := instr.Call.Value.(*ssa.Function); ok {
// Regular function call. // Regular function call.
if _, ok := b.deferFuncs[callee]; !ok { fn := b.ir.GetFunction(callee)
b.deferFuncs[callee] = len(b.allDeferFuncs)
b.allDeferFuncs = append(b.allDeferFuncs, callee) if _, ok := b.deferFuncs[fn]; !ok {
b.deferFuncs[fn] = len(b.allDeferFuncs)
b.allDeferFuncs = append(b.allDeferFuncs, fn)
} }
callback := llvm.ConstInt(b.uintptrType, uint64(b.deferFuncs[callee]), false) callback := llvm.ConstInt(b.uintptrType, uint64(b.deferFuncs[fn]), false)
// Collect all values to be put in the struct (starting with // Collect all values to be put in the struct (starting with
// runtime._defer fields). // runtime._defer fields).
@@ -133,7 +135,7 @@ func (b *builder) createDefer(instr *ssa.Defer) {
context := b.CreateExtractValue(closure, 0, "") context := b.CreateExtractValue(closure, 0, "")
// Get the callback number. // Get the callback number.
fn := makeClosure.Fn.(*ssa.Function) fn := b.ir.GetFunction(makeClosure.Fn.(*ssa.Function))
if _, ok := b.deferClosureFuncs[fn]; !ok { if _, ok := b.deferClosureFuncs[fn]; !ok {
b.deferClosureFuncs[fn] = len(b.allDeferFuncs) b.deferClosureFuncs[fn] = len(b.allDeferFuncs)
b.allDeferFuncs = append(b.allDeferFuncs, makeClosure) b.allDeferFuncs = append(b.allDeferFuncs, makeClosure)
@@ -153,19 +155,19 @@ func (b *builder) createDefer(instr *ssa.Defer) {
valueTypes = append(valueTypes, context.Type()) valueTypes = append(valueTypes, context.Type())
} else if builtin, ok := instr.Call.Value.(*ssa.Builtin); ok { } else if builtin, ok := instr.Call.Value.(*ssa.Builtin); ok {
var argTypes []types.Type var funcName string
var argValues []llvm.Value switch builtin.Name() {
for _, arg := range instr.Call.Args { case "close":
argTypes = append(argTypes, arg.Type()) funcName = "chanClose"
argValues = append(argValues, b.getValue(arg)) default:
b.addError(instr.Pos(), "todo: Implement defer for "+builtin.Name())
return
} }
if _, ok := b.deferBuiltinFuncs[instr.Call.Value]; !ok { if _, ok := b.deferBuiltinFuncs[instr.Call.Value]; !ok {
b.deferBuiltinFuncs[instr.Call.Value] = deferBuiltin{ b.deferBuiltinFuncs[instr.Call.Value] = deferBuiltin{
callName: builtin.Name(), funcName,
pos: builtin.Pos(), len(b.allDeferFuncs),
argTypes: argTypes,
callback: len(b.allDeferFuncs),
} }
b.allDeferFuncs = append(b.allDeferFuncs, instr.Call.Value) b.allDeferFuncs = append(b.allDeferFuncs, instr.Call.Value)
} }
@@ -174,9 +176,10 @@ func (b *builder) createDefer(instr *ssa.Defer) {
// Collect all values to be put in the struct (starting with // Collect all values to be put in the struct (starting with
// runtime._defer fields). // runtime._defer fields).
values = []llvm.Value{callback, next} values = []llvm.Value{callback, next}
for _, param := range argValues { for _, param := range instr.Call.Args {
values = append(values, param) llvmParam := b.getValue(param)
valueTypes = append(valueTypes, param.Type()) values = append(values, llvmParam)
valueTypes = append(valueTypes, llvmParam.Type())
} }
} else { } else {
@@ -220,7 +223,7 @@ func (b *builder) createDefer(instr *ssa.Defer) {
allocCall := b.createRuntimeCall("alloc", []llvm.Value{sizeValue}, "defer.alloc.call") allocCall := b.createRuntimeCall("alloc", []llvm.Value{sizeValue}, "defer.alloc.call")
alloca = b.CreateBitCast(allocCall, llvm.PointerType(deferFrameType, 0), "defer.alloc") alloca = b.CreateBitCast(allocCall, llvm.PointerType(deferFrameType, 0), "defer.alloc")
} }
if b.NeedsStackObjects { if b.NeedsStackObjects() {
b.trackPointer(alloca) b.trackPointer(alloca)
} }
b.CreateStore(deferFrame, alloca) b.CreateStore(deferFrame, alloca)
@@ -248,10 +251,10 @@ func (b *builder) createRunDefers() {
// } // }
// Create loop. // Create loop.
loophead := b.ctx.AddBasicBlock(b.llvmFn, "rundefers.loophead") loophead := b.ctx.AddBasicBlock(b.fn.LLVMFn, "rundefers.loophead")
loop := b.ctx.AddBasicBlock(b.llvmFn, "rundefers.loop") loop := b.ctx.AddBasicBlock(b.fn.LLVMFn, "rundefers.loop")
unreachable := b.ctx.AddBasicBlock(b.llvmFn, "rundefers.default") unreachable := b.ctx.AddBasicBlock(b.fn.LLVMFn, "rundefers.default")
end := b.ctx.AddBasicBlock(b.llvmFn, "rundefers.end") end := b.ctx.AddBasicBlock(b.fn.LLVMFn, "rundefers.end")
b.CreateBr(loophead) b.CreateBr(loophead)
// Create loop head: // Create loop head:
@@ -283,7 +286,7 @@ func (b *builder) createRunDefers() {
// Create switch case, for example: // Create switch case, for example:
// case 0: // case 0:
// // run first deferred call // // run first deferred call
block := b.ctx.AddBasicBlock(b.llvmFn, "rundefers.callback") block := b.ctx.AddBasicBlock(b.fn.LLVMFn, "rundefers.callback")
sw.AddCase(llvm.ConstInt(b.uintptrType, uint64(i), false), block) sw.AddCase(llvm.ConstInt(b.uintptrType, uint64(i), false), block)
b.SetInsertPointAtEnd(block) b.SetInsertPointAtEnd(block)
switch callback := callback.(type) { switch callback := callback.(type) {
@@ -347,12 +350,12 @@ func (b *builder) createRunDefers() {
b.createCall(fnPtr, forwardParams, "") b.createCall(fnPtr, forwardParams, "")
case *ssa.Function: case *ir.Function:
// Direct call. // Direct call.
// Get the real defer struct type and cast to it. // Get the real defer struct type and cast to it.
valueTypes := []llvm.Type{b.uintptrType, llvm.PointerType(b.getLLVMRuntimeType("_defer"), 0)} valueTypes := []llvm.Type{b.uintptrType, llvm.PointerType(b.getLLVMRuntimeType("_defer"), 0)}
for _, param := range getParams(callback.Signature) { for _, param := range callback.Params {
valueTypes = append(valueTypes, b.getLLVMType(param.Type())) valueTypes = append(valueTypes, b.getLLVMType(param.Type()))
} }
deferFrameType := b.ctx.StructType(valueTypes, false) deferFrameType := b.ctx.StructType(valueTypes, false)
@@ -361,7 +364,7 @@ func (b *builder) createRunDefers() {
// Extract the params from the struct. // Extract the params from the struct.
forwardParams := []llvm.Value{} forwardParams := []llvm.Value{}
zero := llvm.ConstInt(b.ctx.Int32Type(), 0, false) zero := llvm.ConstInt(b.ctx.Int32Type(), 0, false)
for i := range getParams(callback.Signature) { for i := range callback.Params {
gep := b.CreateInBoundsGEP(deferFramePtr, []llvm.Value{zero, llvm.ConstInt(b.ctx.Int32Type(), uint64(i+2), false)}, "gep") gep := b.CreateInBoundsGEP(deferFramePtr, []llvm.Value{zero, llvm.ConstInt(b.ctx.Int32Type(), uint64(i+2), false)}, "gep")
forwardParam := b.CreateLoad(gep, "param") forwardParam := b.CreateLoad(gep, "param")
forwardParams = append(forwardParams, forwardParam) forwardParams = append(forwardParams, forwardParam)
@@ -369,7 +372,7 @@ func (b *builder) createRunDefers() {
// Plain TinyGo functions add some extra parameters to implement async functionality and function recievers. // Plain TinyGo functions add some extra parameters to implement async functionality and function recievers.
// These parameters should not be supplied when calling into an external C/ASM function. // These parameters should not be supplied when calling into an external C/ASM function.
if !b.getFunctionInfo(callback).exported { if !callback.IsExported() {
// Add the context parameter. We know it is ignored by the receiving // Add the context parameter. We know it is ignored by the receiving
// function, but we have to pass one anyway. // function, but we have to pass one anyway.
forwardParams = append(forwardParams, llvm.Undef(b.i8ptrType)) forwardParams = append(forwardParams, llvm.Undef(b.i8ptrType))
@@ -379,11 +382,11 @@ func (b *builder) createRunDefers() {
} }
// Call real function. // Call real function.
b.createCall(b.getFunction(callback), forwardParams, "") b.createCall(callback.LLVMFn, forwardParams, "")
case *ssa.MakeClosure: case *ssa.MakeClosure:
// Get the real defer struct type and cast to it. // Get the real defer struct type and cast to it.
fn := callback.Fn.(*ssa.Function) fn := b.ir.GetFunction(callback.Fn.(*ssa.Function))
valueTypes := []llvm.Type{b.uintptrType, llvm.PointerType(b.getLLVMRuntimeType("_defer"), 0)} valueTypes := []llvm.Type{b.uintptrType, llvm.PointerType(b.getLLVMRuntimeType("_defer"), 0)}
params := fn.Signature.Params() params := fn.Signature.Params()
for i := 0; i < params.Len(); i++ { for i := 0; i < params.Len(); i++ {
@@ -406,7 +409,7 @@ func (b *builder) createRunDefers() {
forwardParams = append(forwardParams, llvm.Undef(b.i8ptrType)) forwardParams = append(forwardParams, llvm.Undef(b.i8ptrType))
// Call deferred function. // Call deferred function.
b.createCall(b.getFunction(fn), forwardParams, "") b.createCall(fn.LLVMFn, forwardParams, "")
case *ssa.Builtin: case *ssa.Builtin:
db := b.deferBuiltinFuncs[callback] db := b.deferBuiltinFuncs[callback]
@@ -423,18 +426,15 @@ func (b *builder) createRunDefers() {
deferFramePtr := b.CreateBitCast(deferData, llvm.PointerType(deferFrameType, 0), "deferFrame") deferFramePtr := b.CreateBitCast(deferData, llvm.PointerType(deferFrameType, 0), "deferFrame")
// Extract the params from the struct. // Extract the params from the struct.
var argValues []llvm.Value var forwardParams []llvm.Value
zero := llvm.ConstInt(b.ctx.Int32Type(), 0, false) zero := llvm.ConstInt(b.ctx.Int32Type(), 0, false)
for i := 0; i < params.Len(); i++ { for i := 0; i < params.Len(); i++ {
gep := b.CreateInBoundsGEP(deferFramePtr, []llvm.Value{zero, llvm.ConstInt(b.ctx.Int32Type(), uint64(i+2), false)}, "gep") gep := b.CreateInBoundsGEP(deferFramePtr, []llvm.Value{zero, llvm.ConstInt(b.ctx.Int32Type(), uint64(i+2), false)}, "gep")
forwardParam := b.CreateLoad(gep, "param") forwardParam := b.CreateLoad(gep, "param")
argValues = append(argValues, forwardParam) forwardParams = append(forwardParams, forwardParam)
} }
_, err := b.createBuiltin(db.argTypes, argValues, db.callName, db.pos) b.createRuntimeCall(db.funcName, forwardParams, "")
if err != nil {
b.diagnostics = append(b.diagnostics, err)
}
default: default:
panic("unknown deferred function type") panic("unknown deferred function type")
} }
+1 -1
View File
@@ -14,7 +14,7 @@ import (
// makeError makes it easy to create an error from a token.Pos with a message. // makeError makes it easy to create an error from a token.Pos with a message.
func (c *compilerContext) makeError(pos token.Pos, msg string) types.Error { func (c *compilerContext) makeError(pos token.Pos, msg string) types.Error {
return types.Error{ return types.Error{
Fset: c.program.Fset, Fset: c.ir.Program.Fset,
Pos: pos, Pos: pos,
Msg: msg, Msg: msg,
} }
+18 -29
View File
@@ -6,6 +6,7 @@ package compiler
import ( import (
"go/types" "go/types"
"github.com/tinygo-org/tinygo/compileopts"
"golang.org/x/tools/go/ssa" "golang.org/x/tools/go/ssa"
"tinygo.org/x/go-llvm" "tinygo.org/x/go-llvm"
) )
@@ -20,23 +21,24 @@ func (b *builder) createFuncValue(funcPtr, context llvm.Value, sig *types.Signat
// context. // context.
func (c *compilerContext) createFuncValue(builder llvm.Builder, funcPtr, context llvm.Value, sig *types.Signature) llvm.Value { func (c *compilerContext) createFuncValue(builder llvm.Builder, funcPtr, context llvm.Value, sig *types.Signature) llvm.Value {
var funcValueScalar llvm.Value var funcValueScalar llvm.Value
switch c.FuncImplementation { switch c.FuncImplementation() {
case "doubleword": case compileopts.FuncValueDoubleword:
// Closure is: {context, function pointer} // Closure is: {context, function pointer}
funcValueScalar = funcPtr funcValueScalar = funcPtr
case "switch": case compileopts.FuncValueSwitch:
sigGlobal := c.getTypeCode(sig)
funcValueWithSignatureGlobalName := funcPtr.Name() + "$withSignature" funcValueWithSignatureGlobalName := funcPtr.Name() + "$withSignature"
funcValueWithSignatureGlobal := c.mod.NamedGlobal(funcValueWithSignatureGlobalName) funcValueWithSignatureGlobal := c.mod.NamedGlobal(funcValueWithSignatureGlobalName)
if funcValueWithSignatureGlobal.IsNil() { if funcValueWithSignatureGlobal.IsNil() {
funcValueWithSignatureType := c.getLLVMRuntimeType("funcValueWithSignature") funcValueWithSignatureType := c.getLLVMRuntimeType("funcValueWithSignature")
funcValueWithSignature := llvm.ConstNamedStruct(funcValueWithSignatureType, []llvm.Value{ funcValueWithSignature := llvm.ConstNamedStruct(funcValueWithSignatureType, []llvm.Value{
llvm.ConstPtrToInt(funcPtr, c.uintptrType), llvm.ConstPtrToInt(funcPtr, c.uintptrType),
c.getFuncSignatureID(sig), sigGlobal,
}) })
funcValueWithSignatureGlobal = llvm.AddGlobal(c.mod, funcValueWithSignatureType, funcValueWithSignatureGlobalName) funcValueWithSignatureGlobal = llvm.AddGlobal(c.mod, funcValueWithSignatureType, funcValueWithSignatureGlobalName)
funcValueWithSignatureGlobal.SetInitializer(funcValueWithSignature) funcValueWithSignatureGlobal.SetInitializer(funcValueWithSignature)
funcValueWithSignatureGlobal.SetGlobalConstant(true) funcValueWithSignatureGlobal.SetGlobalConstant(true)
funcValueWithSignatureGlobal.SetLinkage(llvm.LinkOnceODRLinkage) funcValueWithSignatureGlobal.SetLinkage(llvm.InternalLinkage)
} }
funcValueScalar = llvm.ConstPtrToInt(funcValueWithSignatureGlobal, c.uintptrType) funcValueScalar = llvm.ConstPtrToInt(funcValueWithSignatureGlobal, c.uintptrType)
default: default:
@@ -49,19 +51,6 @@ func (c *compilerContext) createFuncValue(builder llvm.Builder, funcPtr, context
return funcValue return funcValue
} }
// getFuncSignatureID returns a new external global for a given signature. This
// global reference is not real, it is only used during func lowering to assign
// signature types to functions and will then be removed.
func (c *compilerContext) getFuncSignatureID(sig *types.Signature) llvm.Value {
sigGlobalName := "reflect/types.funcid:" + getTypeCodeName(sig)
sigGlobal := c.mod.NamedGlobal(sigGlobalName)
if sigGlobal.IsNil() {
sigGlobal = llvm.AddGlobal(c.mod, c.ctx.Int8Type(), sigGlobalName)
sigGlobal.SetGlobalConstant(true)
}
return sigGlobal
}
// extractFuncScalar returns some scalar that can be used in comparisons. It is // extractFuncScalar returns some scalar that can be used in comparisons. It is
// a cheap operation. // a cheap operation.
func (b *builder) extractFuncScalar(funcValue llvm.Value) llvm.Value { func (b *builder) extractFuncScalar(funcValue llvm.Value) llvm.Value {
@@ -78,12 +67,12 @@ func (b *builder) extractFuncContext(funcValue llvm.Value) llvm.Value {
// value. This may be an expensive operation. // value. This may be an expensive operation.
func (b *builder) decodeFuncValue(funcValue llvm.Value, sig *types.Signature) (funcPtr, context llvm.Value) { func (b *builder) decodeFuncValue(funcValue llvm.Value, sig *types.Signature) (funcPtr, context llvm.Value) {
context = b.CreateExtractValue(funcValue, 0, "") context = b.CreateExtractValue(funcValue, 0, "")
switch b.FuncImplementation { switch b.FuncImplementation() {
case "doubleword": case compileopts.FuncValueDoubleword:
funcPtr = b.CreateExtractValue(funcValue, 1, "") funcPtr = b.CreateExtractValue(funcValue, 1, "")
case "switch": case compileopts.FuncValueSwitch:
llvmSig := b.getRawFuncType(sig) llvmSig := b.getRawFuncType(sig)
sigGlobal := b.getFuncSignatureID(sig) sigGlobal := b.getTypeCode(sig)
funcPtr = b.createRuntimeCall("getFuncPtr", []llvm.Value{funcValue, sigGlobal}, "") funcPtr = b.createRuntimeCall("getFuncPtr", []llvm.Value{funcValue, sigGlobal}, "")
funcPtr = b.CreateIntToPtr(funcPtr, llvmSig, "") funcPtr = b.CreateIntToPtr(funcPtr, llvmSig, "")
default: default:
@@ -94,11 +83,11 @@ func (b *builder) decodeFuncValue(funcValue llvm.Value, sig *types.Signature) (f
// getFuncType returns the type of a func value given a signature. // getFuncType returns the type of a func value given a signature.
func (c *compilerContext) getFuncType(typ *types.Signature) llvm.Type { func (c *compilerContext) getFuncType(typ *types.Signature) llvm.Type {
switch c.FuncImplementation { switch c.FuncImplementation() {
case "doubleword": case compileopts.FuncValueDoubleword:
rawPtr := c.getRawFuncType(typ) rawPtr := c.getRawFuncType(typ)
return c.ctx.StructType([]llvm.Type{c.i8ptrType, rawPtr}, false) return c.ctx.StructType([]llvm.Type{c.i8ptrType, rawPtr}, false)
case "switch": case compileopts.FuncValueSwitch:
return c.getLLVMRuntimeType("funcValue") return c.getLLVMRuntimeType("funcValue")
default: default:
panic("unimplemented func value variant") panic("unimplemented func value variant")
@@ -136,13 +125,13 @@ func (c *compilerContext) getRawFuncType(typ *types.Signature) llvm.Type {
// The receiver is not an interface, but a i8* type. // The receiver is not an interface, but a i8* type.
recv = c.i8ptrType recv = c.i8ptrType
} }
for _, info := range c.expandFormalParamType(recv, "", nil) { for _, info := range expandFormalParamType(recv, "", nil) {
paramTypes = append(paramTypes, info.llvmType) paramTypes = append(paramTypes, info.llvmType)
} }
} }
for i := 0; i < typ.Params().Len(); i++ { for i := 0; i < typ.Params().Len(); i++ {
subType := c.getLLVMType(typ.Params().At(i).Type()) subType := c.getLLVMType(typ.Params().At(i).Type())
for _, info := range c.expandFormalParamType(subType, "", nil) { for _, info := range expandFormalParamType(subType, "", nil) {
paramTypes = append(paramTypes, info.llvmType) paramTypes = append(paramTypes, info.llvmType)
} }
} }
@@ -160,7 +149,7 @@ func (b *builder) parseMakeClosure(expr *ssa.MakeClosure) (llvm.Value, error) {
if len(expr.Bindings) == 0 { if len(expr.Bindings) == 0 {
panic("unexpected: MakeClosure without bound variables") panic("unexpected: MakeClosure without bound variables")
} }
f := expr.Fn.(*ssa.Function) f := b.ir.GetFunction(expr.Fn.(*ssa.Function))
// Collect all bound variables. // Collect all bound variables.
boundVars := make([]llvm.Value, len(expr.Bindings)) boundVars := make([]llvm.Value, len(expr.Bindings))
@@ -175,5 +164,5 @@ func (b *builder) parseMakeClosure(expr *ssa.MakeClosure) (llvm.Value, error) {
context := b.emitPointerPack(boundVars) context := b.emitPointerPack(boundVars)
// Create the closure. // Create the closure.
return b.createFuncValue(b.getFunction(f), context, f.Signature), nil return b.createFuncValue(f.LLVMFn, context, f.Signature), nil
} }
+11 -14
View File
@@ -7,7 +7,6 @@ import (
"go/token" "go/token"
"github.com/tinygo-org/tinygo/compiler/llvmutil" "github.com/tinygo-org/tinygo/compiler/llvmutil"
"golang.org/x/tools/go/ssa"
"tinygo.org/x/go-llvm" "tinygo.org/x/go-llvm"
) )
@@ -21,20 +20,19 @@ import (
func (b *builder) createGoInstruction(funcPtr llvm.Value, params []llvm.Value, prefix string, pos token.Pos) llvm.Value { func (b *builder) createGoInstruction(funcPtr llvm.Value, params []llvm.Value, prefix string, pos token.Pos) llvm.Value {
paramBundle := b.emitPointerPack(params) paramBundle := b.emitPointerPack(params)
var callee, stackSize llvm.Value var callee, stackSize llvm.Value
switch b.Scheduler { switch b.Scheduler() {
case "none", "tasks": case "none", "tasks":
callee = b.createGoroutineStartWrapper(funcPtr, prefix, pos) callee = b.createGoroutineStartWrapper(funcPtr, prefix, pos)
if b.AutomaticStackSize { if b.AutomaticStackSize() {
// The stack size is not known until after linking. Call a dummy // The stack size is not known until after linking. Call a dummy
// function that will be replaced with a load from a special ELF // function that will be replaced with a load from a special ELF
// section that contains the stack size (and is modified after // section that contains the stack size (and is modified after
// linking). // linking).
stackSizeFn := b.getFunction(b.program.ImportedPackage("internal/task").Members["getGoroutineStackSize"].(*ssa.Function)) stackSize = b.createCall(b.mod.NamedFunction("internal/task.getGoroutineStackSize"), []llvm.Value{callee, llvm.Undef(b.i8ptrType), llvm.Undef(b.i8ptrType)}, "stacksize")
stackSize = b.createCall(stackSizeFn, []llvm.Value{callee, llvm.Undef(b.i8ptrType), llvm.Undef(b.i8ptrType)}, "stacksize")
} else { } else {
// The stack size is fixed at compile time. By emitting it here as a // The stack size is fixed at compile time. By emitting it here as a
// constant, it can be optimized. // constant, it can be optimized.
stackSize = llvm.ConstInt(b.uintptrType, b.DefaultStackSize, false) stackSize = llvm.ConstInt(b.uintptrType, b.Target.DefaultStackSize, false)
} }
case "coroutines": case "coroutines":
callee = b.CreatePtrToInt(funcPtr, b.uintptrType, "") callee = b.CreatePtrToInt(funcPtr, b.uintptrType, "")
@@ -44,8 +42,7 @@ func (b *builder) createGoInstruction(funcPtr llvm.Value, params []llvm.Value, p
default: default:
panic("unreachable") panic("unreachable")
} }
start := b.getFunction(b.program.ImportedPackage("internal/task").Members["start"].(*ssa.Function)) b.createCall(b.mod.NamedFunction("internal/task.start"), []llvm.Value{callee, paramBundle, stackSize, llvm.Undef(b.i8ptrType), llvm.ConstPointerNull(b.i8ptrType)}, "")
b.createCall(start, []llvm.Value{callee, paramBundle, stackSize, llvm.Undef(b.i8ptrType), llvm.ConstPointerNull(b.i8ptrType)}, "")
return llvm.Undef(funcPtr.Type().ElementType().ReturnType()) return llvm.Undef(funcPtr.Type().ElementType().ReturnType())
} }
@@ -84,14 +81,14 @@ func (c *compilerContext) createGoroutineStartWrapper(fn llvm.Value, prefix stri
// Create the wrapper. // Create the wrapper.
wrapperType := llvm.FunctionType(c.ctx.VoidType(), []llvm.Type{c.i8ptrType}, false) wrapperType := llvm.FunctionType(c.ctx.VoidType(), []llvm.Type{c.i8ptrType}, false)
wrapper = llvm.AddFunction(c.mod, name+"$gowrapper", wrapperType) wrapper = llvm.AddFunction(c.mod, name+"$gowrapper", wrapperType)
wrapper.SetLinkage(llvm.LinkOnceODRLinkage) wrapper.SetLinkage(llvm.InternalLinkage)
wrapper.SetUnnamedAddr(true) wrapper.SetUnnamedAddr(true)
wrapper.AddAttributeAtIndex(-1, c.ctx.CreateStringAttribute("tinygo-gowrapper", name)) wrapper.AddAttributeAtIndex(-1, c.ctx.CreateStringAttribute("tinygo-gowrapper", name))
entry := c.ctx.AddBasicBlock(wrapper, "entry") entry := c.ctx.AddBasicBlock(wrapper, "entry")
builder.SetInsertPointAtEnd(entry) builder.SetInsertPointAtEnd(entry)
if c.Debug { if c.Debug() {
pos := c.program.Fset.Position(pos) pos := c.ir.Program.Fset.Position(pos)
diFuncType := c.dibuilder.CreateSubroutineType(llvm.DISubroutineType{ diFuncType := c.dibuilder.CreateSubroutineType(llvm.DISubroutineType{
File: c.getDIFile(pos.Filename), File: c.getDIFile(pos.Filename),
Parameters: nil, // do not show parameters in debugger Parameters: nil, // do not show parameters in debugger
@@ -141,14 +138,14 @@ func (c *compilerContext) createGoroutineStartWrapper(fn llvm.Value, prefix stri
// Create the wrapper. // Create the wrapper.
wrapperType := llvm.FunctionType(c.ctx.VoidType(), []llvm.Type{c.i8ptrType}, false) wrapperType := llvm.FunctionType(c.ctx.VoidType(), []llvm.Type{c.i8ptrType}, false)
wrapper = llvm.AddFunction(c.mod, prefix+".gowrapper", wrapperType) wrapper = llvm.AddFunction(c.mod, prefix+".gowrapper", wrapperType)
wrapper.SetLinkage(llvm.LinkOnceODRLinkage) wrapper.SetLinkage(llvm.InternalLinkage)
wrapper.SetUnnamedAddr(true) wrapper.SetUnnamedAddr(true)
wrapper.AddAttributeAtIndex(-1, c.ctx.CreateStringAttribute("tinygo-gowrapper", "")) wrapper.AddAttributeAtIndex(-1, c.ctx.CreateStringAttribute("tinygo-gowrapper", ""))
entry := c.ctx.AddBasicBlock(wrapper, "entry") entry := c.ctx.AddBasicBlock(wrapper, "entry")
builder.SetInsertPointAtEnd(entry) builder.SetInsertPointAtEnd(entry)
if c.Debug { if c.Debug() {
pos := c.program.Fset.Position(pos) pos := c.ir.Program.Fset.Position(pos)
diFuncType := c.dibuilder.CreateSubroutineType(llvm.DISubroutineType{ diFuncType := c.dibuilder.CreateSubroutineType(llvm.DISubroutineType{
File: c.getDIFile(pos.Filename), File: c.getDIFile(pos.Filename),
Parameters: nil, // do not show parameters in debugger Parameters: nil, // do not show parameters in debugger
+49 -109
View File
@@ -11,6 +11,7 @@ import (
"strconv" "strconv"
"strings" "strings"
"github.com/tinygo-org/tinygo/ir"
"golang.org/x/tools/go/ssa" "golang.org/x/tools/go/ssa"
"tinygo.org/x/go-llvm" "tinygo.org/x/go-llvm"
) )
@@ -24,7 +25,16 @@ import (
func (b *builder) createMakeInterface(val llvm.Value, typ types.Type, pos token.Pos) llvm.Value { func (b *builder) createMakeInterface(val llvm.Value, typ types.Type, pos token.Pos) llvm.Value {
itfValue := b.emitPointerPack([]llvm.Value{val}) itfValue := b.emitPointerPack([]llvm.Value{val})
itfTypeCodeGlobal := b.getTypeCode(typ) itfTypeCodeGlobal := b.getTypeCode(typ)
itfTypeCode := b.CreatePtrToInt(itfTypeCodeGlobal, b.uintptrType, "") itfMethodSetGlobal := b.getTypeMethodSet(typ)
itfConcreteTypeGlobal := b.mod.NamedGlobal("typeInInterface:" + itfTypeCodeGlobal.Name())
if itfConcreteTypeGlobal.IsNil() {
typeInInterface := b.getLLVMRuntimeType("typeInInterface")
itfConcreteTypeGlobal = llvm.AddGlobal(b.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 := llvm.Undef(b.getLLVMRuntimeType("_interface"))
itf = b.CreateInsertValue(itf, itfTypeCode, 0, "") itf = b.CreateInsertValue(itf, itfTypeCode, 0, "")
itf = b.CreateInsertValue(itf, itfValue, 1, "") itf = b.CreateInsertValue(itf, itfValue, 1, "")
@@ -45,8 +55,6 @@ func (c *compilerContext) getTypeCode(typ types.Type) llvm.Value {
// reflect lowering simpler. // reflect lowering simpler.
var references llvm.Value var references llvm.Value
var length int64 var length int64
var methodSet llvm.Value
var ptrTo llvm.Value
switch typ := typ.(type) { switch typ := typ.(type) {
case *types.Named: case *types.Named:
references = c.getTypeCode(typ.Underlying()) references = c.getTypeCode(typ.Underlying())
@@ -63,32 +71,18 @@ func (c *compilerContext) getTypeCode(typ types.Type) llvm.Value {
// Take a pointer to the typecodeID of the first field (if it exists). // Take a pointer to the typecodeID of the first field (if it exists).
structGlobal := c.makeStructTypeFields(typ) structGlobal := c.makeStructTypeFields(typ)
references = llvm.ConstBitCast(structGlobal, global.Type()) references = llvm.ConstBitCast(structGlobal, global.Type())
case *types.Interface:
methodSetGlobal := c.getInterfaceMethodSet(typ)
references = llvm.ConstBitCast(methodSetGlobal, global.Type())
} }
if _, ok := typ.Underlying().(*types.Interface); !ok {
methodSet = c.getTypeMethodSet(typ)
}
if _, ok := typ.Underlying().(*types.Pointer); !ok {
ptrTo = c.getTypeCode(types.NewPointer(typ))
}
globalValue := llvm.ConstNull(global.Type().ElementType())
if !references.IsNil() { 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}) 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)
} }
if length != 0 {
lengthValue := llvm.ConstInt(c.uintptrType, uint64(length), false)
globalValue = llvm.ConstInsertValue(globalValue, lengthValue, []uint32{1})
}
if !methodSet.IsNil() {
globalValue = llvm.ConstInsertValue(globalValue, methodSet, []uint32{2})
}
if !ptrTo.IsNil() {
globalValue = llvm.ConstInsertValue(globalValue, ptrTo, []uint32{3})
}
global.SetInitializer(globalValue)
global.SetLinkage(llvm.LinkOnceODRLinkage)
global.SetGlobalConstant(true) global.SetGlobalConstant(true)
} }
return global return global
@@ -110,8 +104,8 @@ func (c *compilerContext) makeStructTypeFields(typ *types.Struct) llvm.Value {
fieldName.SetLinkage(llvm.PrivateLinkage) fieldName.SetLinkage(llvm.PrivateLinkage)
fieldName.SetUnnamedAddr(true) fieldName.SetUnnamedAddr(true)
fieldName = llvm.ConstGEP(fieldName, []llvm.Value{ fieldName = llvm.ConstGEP(fieldName, []llvm.Value{
llvm.ConstInt(c.ctx.Int32Type(), 0, false), llvm.ConstInt(llvm.Int32Type(), 0, false),
llvm.ConstInt(c.ctx.Int32Type(), 0, false), llvm.ConstInt(llvm.Int32Type(), 0, false),
}) })
fieldGlobalValue = llvm.ConstInsertValue(fieldGlobalValue, fieldName, []uint32{1}) fieldGlobalValue = llvm.ConstInsertValue(fieldGlobalValue, fieldName, []uint32{1})
if typ.Tag(i) != "" { if typ.Tag(i) != "" {
@@ -119,8 +113,8 @@ func (c *compilerContext) makeStructTypeFields(typ *types.Struct) llvm.Value {
fieldTag.SetLinkage(llvm.PrivateLinkage) fieldTag.SetLinkage(llvm.PrivateLinkage)
fieldTag.SetUnnamedAddr(true) fieldTag.SetUnnamedAddr(true)
fieldTag = llvm.ConstGEP(fieldTag, []llvm.Value{ fieldTag = llvm.ConstGEP(fieldTag, []llvm.Value{
llvm.ConstInt(c.ctx.Int32Type(), 0, false), llvm.ConstInt(llvm.Int32Type(), 0, false),
llvm.ConstInt(c.ctx.Int32Type(), 0, false), llvm.ConstInt(llvm.Int32Type(), 0, false),
}) })
fieldGlobalValue = llvm.ConstInsertValue(fieldGlobalValue, fieldTag, []uint32{2}) fieldGlobalValue = llvm.ConstInsertValue(fieldGlobalValue, fieldTag, []uint32{2})
} }
@@ -193,7 +187,7 @@ func getTypeCodeName(t types.Type) string {
case *types.Interface: case *types.Interface:
methods := make([]string, t.NumMethods()) methods := make([]string, t.NumMethods())
for i := 0; i < t.NumMethods(); i++ { for i := 0; i < t.NumMethods(); i++ {
methods[i] = t.Method(i).Name() + ":" + getTypeCodeName(t.Method(i).Type()) methods[i] = getTypeCodeName(t.Method(i).Type())
} }
return "interface:" + "{" + strings.Join(methods, ",") + "}" return "interface:" + "{" + strings.Join(methods, ",") + "}"
case *types.Map: case *types.Map:
@@ -242,7 +236,7 @@ func (c *compilerContext) getTypeMethodSet(typ types.Type) llvm.Value {
return llvm.ConstGEP(global, []llvm.Value{zero, zero}) return llvm.ConstGEP(global, []llvm.Value{zero, zero})
} }
ms := c.program.MethodSets.MethodSet(typ) ms := c.ir.Program.MethodSets.MethodSet(typ)
if ms.Len() == 0 { if ms.Len() == 0 {
// no methods, so can leave that one out // no methods, so can leave that one out
return llvm.ConstPointerNull(llvm.PointerType(c.getLLVMRuntimeType("interfaceMethodInfo"), 0)) return llvm.ConstPointerNull(llvm.PointerType(c.getLLVMRuntimeType("interfaceMethodInfo"), 0))
@@ -253,16 +247,15 @@ func (c *compilerContext) getTypeMethodSet(typ types.Type) llvm.Value {
for i := 0; i < ms.Len(); i++ { for i := 0; i < ms.Len(); i++ {
method := ms.At(i) method := ms.At(i)
signatureGlobal := c.getMethodSignature(method.Obj().(*types.Func)) signatureGlobal := c.getMethodSignature(method.Obj().(*types.Func))
fn := c.program.MethodValue(method) f := c.ir.GetFunction(c.ir.Program.MethodValue(method))
llvmFn := c.getFunction(fn) if f.LLVMFn.IsNil() {
if llvmFn.IsNil() {
// compiler error, so panic // compiler error, so panic
panic("cannot find function: " + c.getFunctionInfo(fn).linkName) panic("cannot find function: " + f.LinkName())
} }
wrapper := c.getInterfaceInvokeWrapper(fn, llvmFn) fn := c.getInterfaceInvokeWrapper(f)
methodInfo := llvm.ConstNamedStruct(interfaceMethodInfoType, []llvm.Value{ methodInfo := llvm.ConstNamedStruct(interfaceMethodInfoType, []llvm.Value{
signatureGlobal, signatureGlobal,
llvm.ConstPtrToInt(wrapper, c.uintptrType), llvm.ConstPtrToInt(fn, c.uintptrType),
}) })
methods[i] = methodInfo methods[i] = methodInfo
} }
@@ -271,7 +264,7 @@ func (c *compilerContext) getTypeMethodSet(typ types.Type) llvm.Value {
global = llvm.AddGlobal(c.mod, arrayType, typ.String()+"$methodset") global = llvm.AddGlobal(c.mod, arrayType, typ.String()+"$methodset")
global.SetInitializer(value) global.SetInitializer(value)
global.SetGlobalConstant(true) global.SetGlobalConstant(true)
global.SetLinkage(llvm.LinkOnceODRLinkage) global.SetLinkage(llvm.PrivateLinkage)
return llvm.ConstGEP(global, []llvm.Value{zero, zero}) return llvm.ConstGEP(global, []llvm.Value{zero, zero})
} }
@@ -302,7 +295,7 @@ func (c *compilerContext) getInterfaceMethodSet(typ types.Type) llvm.Value {
global = llvm.AddGlobal(c.mod, value.Type(), name+"$interface") global = llvm.AddGlobal(c.mod, value.Type(), name+"$interface")
global.SetInitializer(value) global.SetInitializer(value)
global.SetGlobalConstant(true) global.SetGlobalConstant(true)
global.SetLinkage(llvm.LinkOnceODRLinkage) global.SetLinkage(llvm.PrivateLinkage)
return llvm.ConstGEP(global, []llvm.Value{zero, zero}) return llvm.ConstGEP(global, []llvm.Value{zero, zero})
} }
@@ -310,7 +303,7 @@ func (c *compilerContext) getInterfaceMethodSet(typ types.Type) llvm.Value {
// external *i8 indicating the indicating the signature of this method. It is // external *i8 indicating the indicating the signature of this method. It is
// used during the interface lowering pass. // used during the interface lowering pass.
func (c *compilerContext) getMethodSignature(method *types.Func) llvm.Value { func (c *compilerContext) getMethodSignature(method *types.Func) llvm.Value {
signature := methodSignature(method) signature := ir.MethodSignature(method)
signatureGlobal := c.mod.NamedGlobal("func " + signature) signatureGlobal := c.mod.NamedGlobal("func " + signature)
if signatureGlobal.IsNil() { if signatureGlobal.IsNil() {
signatureGlobal = llvm.AddGlobal(c.mod, c.ctx.Int8Type(), "func "+signature) signatureGlobal = llvm.AddGlobal(c.mod, c.ctx.Int8Type(), "func "+signature)
@@ -345,16 +338,10 @@ func (b *builder) createTypeAssert(expr *ssa.TypeAssert) llvm.Value {
commaOk = b.createRuntimeCall("interfaceImplements", []llvm.Value{actualTypeNum, methodSet}, "") commaOk = b.createRuntimeCall("interfaceImplements", []llvm.Value{actualTypeNum, methodSet}, "")
} else { } else {
globalName := "reflect/types.typeid:" + getTypeCodeName(expr.AssertedType)
assertedTypeCodeGlobal := b.mod.NamedGlobal(globalName)
if assertedTypeCodeGlobal.IsNil() {
// Create a new typecode global.
assertedTypeCodeGlobal = llvm.AddGlobal(b.mod, b.ctx.Int8Type(), globalName)
assertedTypeCodeGlobal.SetGlobalConstant(true)
}
// Type assert on concrete type. // Type assert on concrete type.
// Call runtime.typeAssert, which will be lowered to a simple icmp or // Call runtime.typeAssert, which will be lowered to a simple icmp or
// const false in the interface lowering pass. // const false in the interface lowering pass.
assertedTypeCodeGlobal := b.getTypeCode(expr.AssertedType)
commaOk = b.createRuntimeCall("typeAssert", []llvm.Value{actualTypeNum, assertedTypeCodeGlobal}, "typecode") commaOk = b.createRuntimeCall("typeAssert", []llvm.Value{actualTypeNum, assertedTypeCodeGlobal}, "typecode")
} }
@@ -370,8 +357,8 @@ func (b *builder) createTypeAssert(expr *ssa.TypeAssert) llvm.Value {
// value. // value.
prevBlock := b.GetInsertBlock() prevBlock := b.GetInsertBlock()
okBlock := b.ctx.AddBasicBlock(b.llvmFn, "typeassert.ok") okBlock := b.ctx.AddBasicBlock(b.fn.LLVMFn, "typeassert.ok")
nextBlock := b.ctx.AddBasicBlock(b.llvmFn, "typeassert.next") nextBlock := b.ctx.AddBasicBlock(b.fn.LLVMFn, "typeassert.next")
b.blockExits[b.currentBlock] = nextBlock // adjust outgoing block for phi nodes b.blockExits[b.currentBlock] = nextBlock // adjust outgoing block for phi nodes
b.CreateCondBr(commaOk, okBlock, nextBlock) b.CreateCondBr(commaOk, okBlock, nextBlock)
@@ -449,8 +436,8 @@ func (b *builder) getInvokeCall(instr *ssa.CallCommon) (llvm.Value, []llvm.Value
// value, dereferences or unpacks it if necessary, and calls the real method. // value, dereferences or unpacks it if necessary, and calls the real method.
// If the method to wrap has a pointer receiver, no wrapping is necessary and // If the method to wrap has a pointer receiver, no wrapping is necessary and
// the function is returned directly. // the function is returned directly.
func (c *compilerContext) getInterfaceInvokeWrapper(fn *ssa.Function, llvmFn llvm.Value) llvm.Value { func (c *compilerContext) getInterfaceInvokeWrapper(f *ir.Function) llvm.Value {
wrapperName := llvmFn.Name() + "$invoke" wrapperName := f.LinkName() + "$invoke"
wrapper := c.mod.NamedFunction(wrapperName) wrapper := c.mod.NamedFunction(wrapperName)
if !wrapper.IsNil() { if !wrapper.IsNil() {
// Wrapper already created. Return it directly. // Wrapper already created. Return it directly.
@@ -458,9 +445,9 @@ func (c *compilerContext) getInterfaceInvokeWrapper(fn *ssa.Function, llvmFn llv
} }
// Get the expanded receiver type. // Get the expanded receiver type.
receiverType := c.getLLVMType(fn.Signature.Recv().Type()) receiverType := c.getLLVMType(f.Params[0].Type())
var expandedReceiverType []llvm.Type var expandedReceiverType []llvm.Type
for _, info := range c.expandFormalParamType(receiverType, "", nil) { for _, info := range expandFormalParamType(receiverType, "", nil) {
expandedReceiverType = append(expandedReceiverType, info.llvmType) expandedReceiverType = append(expandedReceiverType, info.llvmType)
} }
@@ -470,17 +457,17 @@ func (c *compilerContext) getInterfaceInvokeWrapper(fn *ssa.Function, llvmFn llv
// Casting a function signature to a different signature and calling it // Casting a function signature to a different signature and calling it
// with a receiver pointer bitcasted to *i8 (as done in calls on an // with a receiver pointer bitcasted to *i8 (as done in calls on an
// interface) is hopefully a safe (defined) operation. // interface) is hopefully a safe (defined) operation.
return llvmFn return f.LLVMFn
} }
// create wrapper function // create wrapper function
fnType := llvmFn.Type().ElementType() fnType := f.LLVMFn.Type().ElementType()
paramTypes := append([]llvm.Type{c.i8ptrType}, fnType.ParamTypes()[len(expandedReceiverType):]...) paramTypes := append([]llvm.Type{c.i8ptrType}, fnType.ParamTypes()[len(expandedReceiverType):]...)
wrapFnType := llvm.FunctionType(fnType.ReturnType(), paramTypes, false) wrapFnType := llvm.FunctionType(fnType.ReturnType(), paramTypes, false)
wrapper = llvm.AddFunction(c.mod, wrapperName, wrapFnType) wrapper = llvm.AddFunction(c.mod, wrapperName, wrapFnType)
wrapper.LastParam().SetName("parentHandle") wrapper.LastParam().SetName("parentHandle")
wrapper.SetLinkage(llvm.LinkOnceODRLinkage) wrapper.SetLinkage(llvm.InternalLinkage)
wrapper.SetUnnamedAddr(true) wrapper.SetUnnamedAddr(true)
// Create a new builder just to create this wrapper. // Create a new builder just to create this wrapper.
@@ -491,9 +478,9 @@ func (c *compilerContext) getInterfaceInvokeWrapper(fn *ssa.Function, llvmFn llv
defer b.Builder.Dispose() defer b.Builder.Dispose()
// add debug info if needed // add debug info if needed
if c.Debug { if c.Debug() {
pos := c.program.Fset.Position(fn.Pos()) pos := c.ir.Program.Fset.Position(f.Pos())
difunc := c.attachDebugInfoRaw(fn, wrapper, "$invoke", pos.Filename, pos.Line) difunc := c.attachDebugInfoRaw(f, wrapper, "$invoke", pos.Filename, pos.Line)
b.SetCurrentDebugLocation(uint(pos.Line), uint(pos.Column), difunc, llvm.Metadata{}) b.SetCurrentDebugLocation(uint(pos.Line), uint(pos.Column), difunc, llvm.Metadata{})
} }
@@ -503,60 +490,13 @@ func (c *compilerContext) getInterfaceInvokeWrapper(fn *ssa.Function, llvmFn llv
receiverValue := b.emitPointerUnpack(wrapper.Param(0), []llvm.Type{receiverType})[0] receiverValue := b.emitPointerUnpack(wrapper.Param(0), []llvm.Type{receiverType})[0]
params := append(b.expandFormalParam(receiverValue), wrapper.Params()[1:]...) params := append(b.expandFormalParam(receiverValue), wrapper.Params()[1:]...)
if llvmFn.Type().ElementType().ReturnType().TypeKind() == llvm.VoidTypeKind { if f.LLVMFn.Type().ElementType().ReturnType().TypeKind() == llvm.VoidTypeKind {
b.CreateCall(llvmFn, params, "") b.CreateCall(f.LLVMFn, params, "")
b.CreateRetVoid() b.CreateRetVoid()
} else { } else {
ret := b.CreateCall(llvmFn, params, "ret") ret := b.CreateCall(f.LLVMFn, params, "ret")
b.CreateRet(ret) b.CreateRet(ret)
} }
return wrapper return wrapper
} }
// methodSignature creates a readable version of a method signature (including
// the function name, excluding the receiver name). This string is used
// internally to match interfaces and to call the correct method on an
// interface. Examples:
//
// String() string
// Read([]byte) (int, error)
func methodSignature(method *types.Func) string {
return method.Name() + signature(method.Type().(*types.Signature))
}
// Make a readable version of a function (pointer) signature.
// Examples:
//
// () string
// (string, int) (int, error)
func signature(sig *types.Signature) string {
s := ""
if sig.Params().Len() == 0 {
s += "()"
} else {
s += "("
for i := 0; i < sig.Params().Len(); i++ {
if i > 0 {
s += ", "
}
s += sig.Params().At(i).Type().String()
}
s += ")"
}
if sig.Results().Len() == 0 {
// keep as-is
} else if sig.Results().Len() == 1 {
s += " " + sig.Results().At(0).Type().String()
} else {
s += " ("
for i := 0; i < sig.Results().Len(); i++ {
if i > 0 {
s += ", "
}
s += sig.Results().At(i).Type().String()
}
s += ")"
}
return s
}
+5 -5
View File
@@ -39,14 +39,14 @@ func (b *builder) createInterruptGlobal(instr *ssa.CallCommon) (llvm.Value, erro
// Create a new global of type runtime/interrupt.handle. Globals of this // Create a new global of type runtime/interrupt.handle. Globals of this
// type are lowered in the interrupt lowering pass. // type are lowered in the interrupt lowering pass.
globalType := b.program.ImportedPackage("runtime/interrupt").Type("handle").Type() globalType := b.ir.Program.ImportedPackage("runtime/interrupt").Type("handle").Type()
globalLLVMType := b.getLLVMType(globalType) globalLLVMType := b.getLLVMType(globalType)
globalName := "runtime/interrupt.$interrupt" + strconv.FormatInt(id.Int64(), 10) globalName := "runtime/interrupt.$interrupt" + strconv.FormatInt(id.Int64(), 10)
if global := b.mod.NamedGlobal(globalName); !global.IsNil() { if global := b.mod.NamedGlobal(globalName); !global.IsNil() {
return llvm.Value{}, b.makeError(instr.Pos(), "interrupt redeclared in this program") return llvm.Value{}, b.makeError(instr.Pos(), "interrupt redeclared in this program")
} }
global := llvm.AddGlobal(b.mod, globalLLVMType, globalName) global := llvm.AddGlobal(b.mod, globalLLVMType, globalName)
global.SetVisibility(llvm.HiddenVisibility) global.SetLinkage(llvm.PrivateLinkage)
global.SetGlobalConstant(true) global.SetGlobalConstant(true)
global.SetUnnamedAddr(true) global.SetUnnamedAddr(true)
initializer := llvm.ConstNull(globalLLVMType) initializer := llvm.ConstNull(globalLLVMType)
@@ -55,8 +55,8 @@ func (b *builder) createInterruptGlobal(instr *ssa.CallCommon) (llvm.Value, erro
global.SetInitializer(initializer) global.SetInitializer(initializer)
// Add debug info to the interrupt global. // Add debug info to the interrupt global.
if b.Debug { if b.Debug() {
pos := b.program.Fset.Position(instr.Pos()) pos := b.ir.Program.Fset.Position(instr.Pos())
diglobal := b.dibuilder.CreateGlobalVariableExpression(b.getDIFile(pos.Filename), llvm.DIGlobalVariableExpression{ diglobal := b.dibuilder.CreateGlobalVariableExpression(b.getDIFile(pos.Filename), llvm.DIGlobalVariableExpression{
Name: "interrupt" + strconv.FormatInt(id.Int64(), 10), Name: "interrupt" + strconv.FormatInt(id.Int64(), 10),
LinkageName: globalName, LinkageName: globalName,
@@ -79,7 +79,7 @@ func (b *builder) createInterruptGlobal(instr *ssa.CallCommon) (llvm.Value, erro
// PLIC where each interrupt must be enabled using the interrupt number, and // PLIC where each interrupt must be enabled using the interrupt number, and
// thus keeps the Interrupt object alive. // thus keeps the Interrupt object alive.
// This call is removed during interrupt lowering. // This call is removed during interrupt lowering.
if strings.HasPrefix(b.Triple, "avr") { if strings.HasPrefix(b.Triple(), "avr") {
useFn := b.mod.NamedFunction("runtime/interrupt.use") useFn := b.mod.NamedFunction("runtime/interrupt.use")
if useFn.IsNil() { if useFn.IsNil() {
useFnType := llvm.FunctionType(b.ctx.VoidType(), []llvm.Type{interrupt.Type()}, false) useFnType := llvm.FunctionType(b.ctx.VoidType(), []llvm.Type{interrupt.Type()}, false)
+1 -1
View File
@@ -44,7 +44,7 @@ func (b *builder) emitLifetimeEnd(ptr, size llvm.Value) {
// bitcasts, or else allocates a value on the heap if it cannot be packed in the // bitcasts, or else allocates a value on the heap if it cannot be packed in the
// pointer value directly. It returns the pointer with the packed data. // pointer value directly. It returns the pointer with the packed data.
func (b *builder) emitPointerPack(values []llvm.Value) llvm.Value { func (b *builder) emitPointerPack(values []llvm.Value) llvm.Value {
return llvmutil.EmitPointerPack(b.Builder, b.mod, b.NeedsStackObjects, values) return llvmutil.EmitPointerPack(b.Builder, b.mod, b.Config, values)
} }
// emitPointerUnpack extracts a list of values packed using emitPointerPack. // emitPointerUnpack extracts a list of values packed using emitPointerPack.
+3 -2
View File
@@ -5,6 +5,7 @@ package llvmutil
// itself if possible and legal. // itself if possible and legal.
import ( import (
"github.com/tinygo-org/tinygo/compileopts"
"tinygo.org/x/go-llvm" "tinygo.org/x/go-llvm"
) )
@@ -12,7 +13,7 @@ import (
// bitcasts, or else allocates a value on the heap if it cannot be packed in the // bitcasts, or else allocates a value on the heap if it cannot be packed in the
// pointer value directly. It returns the pointer with the packed data. // pointer value directly. It returns the pointer with the packed data.
// If the values are all constants, they are be stored in a constant global and deduplicated. // If the values are all constants, they are be stored in a constant global and deduplicated.
func EmitPointerPack(builder llvm.Builder, mod llvm.Module, needsStackObjects bool, values []llvm.Value) llvm.Value { func EmitPointerPack(builder llvm.Builder, mod llvm.Module, config *compileopts.Config, values []llvm.Value) llvm.Value {
ctx := mod.Context() ctx := mod.Context()
targetData := llvm.NewTargetData(mod.DataLayout()) targetData := llvm.NewTargetData(mod.DataLayout())
i8ptrType := llvm.PointerType(mod.Context().Int8Type(), 0) i8ptrType := llvm.PointerType(mod.Context().Int8Type(), 0)
@@ -100,7 +101,7 @@ func EmitPointerPack(builder llvm.Builder, mod llvm.Module, needsStackObjects bo
llvm.Undef(i8ptrType), // unused context parameter llvm.Undef(i8ptrType), // unused context parameter
llvm.ConstPointerNull(i8ptrType), // coroutine handle llvm.ConstPointerNull(i8ptrType), // coroutine handle
}, "") }, "")
if needsStackObjects { if config.NeedsStackObjects() {
trackPointer := mod.NamedFunction("runtime.trackPointer") trackPointer := mod.NamedFunction("runtime.trackPointer")
builder.CreateCall(trackPointer, []llvm.Value{ builder.CreateCall(trackPointer, []llvm.Value{
packedHeapAlloc, packedHeapAlloc,
-3
View File
@@ -150,9 +150,6 @@ func (s *stdSizes) Sizeof(T types.Type) int64 {
return s.PtrSize * 2 return s.PtrSize * 2
case *types.Pointer: case *types.Pointer:
return s.PtrSize return s.PtrSize
case *types.Signature:
// Func values in TinyGo are two words in size.
return s.PtrSize * 2
default: default:
panic("unknown type: " + t.String()) panic("unknown type: " + t.String())
} }
+32 -348
View File
@@ -15,309 +15,6 @@ import (
"tinygo.org/x/go-llvm" "tinygo.org/x/go-llvm"
) )
// 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 if //go:linkname is not
// present.
type functionInfo struct {
module string // go:wasm-module
importName string // go:linkname, go:export - The name the developer assigns
linkName string // go:linkname, go:export - The name that we map for the particular module -> importName
exported bool // go:export, CGo
nobounds bool // go:nobounds
variadic bool // go:variadic (CGo only)
inline inlineType // go:inline
}
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
)
// 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)
llvmFn := c.mod.NamedFunction(info.linkName)
if !llvmFn.IsNil() {
return llvmFn
}
var retType llvm.Type
if fn.Signature.Results() == nil {
retType = c.ctx.VoidType()
} else if fn.Signature.Results().Len() == 1 {
retType = c.getLLVMType(fn.Signature.Results().At(0).Type())
} else {
results := make([]llvm.Type, 0, fn.Signature.Results().Len())
for i := 0; i < fn.Signature.Results().Len(); i++ {
results = append(results, c.getLLVMType(fn.Signature.Results().At(i).Type()))
}
retType = c.ctx.StructType(results, false)
}
var paramInfos []paramInfo
for _, param := range getParams(fn.Signature) {
paramType := c.getLLVMType(param.Type())
paramFragmentInfos := c.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, info.variadic)
llvmFn = llvm.AddFunction(c.mod, info.linkName, fnType)
if strings.HasPrefix(c.Triple, "wasm") {
// C functions without prototypes like this:
// void foo();
// are actually variadic functions. However, it appears that it has been
// decided in WebAssembly that such prototype-less functions are not
// allowed in WebAssembly.
// In C, this can only happen when there are zero parameters, hence this
// check here. For more information:
// https://reviews.llvm.org/D48443
// https://github.com/WebAssembly/tool-conventions/issues/16
if info.variadic && len(fn.Params) == 0 {
attr := c.ctx.CreateStringAttribute("no-prototype", "")
llvmFn.AddFunctionAttr(attr)
}
}
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)
}
}
// Set a number of function or parameter attributes, depending on the
// function. These functions are runtime functions that are known to have
// certain attributes that might not be inferred by the compiler.
switch info.linkName {
case "abort":
// On *nix systems, the "abort" functuion in libc is used to handle fatal panics.
// Mark it as noreturn so LLVM can optimize away code.
llvmFn.AddFunctionAttr(c.ctx.CreateEnumAttribute(llvm.AttributeKindID("noreturn"), 0))
case "runtime.alloc":
// Tell the optimizer that runtime.alloc is an allocator, meaning that it
// returns values that are never null and never alias to an existing value.
for _, attrName := range []string{"noalias", "nonnull"} {
llvmFn.AddAttributeAtIndex(0, c.ctx.CreateEnumAttribute(llvm.AttributeKindID(attrName), 0))
}
case "runtime.sliceAppend":
// Appending a slice will only read the to-be-appended slice, it won't
// be modified.
llvmFn.AddAttributeAtIndex(2, c.ctx.CreateEnumAttribute(llvm.AttributeKindID("nocapture"), 0))
llvmFn.AddAttributeAtIndex(2, c.ctx.CreateEnumAttribute(llvm.AttributeKindID("readonly"), 0))
case "runtime.sliceCopy":
// Copying a slice won't capture any of the parameters.
llvmFn.AddAttributeAtIndex(1, c.ctx.CreateEnumAttribute(llvm.AttributeKindID("writeonly"), 0))
llvmFn.AddAttributeAtIndex(1, c.ctx.CreateEnumAttribute(llvm.AttributeKindID("nocapture"), 0))
llvmFn.AddAttributeAtIndex(2, c.ctx.CreateEnumAttribute(llvm.AttributeKindID("readonly"), 0))
llvmFn.AddAttributeAtIndex(2, c.ctx.CreateEnumAttribute(llvm.AttributeKindID("nocapture"), 0))
case "runtime.trackPointer":
// This function is necessary for tracking pointers on the stack in a
// portable way (see gc_stack_portable.go). Indicate to the optimizer
// that the only thing we'll do is read the pointer.
llvmFn.AddAttributeAtIndex(1, c.ctx.CreateEnumAttribute(llvm.AttributeKindID("nocapture"), 0))
llvmFn.AddAttributeAtIndex(1, c.ctx.CreateEnumAttribute(llvm.AttributeKindID("readonly"), 0))
}
// 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 != "" {
// We need to add the wasm-import-module and the wasm-import-name
wasmImportModuleAttr := c.ctx.CreateStringAttribute("wasm-import-module", info.module)
llvmFn.AddFunctionAttr(wasmImportModuleAttr)
// Add the Wasm Import Name, if we are a named wasm import
if info.importName != "" {
wasmImportNameAttr := c.ctx.CreateStringAttribute("wasm-import-name", info.importName)
llvmFn.AddFunctionAttr(wasmImportNameAttr)
}
}
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)
}
}
}
// Synthetic functions are functions that do not appear in the source code,
// they are artificially constructed. Usually they are wrapper functions
// that are not referenced anywhere except in a SSA call instruction so
// should be created right away.
// The exception is the package initializer, which does appear in the
// *ssa.Package members and so shouldn't be created here.
if fn.Synthetic != "" && fn.Synthetic != "package initializer" {
irbuilder := c.ctx.NewBuilder()
b := newBuilder(c, irbuilder, fn)
b.createFunction()
irbuilder.Dispose()
llvmFn.SetLinkage(llvm.LinkOnceODRLinkage)
llvmFn.SetUnnamedAddr(true)
}
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) {
if f.Syntax() == nil {
return
}
if decl, ok := f.Syntax().(*ast.FuncDecl); ok && decl.Doc != nil {
// Our importName for a wasm module (if we are compiling to wasm), or llvm link name
var importName string
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
}
importName = 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
}
case "//go:variadic":
// The //go:variadic pragma is emitted by the CGo preprocessing
// pass for C variadic functions. This includes both explicit
// (with ...) and implicit (no parameters in signature)
// functions.
if strings.HasPrefix(f.Name(), "C.") {
// This prefix cannot naturally be created, it must have
// been created as a result of CGo preprocessing.
info.variadic = true
}
}
}
// Set the importName for our exported function if we have one
if importName != "" {
if info.module == "" {
info.linkName = importName
} else {
// WebAssembly import
info.importName = importName
}
}
}
}
// getParams returns the function parameters, including the receiver at the
// start. This is an alternative to the Params member of *ssa.Function, which is
// not yet populated when the package has not yet been built.
func getParams(sig *types.Signature) []*types.Var {
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))
}
return params
}
// globalInfo contains some information about a specific global. By default, // globalInfo contains some information about a specific global. By default,
// linkName is equal to .RelString(nil) on a global and extern is false, but for // linkName is equal to .RelString(nil) on a global and extern is false, but for
// some symbols this is different (due to //go:extern for example). // some symbols this is different (due to //go:extern for example).
@@ -329,22 +26,25 @@ type globalInfo struct {
// loadASTComments loads comments on globals from the AST, for use later in the // loadASTComments loads comments on globals from the AST, for use later in the
// program. In particular, they are required for //go:extern pragmas on globals. // program. In particular, they are required for //go:extern pragmas on globals.
func (c *compilerContext) loadASTComments(pkg *loader.Package) { func (c *compilerContext) loadASTComments(lprogram *loader.Program) {
for _, file := range pkg.Files { c.astComments = map[string]*ast.CommentGroup{}
for _, decl := range file.Decls { for _, pkgInfo := range lprogram.Sorted() {
switch decl := decl.(type) { for _, file := range pkgInfo.Files {
case *ast.GenDecl: for _, decl := range file.Decls {
switch decl.Tok { switch decl := decl.(type) {
case token.VAR: case *ast.GenDecl:
if len(decl.Specs) != 1 { switch decl.Tok {
continue case token.VAR:
} if len(decl.Specs) != 1 {
for _, spec := range decl.Specs { continue
switch spec := spec.(type) { }
case *ast.ValueSpec: // decl.Tok == token.VAR for _, spec := range decl.Specs {
for _, name := range spec.Names { switch spec := spec.(type) {
id := pkg.Pkg.Path() + "." + name.Name case *ast.ValueSpec: // decl.Tok == token.VAR
c.astComments[id] = decl.Doc for _, name := range spec.Names {
id := pkgInfo.Pkg.Path() + "." + name.Name
c.astComments[id] = decl.Doc
}
} }
} }
} }
@@ -363,26 +63,30 @@ func (c *compilerContext) getGlobal(g *ssa.Global) llvm.Value {
typ := g.Type().(*types.Pointer).Elem() typ := g.Type().(*types.Pointer).Elem()
llvmType := c.getLLVMType(typ) llvmType := c.getLLVMType(typ)
llvmGlobal = llvm.AddGlobal(c.mod, llvmType, info.linkName) 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. // Set alignment from the //go:align comment.
var alignInBits uint32 var alignInBits uint32
alignment := c.targetData.ABITypeAlignment(llvmType) if info.align < 0 || info.align&(info.align-1) != 0 {
if info.align > alignment {
alignment = info.align
}
if alignment <= 0 || alignment&(alignment-1) != 0 {
// Check for power-of-two (or 0). // Check for power-of-two (or 0).
// See: https://stackoverflow.com/a/108360 // See: https://stackoverflow.com/a/108360
c.addError(g.Pos(), "global variable alignment must be a positive power of two") c.addError(g.Pos(), "global variable alignment must be a positive power of two")
} else { } else {
// Set the alignment only when it is a power of two. // Set the alignment only when it is a power of two.
alignInBits = uint32(alignment) ^ uint32(alignment-1) alignInBits = uint32(info.align) ^ uint32(info.align-1)
llvmGlobal.SetAlignment(alignment) if info.align > c.targetData.ABITypeAlignment(llvmType) {
llvmGlobal.SetAlignment(info.align)
}
} }
if c.Debug && !info.extern { if c.Debug() && !info.extern {
// Add debug info. // Add debug info.
pos := c.program.Fset.Position(g.Pos()) // TODO: this should be done for every global in the program, not just
// the ones that are referenced from some code.
pos := c.ir.Program.Fset.Position(g.Pos())
diglobal := c.dibuilder.CreateGlobalVariableExpression(c.difiles[pos.Filename], llvm.DIGlobalVariableExpression{ diglobal := c.dibuilder.CreateGlobalVariableExpression(c.difiles[pos.Filename], llvm.DIGlobalVariableExpression{
Name: g.RelString(nil), Name: g.RelString(nil),
LinkageName: info.linkName, LinkageName: info.linkName,
@@ -441,23 +145,3 @@ func (info *globalInfo) parsePragmas(doc *ast.CommentGroup) {
} }
} }
} }
// Get all methods of a type.
func getAllMethods(prog *ssa.Program, typ types.Type) []*types.Selection {
ms := prog.MethodSets.MethodSet(typ)
methods := make([]*types.Selection, ms.Len())
for i := 0; i < ms.Len(); i++ {
methods[i] = ms.At(i)
}
return methods
}
// Return true if this package imports "unsafe", false otherwise.
func hasUnsafeImport(pkg *types.Package) bool {
for _, imp := range pkg.Imports() {
if imp == types.Unsafe {
return true
}
}
return false
}
+8 -8
View File
@@ -16,8 +16,8 @@ func (b *builder) createSyscall(call *ssa.CallCommon) (llvm.Value, error) {
num := b.getValue(call.Args[0]) num := b.getValue(call.Args[0])
var syscallResult llvm.Value var syscallResult llvm.Value
switch { switch {
case b.GOARCH == "amd64": case b.GOARCH() == "amd64":
if b.GOOS == "darwin" { if b.GOOS() == "darwin" {
// Darwin adds this magic number to system call numbers: // Darwin adds this magic number to system call numbers:
// //
// > Syscall classes for 64-bit system call entry. // > Syscall classes for 64-bit system call entry.
@@ -58,7 +58,7 @@ func (b *builder) createSyscall(call *ssa.CallCommon) (llvm.Value, error) {
fnType := llvm.FunctionType(b.uintptrType, argTypes, false) fnType := llvm.FunctionType(b.uintptrType, argTypes, false)
target := llvm.InlineAsm(fnType, "syscall", constraints, true, false, llvm.InlineAsmDialectIntel) target := llvm.InlineAsm(fnType, "syscall", constraints, true, false, llvm.InlineAsmDialectIntel)
syscallResult = b.CreateCall(target, args, "") syscallResult = b.CreateCall(target, args, "")
case b.GOARCH == "386" && b.GOOS == "linux": case b.GOARCH() == "386" && b.GOOS() == "linux":
// Sources: // Sources:
// syscall(2) man page // syscall(2) man page
// https://stackoverflow.com/a/2538212 // https://stackoverflow.com/a/2538212
@@ -84,7 +84,7 @@ func (b *builder) createSyscall(call *ssa.CallCommon) (llvm.Value, error) {
fnType := llvm.FunctionType(b.uintptrType, argTypes, false) fnType := llvm.FunctionType(b.uintptrType, argTypes, false)
target := llvm.InlineAsm(fnType, "int 0x80", constraints, true, false, llvm.InlineAsmDialectIntel) target := llvm.InlineAsm(fnType, "int 0x80", constraints, true, false, llvm.InlineAsmDialectIntel)
syscallResult = b.CreateCall(target, args, "") syscallResult = b.CreateCall(target, args, "")
case b.GOARCH == "arm" && b.GOOS == "linux": case b.GOARCH() == "arm" && b.GOOS() == "linux":
// Implement the EABI system call convention for Linux. // Implement the EABI system call convention for Linux.
// Source: syscall(2) man page. // Source: syscall(2) man page.
args := []llvm.Value{} args := []llvm.Value{}
@@ -116,7 +116,7 @@ func (b *builder) createSyscall(call *ssa.CallCommon) (llvm.Value, error) {
fnType := llvm.FunctionType(b.uintptrType, argTypes, false) fnType := llvm.FunctionType(b.uintptrType, argTypes, false)
target := llvm.InlineAsm(fnType, "svc #0", constraints, true, false, 0) target := llvm.InlineAsm(fnType, "svc #0", constraints, true, false, 0)
syscallResult = b.CreateCall(target, args, "") syscallResult = b.CreateCall(target, args, "")
case b.GOARCH == "arm64" && b.GOOS == "linux": case b.GOARCH() == "arm64" && b.GOOS() == "linux":
// Source: syscall(2) man page. // Source: syscall(2) man page.
args := []llvm.Value{} args := []llvm.Value{}
argTypes := []llvm.Type{} argTypes := []llvm.Type{}
@@ -149,9 +149,9 @@ func (b *builder) createSyscall(call *ssa.CallCommon) (llvm.Value, error) {
target := llvm.InlineAsm(fnType, "svc #0", constraints, true, false, 0) target := llvm.InlineAsm(fnType, "svc #0", constraints, true, false, 0)
syscallResult = b.CreateCall(target, args, "") syscallResult = b.CreateCall(target, args, "")
default: default:
return llvm.Value{}, b.makeError(call.Pos(), "unknown GOOS/GOARCH for syscall: "+b.GOOS+"/"+b.GOARCH) return llvm.Value{}, b.makeError(call.Pos(), "unknown GOOS/GOARCH for syscall: "+b.GOOS()+"/"+b.GOARCH())
} }
switch b.GOOS { switch b.GOOS() {
case "linux", "freebsd": case "linux", "freebsd":
// Return values: r0, r1 uintptr, err Errno // Return values: r0, r1 uintptr, err Errno
// Pseudocode: // Pseudocode:
@@ -187,6 +187,6 @@ func (b *builder) createSyscall(call *ssa.CallCommon) (llvm.Value, error) {
retval = b.CreateInsertValue(retval, errResult, 2, "") retval = b.CreateInsertValue(retval, errResult, 2, "")
return retval, nil return retval, nil
default: default:
return llvm.Value{}, b.makeError(call.Pos(), "unknown GOOS/GOARCH for syscall: "+b.GOOS+"/"+b.GOARCH) return llvm.Value{}, b.makeError(call.Pos(), "unknown GOOS/GOARCH for syscall: "+b.GOOS()+"/"+b.GOARCH())
} }
} }
-57
View File
@@ -1,57 +0,0 @@
package main
// Basic tests that don't need to be split into a separate file.
func addInt(x, y int) int {
return x + y
}
func equalInt(x, y int) bool {
return x == y
}
func floatEQ(x, y float32) bool {
return x == y
}
func floatNE(x, y float32) bool {
return x != y
}
func floatLower(x, y float32) bool {
return x < y
}
func floatLowerEqual(x, y float32) bool {
return x <= y
}
func floatGreater(x, y float32) bool {
return x > y
}
func floatGreaterEqual(x, y float32) bool {
return x >= y
}
func complexReal(x complex64) float32 {
return real(x)
}
func complexImag(x complex64) float32 {
return imag(x)
}
func complexAdd(x, y complex64) complex64 {
return x + y
}
func complexSub(x, y complex64) complex64 {
return x - y
}
func complexMul(x, y complex64) complex64 {
return x * y
}
// TODO: complexDiv (requires runtime call)
-100
View File
@@ -1,100 +0,0 @@
; ModuleID = 'basic.go'
source_filename = "basic.go"
target datalayout = "e-m:e-p:32:32-p270:32:32-p271:32:32-p272:64:64-f64:32:64-f80:32-n8:16:32-S128"
target triple = "i686--linux"
declare noalias nonnull i8* @runtime.alloc(i32, i8*, i8*)
define hidden void @main.init(i8* %context, i8* %parentHandle) unnamed_addr {
entry:
ret void
}
define hidden i32 @main.addInt(i32 %x, i32 %y, i8* %context, i8* %parentHandle) unnamed_addr {
entry:
%0 = add i32 %x, %y
ret i32 %0
}
define hidden i1 @main.equalInt(i32 %x, i32 %y, i8* %context, i8* %parentHandle) unnamed_addr {
entry:
%0 = icmp eq i32 %x, %y
ret i1 %0
}
define hidden i1 @main.floatEQ(float %x, float %y, i8* %context, i8* %parentHandle) unnamed_addr {
entry:
%0 = fcmp oeq float %x, %y
ret i1 %0
}
define hidden i1 @main.floatNE(float %x, float %y, i8* %context, i8* %parentHandle) unnamed_addr {
entry:
%0 = fcmp une float %x, %y
ret i1 %0
}
define hidden i1 @main.floatLower(float %x, float %y, i8* %context, i8* %parentHandle) unnamed_addr {
entry:
%0 = fcmp olt float %x, %y
ret i1 %0
}
define hidden i1 @main.floatLowerEqual(float %x, float %y, i8* %context, i8* %parentHandle) unnamed_addr {
entry:
%0 = fcmp ole float %x, %y
ret i1 %0
}
define hidden i1 @main.floatGreater(float %x, float %y, i8* %context, i8* %parentHandle) unnamed_addr {
entry:
%0 = fcmp ogt float %x, %y
ret i1 %0
}
define hidden i1 @main.floatGreaterEqual(float %x, float %y, i8* %context, i8* %parentHandle) unnamed_addr {
entry:
%0 = fcmp oge float %x, %y
ret i1 %0
}
define hidden float @main.complexReal(float %x.r, float %x.i, i8* %context, i8* %parentHandle) unnamed_addr {
entry:
ret float %x.r
}
define hidden float @main.complexImag(float %x.r, float %x.i, i8* %context, i8* %parentHandle) unnamed_addr {
entry:
ret float %x.i
}
define hidden { float, float } @main.complexAdd(float %x.r, float %x.i, float %y.r, float %y.i, i8* %context, i8* %parentHandle) unnamed_addr {
entry:
%0 = fadd float %x.r, %y.r
%1 = fadd float %x.i, %y.i
%2 = insertvalue { float, float } undef, float %0, 0
%3 = insertvalue { float, float } %2, float %1, 1
ret { float, float } %3
}
define hidden { float, float } @main.complexSub(float %x.r, float %x.i, float %y.r, float %y.i, i8* %context, i8* %parentHandle) unnamed_addr {
entry:
%0 = fsub float %x.r, %y.r
%1 = fsub float %x.i, %y.i
%2 = insertvalue { float, float } undef, float %0, 0
%3 = insertvalue { float, float } %2, float %1, 1
ret { float, float } %3
}
define hidden { float, float } @main.complexMul(float %x.r, float %x.i, float %y.r, float %y.i, i8* %context, i8* %parentHandle) unnamed_addr {
entry:
%0 = fmul float %x.r, %y.r
%1 = fmul float %x.i, %y.i
%2 = fsub float %0, %1
%3 = fmul float %x.r, %y.i
%4 = fmul float %x.i, %y.r
%5 = fadd float %3, %4
%6 = insertvalue { float, float } undef, float %2, 0
%7 = insertvalue { float, float } %6, float %5, 1
ret { float, float } %7
}
-39
View File
@@ -1,39 +0,0 @@
package main
// Test converting floats to ints.
func f32tou32(v float32) uint32 {
return uint32(v)
}
func maxu32f() float32 {
return float32(^uint32(0))
}
func maxu32tof32() uint32 {
f := float32(^uint32(0))
return uint32(f)
}
func inftoi32() (uint32, uint32, int32, int32) {
inf := 1.0
inf /= 0.0
return uint32(inf), uint32(-inf), int32(inf), int32(-inf)
}
func u32tof32tou32(v uint32) uint32 {
return uint32(float32(v))
}
func f32tou32tof32(v float32) float32 {
return float32(uint32(v))
}
func f32tou8(v float32) uint8 {
return uint8(v)
}
func f32toi8(v float32) int8 {
return int8(v)
}
-82
View File
@@ -1,82 +0,0 @@
; ModuleID = 'float.go'
source_filename = "float.go"
target datalayout = "e-m:e-p:32:32-p270:32:32-p271:32:32-p272:64:64-f64:32:64-f80:32-n8:16:32-S128"
target triple = "i686--linux"
declare noalias nonnull i8* @runtime.alloc(i32, i8*, i8*)
define hidden void @main.init(i8* %context, i8* %parentHandle) unnamed_addr {
entry:
ret void
}
define hidden i32 @main.f32tou32(float %v, i8* %context, i8* %parentHandle) unnamed_addr {
entry:
%positive = fcmp oge float %v, 0.000000e+00
%withinmax = fcmp ole float %v, 0x41EFFFFFC0000000
%inbounds = and i1 %positive, %withinmax
%saturated = sext i1 %positive to i32
%normal = fptoui float %v to i32
%0 = select i1 %inbounds, i32 %normal, i32 %saturated
ret i32 %0
}
define hidden float @main.maxu32f(i8* %context, i8* %parentHandle) unnamed_addr {
entry:
ret float 0x41F0000000000000
}
define hidden i32 @main.maxu32tof32(i8* %context, i8* %parentHandle) unnamed_addr {
entry:
ret i32 -1
}
define hidden { i32, i32, i32, i32 } @main.inftoi32(i8* %context, i8* %parentHandle) unnamed_addr {
entry:
ret { i32, i32, i32, i32 } { i32 -1, i32 0, i32 2147483647, i32 -2147483648 }
}
define hidden i32 @main.u32tof32tou32(i32 %v, i8* %context, i8* %parentHandle) unnamed_addr {
entry:
%0 = uitofp i32 %v to float
%withinmax = fcmp ole float %0, 0x41EFFFFFC0000000
%normal = fptoui float %0 to i32
%1 = select i1 %withinmax, i32 %normal, i32 -1
ret i32 %1
}
define hidden float @main.f32tou32tof32(float %v, i8* %context, i8* %parentHandle) unnamed_addr {
entry:
%positive = fcmp oge float %v, 0.000000e+00
%withinmax = fcmp ole float %v, 0x41EFFFFFC0000000
%inbounds = and i1 %positive, %withinmax
%saturated = sext i1 %positive to i32
%normal = fptoui float %v to i32
%0 = select i1 %inbounds, i32 %normal, i32 %saturated
%1 = uitofp i32 %0 to float
ret float %1
}
define hidden i8 @main.f32tou8(float %v, i8* %context, i8* %parentHandle) unnamed_addr {
entry:
%positive = fcmp oge float %v, 0.000000e+00
%withinmax = fcmp ole float %v, 2.550000e+02
%inbounds = and i1 %positive, %withinmax
%saturated = sext i1 %positive to i8
%normal = fptoui float %v to i8
%0 = select i1 %inbounds, i8 %normal, i8 %saturated
ret i8 %0
}
define hidden i8 @main.f32toi8(float %v, i8* %context, i8* %parentHandle) unnamed_addr {
entry:
%abovemin = fcmp oge float %v, -1.280000e+02
%belowmax = fcmp ole float %v, 1.270000e+02
%inbounds = and i1 %abovemin, %belowmax
%saturated = select i1 %abovemin, i8 127, i8 -128
%isnan = fcmp uno float %v, 0.000000e+00
%remapped = select i1 %isnan, i8 0, i8 %saturated
%normal = fptosi float %v to i8
%0 = select i1 %inbounds, i8 %normal, i8 %remapped
ret i8 %0
}
-12
View File
@@ -1,12 +0,0 @@
package main
func foo(callback func(int)) {
callback(3)
}
func bar() {
foo(someFunc)
}
func someFunc(int) {
}
-47
View File
@@ -1,47 +0,0 @@
; ModuleID = 'func.go'
source_filename = "func.go"
target datalayout = "e-m:e-p:32:32-p270:32:32-p271:32:32-p272:64:64-f64:32:64-f80:32-n8:16:32-S128"
target triple = "i686--linux"
%runtime.funcValueWithSignature = type { i32, i8* }
@"reflect/types.funcid:func:{basic:int}{}" = external constant i8
@"main.someFunc$withSignature" = linkonce_odr constant %runtime.funcValueWithSignature { i32 ptrtoint (void (i32, i8*, i8*)* @main.someFunc to i32), i8* @"reflect/types.funcid:func:{basic:int}{}" }
declare noalias nonnull i8* @runtime.alloc(i32, i8*, i8*)
define hidden void @main.init(i8* %context, i8* %parentHandle) unnamed_addr {
entry:
ret void
}
define hidden void @main.foo(i8* %callback.context, i32 %callback.funcptr, i8* %context, i8* %parentHandle) unnamed_addr {
entry:
%0 = call i32 @runtime.getFuncPtr(i8* %callback.context, i32 %callback.funcptr, i8* nonnull @"reflect/types.funcid:func:{basic:int}{}", i8* undef, i8* null)
%1 = icmp eq i32 %0, 0
br i1 %1, label %fpcall.throw, label %fpcall.next
fpcall.throw: ; preds = %entry
call void @runtime.nilPanic(i8* undef, i8* null)
unreachable
fpcall.next: ; preds = %entry
%2 = inttoptr i32 %0 to void (i32, i8*, i8*)*
call void %2(i32 3, i8* %callback.context, i8* undef)
ret void
}
declare i32 @runtime.getFuncPtr(i8*, i32, i8* dereferenceable_or_null(1), i8*, i8*)
declare void @runtime.nilPanic(i8*, i8*)
define hidden void @main.bar(i8* %context, i8* %parentHandle) unnamed_addr {
entry:
call void @main.foo(i8* undef, i32 ptrtoint (%runtime.funcValueWithSignature* @"main.someFunc$withSignature" to i32), i8* undef, i8* undef)
ret void
}
define hidden void @main.someFunc(i32 %arg0, i8* %context, i8* %parentHandle) unnamed_addr {
entry:
ret void
}
-54
View File
@@ -1,54 +0,0 @@
// This file tests interface types and interface builtins.
package main
// Test interface construction.
func simpleType() interface{} {
return 0
}
func pointerType() interface{} {
// Pointers have an element type, in this case int.
var v *int
return v
}
func interfaceType() interface{} {
// Interfaces can exist in interfaces, but only indirectly (through
// pointers).
var v *error
return v
}
func anonymousInterfaceType() interface{} {
var v *interface {
String() string
}
return v
}
// Test interface builtins.
func isInt(itf interface{}) bool {
_, ok := itf.(int)
return ok
}
func isError(itf interface{}) bool {
// Interface assert on (builtin) named interface type.
_, ok := itf.(error)
return ok
}
func isStringer(itf interface{}) bool {
// Interface assert on anonymous interface type.
_, ok := itf.(interface {
String() string
})
return ok
}
func callErrorMethod(itf error) string {
return itf.Error()
}
-101
View File
@@ -1,101 +0,0 @@
; ModuleID = 'interface.go'
source_filename = "interface.go"
target datalayout = "e-m:e-p:32:32-p270:32:32-p271:32:32-p272:64:64-f64:32:64-f80:32-n8:16:32-S128"
target triple = "i686--linux"
%runtime.typecodeID = type { %runtime.typecodeID*, i32, %runtime.interfaceMethodInfo*, %runtime.typecodeID* }
%runtime.interfaceMethodInfo = type { i8*, i32 }
%runtime._interface = type { i32, i8* }
%runtime._string = type { i8*, i32 }
@"reflect/types.type:basic:int" = linkonce_odr constant %runtime.typecodeID { %runtime.typecodeID* null, i32 0, %runtime.interfaceMethodInfo* null, %runtime.typecodeID* @"reflect/types.type:pointer:basic:int" }
@"reflect/types.type:pointer:basic:int" = linkonce_odr constant %runtime.typecodeID { %runtime.typecodeID* @"reflect/types.type:basic:int", i32 0, %runtime.interfaceMethodInfo* null, %runtime.typecodeID* null }
@"reflect/types.type:pointer:named:error" = linkonce_odr constant %runtime.typecodeID { %runtime.typecodeID* @"reflect/types.type:named:error", i32 0, %runtime.interfaceMethodInfo* null, %runtime.typecodeID* null }
@"reflect/types.type:named:error" = linkonce_odr constant %runtime.typecodeID { %runtime.typecodeID* @"reflect/types.type:interface:{Error:func:{}{basic:string}}", i32 0, %runtime.interfaceMethodInfo* null, %runtime.typecodeID* @"reflect/types.type:pointer:named:error" }
@"reflect/types.type:interface:{Error:func:{}{basic:string}}" = linkonce_odr constant %runtime.typecodeID { %runtime.typecodeID* bitcast ([1 x i8*]* @"reflect/types.interface:interface{Error() string}$interface" to %runtime.typecodeID*), i32 0, %runtime.interfaceMethodInfo* null, %runtime.typecodeID* @"reflect/types.type:pointer:interface:{Error:func:{}{basic:string}}" }
@"func Error() string" = external constant i8
@"reflect/types.interface:interface{Error() string}$interface" = linkonce_odr constant [1 x i8*] [i8* @"func Error() string"]
@"reflect/types.type:pointer:interface:{Error:func:{}{basic:string}}" = linkonce_odr constant %runtime.typecodeID { %runtime.typecodeID* @"reflect/types.type:interface:{Error:func:{}{basic:string}}", i32 0, %runtime.interfaceMethodInfo* null, %runtime.typecodeID* null }
@"reflect/types.type:pointer:interface:{String:func:{}{basic:string}}" = linkonce_odr constant %runtime.typecodeID { %runtime.typecodeID* @"reflect/types.type:interface:{String:func:{}{basic:string}}", i32 0, %runtime.interfaceMethodInfo* null, %runtime.typecodeID* null }
@"reflect/types.type:interface:{String:func:{}{basic:string}}" = linkonce_odr constant %runtime.typecodeID { %runtime.typecodeID* bitcast ([1 x i8*]* @"reflect/types.interface:interface{String() string}$interface" to %runtime.typecodeID*), i32 0, %runtime.interfaceMethodInfo* null, %runtime.typecodeID* @"reflect/types.type:pointer:interface:{String:func:{}{basic:string}}" }
@"func String() string" = external constant i8
@"reflect/types.interface:interface{String() string}$interface" = linkonce_odr constant [1 x i8*] [i8* @"func String() string"]
@"reflect/types.typeid:basic:int" = external constant i8
@"error$interface" = linkonce_odr constant [1 x i8*] [i8* @"func Error() string"]
declare noalias nonnull i8* @runtime.alloc(i32, i8*, i8*)
define hidden void @main.init(i8* %context, i8* %parentHandle) unnamed_addr {
entry:
ret void
}
define hidden %runtime._interface @main.simpleType(i8* %context, i8* %parentHandle) unnamed_addr {
entry:
ret %runtime._interface { i32 ptrtoint (%runtime.typecodeID* @"reflect/types.type:basic:int" to i32), i8* null }
}
define hidden %runtime._interface @main.pointerType(i8* %context, i8* %parentHandle) unnamed_addr {
entry:
ret %runtime._interface { i32 ptrtoint (%runtime.typecodeID* @"reflect/types.type:pointer:basic:int" to i32), i8* null }
}
define hidden %runtime._interface @main.interfaceType(i8* %context, i8* %parentHandle) unnamed_addr {
entry:
ret %runtime._interface { i32 ptrtoint (%runtime.typecodeID* @"reflect/types.type:pointer:named:error" to i32), i8* null }
}
define hidden %runtime._interface @main.anonymousInterfaceType(i8* %context, i8* %parentHandle) unnamed_addr {
entry:
ret %runtime._interface { i32 ptrtoint (%runtime.typecodeID* @"reflect/types.type:pointer:interface:{String:func:{}{basic:string}}" to i32), i8* null }
}
define hidden i1 @main.isInt(i32 %itf.typecode, i8* %itf.value, i8* %context, i8* %parentHandle) unnamed_addr {
entry:
%typecode = call i1 @runtime.typeAssert(i32 %itf.typecode, i8* nonnull @"reflect/types.typeid:basic:int", i8* undef, i8* null)
br i1 %typecode, label %typeassert.ok, label %typeassert.next
typeassert.ok: ; preds = %entry
br label %typeassert.next
typeassert.next: ; preds = %typeassert.ok, %entry
ret i1 %typecode
}
declare i1 @runtime.typeAssert(i32, i8* dereferenceable_or_null(1), i8*, i8*)
define hidden i1 @main.isError(i32 %itf.typecode, i8* %itf.value, i8* %context, i8* %parentHandle) unnamed_addr {
entry:
%0 = call i1 @runtime.interfaceImplements(i32 %itf.typecode, i8** getelementptr inbounds ([1 x i8*], [1 x i8*]* @"error$interface", i32 0, i32 0), i8* undef, i8* null)
br i1 %0, label %typeassert.ok, label %typeassert.next
typeassert.ok: ; preds = %entry
br label %typeassert.next
typeassert.next: ; preds = %typeassert.ok, %entry
ret i1 %0
}
declare i1 @runtime.interfaceImplements(i32, i8** dereferenceable_or_null(4), i8*, i8*)
define hidden i1 @main.isStringer(i32 %itf.typecode, i8* %itf.value, i8* %context, i8* %parentHandle) unnamed_addr {
entry:
%0 = call i1 @runtime.interfaceImplements(i32 %itf.typecode, i8** getelementptr inbounds ([1 x i8*], [1 x i8*]* @"reflect/types.interface:interface{String() string}$interface", i32 0, i32 0), i8* undef, i8* null)
br i1 %0, label %typeassert.ok, label %typeassert.next
typeassert.ok: ; preds = %entry
br label %typeassert.next
typeassert.next: ; preds = %typeassert.ok, %entry
ret i1 %0
}
define hidden %runtime._string @main.callErrorMethod(i32 %itf.typecode, i8* %itf.value, i8* %context, i8* %parentHandle) unnamed_addr {
entry:
%invoke.func = call i32 @runtime.interfaceMethod(i32 %itf.typecode, i8** getelementptr inbounds ([1 x i8*], [1 x i8*]* @"error$interface", i32 0, i32 0), i8* nonnull @"func Error() string", i8* undef, i8* null)
%invoke.func.cast = inttoptr i32 %invoke.func to %runtime._string (i8*, i8*, i8*)*
%0 = call %runtime._string %invoke.func.cast(i8* %itf.value, i8* undef, i8* undef)
ret %runtime._string %0
}
declare i32 @runtime.interfaceMethod(i32, i8** dereferenceable_or_null(4), i8* dereferenceable_or_null(1), i8*, i8*)
-41
View File
@@ -1,41 +0,0 @@
package main
// This file tests various operations on pointers, such as pointer arithmetic
// and dereferencing pointers.
import "unsafe"
// Dereference pointers.
func pointerDerefZero(x *[0]int) [0]int {
return *x // This is a no-op, there is nothing to load.
}
// Unsafe pointer casts, they are sometimes a no-op.
func pointerCastFromUnsafe(x unsafe.Pointer) *int {
return (*int)(x)
}
func pointerCastToUnsafe(x *int) unsafe.Pointer {
return unsafe.Pointer(x)
}
func pointerCastToUnsafeNoop(x *byte) unsafe.Pointer {
return unsafe.Pointer(x)
}
// The compiler has support for a few special cast+add patterns that are
// transformed into a single GEP.
func pointerUnsafeGEPFixedOffset(ptr *byte) *byte {
return (*byte)(unsafe.Pointer(uintptr(unsafe.Pointer(ptr)) + 10))
}
func pointerUnsafeGEPByteOffset(ptr *byte, offset uintptr) *byte {
return (*byte)(unsafe.Pointer(uintptr(unsafe.Pointer(ptr)) + offset))
}
func pointerUnsafeGEPIntOffset(ptr *int32, offset uintptr) *int32 {
return (*int32)(unsafe.Pointer(uintptr(unsafe.Pointer(ptr)) + offset*4))
}
-51
View File
@@ -1,51 +0,0 @@
; ModuleID = 'pointer.go'
source_filename = "pointer.go"
target datalayout = "e-m:e-p:32:32-p270:32:32-p271:32:32-p272:64:64-f64:32:64-f80:32-n8:16:32-S128"
target triple = "i686--linux"
declare noalias nonnull i8* @runtime.alloc(i32, i8*, i8*)
define hidden void @main.init(i8* %context, i8* %parentHandle) unnamed_addr {
entry:
ret void
}
define hidden [0 x i32] @main.pointerDerefZero([0 x i32]* %x, i8* %context, i8* %parentHandle) unnamed_addr {
entry:
ret [0 x i32] zeroinitializer
}
define hidden i32* @main.pointerCastFromUnsafe(i8* %x, i8* %context, i8* %parentHandle) unnamed_addr {
entry:
%0 = bitcast i8* %x to i32*
ret i32* %0
}
define hidden i8* @main.pointerCastToUnsafe(i32* dereferenceable_or_null(4) %x, i8* %context, i8* %parentHandle) unnamed_addr {
entry:
%0 = bitcast i32* %x to i8*
ret i8* %0
}
define hidden i8* @main.pointerCastToUnsafeNoop(i8* dereferenceable_or_null(1) %x, i8* %context, i8* %parentHandle) unnamed_addr {
entry:
ret i8* %x
}
define hidden i8* @main.pointerUnsafeGEPFixedOffset(i8* dereferenceable_or_null(1) %ptr, i8* %context, i8* %parentHandle) unnamed_addr {
entry:
%0 = getelementptr inbounds i8, i8* %ptr, i32 10
ret i8* %0
}
define hidden i8* @main.pointerUnsafeGEPByteOffset(i8* dereferenceable_or_null(1) %ptr, i32 %offset, i8* %context, i8* %parentHandle) unnamed_addr {
entry:
%0 = getelementptr inbounds i8, i8* %ptr, i32 %offset
ret i8* %0
}
define hidden i32* @main.pointerUnsafeGEPIntOffset(i32* dereferenceable_or_null(4) %ptr, i32 %offset, i8* %context, i8* %parentHandle) unnamed_addr {
entry:
%0 = getelementptr i32, i32* %ptr, i32 %offset
ret i32* %0
}
-25
View File
@@ -1,25 +0,0 @@
package main
func sliceLen(ints []int) int {
return len(ints)
}
func sliceCap(ints []int) int {
return cap(ints)
}
func sliceElement(ints []int, index int) int {
return ints[index]
}
func sliceAppendValues(ints []int) []int {
return append(ints, 1, 2, 3)
}
func sliceAppendSlice(ints, added []int) []int {
return append(ints, added...)
}
func sliceCopy(dst, src []int) int {
return copy(dst, src)
}
-88
View File
@@ -1,88 +0,0 @@
; ModuleID = 'slice.go'
source_filename = "slice.go"
target datalayout = "e-m:e-p:32:32-p270:32:32-p271:32:32-p272:64:64-f64:32:64-f80:32-n8:16:32-S128"
target triple = "i686--linux"
declare noalias nonnull i8* @runtime.alloc(i32, i8*, i8*)
define hidden void @main.init(i8* %context, i8* %parentHandle) unnamed_addr {
entry:
ret void
}
define hidden i32 @main.sliceLen(i32* %ints.data, i32 %ints.len, i32 %ints.cap, i8* %context, i8* %parentHandle) unnamed_addr {
entry:
ret i32 %ints.len
}
define hidden i32 @main.sliceCap(i32* %ints.data, i32 %ints.len, i32 %ints.cap, i8* %context, i8* %parentHandle) unnamed_addr {
entry:
ret i32 %ints.cap
}
define hidden i32 @main.sliceElement(i32* %ints.data, i32 %ints.len, i32 %ints.cap, i32 %index, i8* %context, i8* %parentHandle) unnamed_addr {
entry:
%.not = icmp ult i32 %index, %ints.len
br i1 %.not, label %lookup.next, label %lookup.throw
lookup.throw: ; preds = %entry
call void @runtime.lookupPanic(i8* undef, i8* null)
unreachable
lookup.next: ; preds = %entry
%0 = getelementptr inbounds i32, i32* %ints.data, i32 %index
%1 = load i32, i32* %0, align 4
ret i32 %1
}
declare void @runtime.lookupPanic(i8*, i8*)
define hidden { i32*, i32, i32 } @main.sliceAppendValues(i32* %ints.data, i32 %ints.len, i32 %ints.cap, i8* %context, i8* %parentHandle) unnamed_addr {
entry:
%varargs = call i8* @runtime.alloc(i32 12, i8* undef, i8* null)
%0 = bitcast i8* %varargs to i32*
store i32 1, i32* %0, align 4
%1 = getelementptr inbounds i8, i8* %varargs, i32 4
%2 = bitcast i8* %1 to i32*
store i32 2, i32* %2, align 4
%3 = getelementptr inbounds i8, i8* %varargs, i32 8
%4 = bitcast i8* %3 to i32*
store i32 3, i32* %4, align 4
%append.srcPtr = bitcast i32* %ints.data to i8*
%append.new = call { i8*, i32, i32 } @runtime.sliceAppend(i8* %append.srcPtr, i8* nonnull %varargs, i32 %ints.len, i32 %ints.cap, i32 3, i32 4, i8* undef, i8* null)
%append.newPtr = extractvalue { i8*, i32, i32 } %append.new, 0
%append.newBuf = bitcast i8* %append.newPtr to i32*
%append.newLen = extractvalue { i8*, i32, i32 } %append.new, 1
%append.newCap = extractvalue { i8*, i32, i32 } %append.new, 2
%5 = insertvalue { i32*, i32, i32 } undef, i32* %append.newBuf, 0
%6 = insertvalue { i32*, i32, i32 } %5, i32 %append.newLen, 1
%7 = insertvalue { i32*, i32, i32 } %6, i32 %append.newCap, 2
ret { i32*, i32, i32 } %7
}
declare { i8*, i32, i32 } @runtime.sliceAppend(i8*, i8* nocapture readonly, i32, i32, i32, i32, i8*, i8*)
define hidden { i32*, i32, i32 } @main.sliceAppendSlice(i32* %ints.data, i32 %ints.len, i32 %ints.cap, i32* %added.data, i32 %added.len, i32 %added.cap, i8* %context, i8* %parentHandle) unnamed_addr {
entry:
%append.srcPtr = bitcast i32* %ints.data to i8*
%append.srcPtr1 = bitcast i32* %added.data to i8*
%append.new = call { i8*, i32, i32 } @runtime.sliceAppend(i8* %append.srcPtr, i8* %append.srcPtr1, i32 %ints.len, i32 %ints.cap, i32 %added.len, i32 4, i8* undef, i8* null)
%append.newPtr = extractvalue { i8*, i32, i32 } %append.new, 0
%append.newBuf = bitcast i8* %append.newPtr to i32*
%append.newLen = extractvalue { i8*, i32, i32 } %append.new, 1
%append.newCap = extractvalue { i8*, i32, i32 } %append.new, 2
%0 = insertvalue { i32*, i32, i32 } undef, i32* %append.newBuf, 0
%1 = insertvalue { i32*, i32, i32 } %0, i32 %append.newLen, 1
%2 = insertvalue { i32*, i32, i32 } %1, i32 %append.newCap, 2
ret { i32*, i32, i32 } %2
}
define hidden i32 @main.sliceCopy(i32* %dst.data, i32 %dst.len, i32 %dst.cap, i32* %src.data, i32 %src.len, i32 %src.cap, i8* %context, i8* %parentHandle) unnamed_addr {
entry:
%copy.dstPtr = bitcast i32* %dst.data to i8*
%copy.srcPtr = bitcast i32* %src.data to i8*
%copy.n = call i32 @runtime.sliceCopy(i8* %copy.dstPtr, i8* %copy.srcPtr, i32 %dst.len, i32 %src.len, i32 4, i8* undef, i8* null)
ret i32 %copy.n
}
declare i32 @runtime.sliceCopy(i8* nocapture writeonly, i8* nocapture readonly, i32, i32, i32, i8*, i8*)
-29
View File
@@ -1,29 +0,0 @@
package main
func someString() string {
return "foo"
}
func zeroLengthString() string {
return ""
}
func stringLen(s string) int {
return len(s)
}
func stringIndex(s string, index int) byte {
return s[index]
}
func stringCompareEqual(s1, s2 string) bool {
return s1 == s2
}
func stringCompareUnequal(s1, s2 string) bool {
return s1 != s2
}
func stringCompareLarger(s1, s2 string) bool {
return s1 > s2
}
-71
View File
@@ -1,71 +0,0 @@
; ModuleID = 'string.go'
source_filename = "string.go"
target datalayout = "e-m:e-p:32:32-p270:32:32-p271:32:32-p272:64:64-f64:32:64-f80:32-n8:16:32-S128"
target triple = "i686--linux"
%runtime._string = type { i8*, i32 }
@"main.someString$string" = internal unnamed_addr constant [3 x i8] c"foo", align 1
declare noalias nonnull i8* @runtime.alloc(i32, i8*, i8*)
define hidden void @main.init(i8* %context, i8* %parentHandle) unnamed_addr {
entry:
ret void
}
define hidden %runtime._string @main.someString(i8* %context, i8* %parentHandle) unnamed_addr {
entry:
ret %runtime._string { i8* getelementptr inbounds ([3 x i8], [3 x i8]* @"main.someString$string", i32 0, i32 0), i32 3 }
}
define hidden %runtime._string @main.zeroLengthString(i8* %context, i8* %parentHandle) unnamed_addr {
entry:
ret %runtime._string zeroinitializer
}
define hidden i32 @main.stringLen(i8* %s.data, i32 %s.len, i8* %context, i8* %parentHandle) unnamed_addr {
entry:
ret i32 %s.len
}
define hidden i8 @main.stringIndex(i8* %s.data, i32 %s.len, i32 %index, i8* %context, i8* %parentHandle) unnamed_addr {
entry:
%.not = icmp ult i32 %index, %s.len
br i1 %.not, label %lookup.next, label %lookup.throw
lookup.throw: ; preds = %entry
call void @runtime.lookupPanic(i8* undef, i8* null)
unreachable
lookup.next: ; preds = %entry
%0 = getelementptr inbounds i8, i8* %s.data, i32 %index
%1 = load i8, i8* %0, align 1
ret i8 %1
}
declare void @runtime.lookupPanic(i8*, i8*)
define hidden i1 @main.stringCompareEqual(i8* %s1.data, i32 %s1.len, i8* %s2.data, i32 %s2.len, i8* %context, i8* %parentHandle) unnamed_addr {
entry:
%0 = call i1 @runtime.stringEqual(i8* %s1.data, i32 %s1.len, i8* %s2.data, i32 %s2.len, i8* undef, i8* null)
ret i1 %0
}
declare i1 @runtime.stringEqual(i8*, i32, i8*, i32, i8*, i8*)
define hidden i1 @main.stringCompareUnequal(i8* %s1.data, i32 %s1.len, i8* %s2.data, i32 %s2.len, i8* %context, i8* %parentHandle) unnamed_addr {
entry:
%0 = call i1 @runtime.stringEqual(i8* %s1.data, i32 %s1.len, i8* %s2.data, i32 %s2.len, i8* undef, i8* null)
%1 = xor i1 %0, true
ret i1 %1
}
define hidden i1 @main.stringCompareLarger(i8* %s1.data, i32 %s1.len, i8* %s2.data, i32 %s2.len, i8* %context, i8* %parentHandle) unnamed_addr {
entry:
%0 = call i1 @runtime.stringLess(i8* %s1.data, i32 %s1.len, i8* %s2.data, i32 %s2.len, i8* undef, i8* null)
%1 = xor i1 %0, true
ret i1 %1
}
declare i1 @runtime.stringLess(i8*, i32, i8*, i32, i8*, i8*)
+5 -7
View File
@@ -1,16 +1,14 @@
module github.com/tinygo-org/tinygo module github.com/tinygo-org/tinygo
go 1.13 go 1.11
require ( require (
github.com/blakesmith/ar v0.0.0-20150311145944-8bd4349a67f2 github.com/blakesmith/ar v0.0.0-20150311145944-8bd4349a67f2
github.com/chromedp/cdproto v0.0.0-20210113043257-dabd2f2e7693 github.com/chromedp/cdproto v0.0.0-20200709115526-d1f6fc58448b
github.com/chromedp/chromedp v0.6.4 github.com/chromedp/chromedp v0.5.4-0.20200303084119-2bb39134ab9e
github.com/google/shlex v0.0.0-20181106134648-c34317bd91bf github.com/google/shlex v0.0.0-20181106134648-c34317bd91bf
github.com/marcinbor85/gohex v0.0.0-20200531091804-343a4b548892 github.com/marcinbor85/gohex v0.0.0-20200531091804-343a4b548892
github.com/mattn/go-colorable v0.1.8 go.bug.st/serial v1.0.0
go.bug.st/serial v1.1.2
golang.org/x/sys v0.0.0-20210113181707-4bcb84eeeb78
golang.org/x/tools v0.0.0-20200216192241-b320d3a0f5a2 golang.org/x/tools v0.0.0-20200216192241-b320d3a0f5a2
tinygo.org/x/go-llvm v0.0.0-20210325115028-e7b85195e81c tinygo.org/x/go-llvm v0.0.0-20201104183921-570e7a6841d9
) )
+19 -30
View File
@@ -1,39 +1,33 @@
github.com/blakesmith/ar v0.0.0-20150311145944-8bd4349a67f2 h1:oMCHnXa6CCCafdPDbMh/lWRhRByN0VFLvv+g+ayx1SI= github.com/blakesmith/ar v0.0.0-20150311145944-8bd4349a67f2 h1:oMCHnXa6CCCafdPDbMh/lWRhRByN0VFLvv+g+ayx1SI=
github.com/blakesmith/ar v0.0.0-20150311145944-8bd4349a67f2/go.mod h1:PkYb9DJNAwrSvRx5DYA+gUcOIgTGVMNkfSCbZM8cWpI= github.com/blakesmith/ar v0.0.0-20150311145944-8bd4349a67f2/go.mod h1:PkYb9DJNAwrSvRx5DYA+gUcOIgTGVMNkfSCbZM8cWpI=
github.com/chromedp/cdproto v0.0.0-20210113043257-dabd2f2e7693 h1:11eq/RkpaotwdF6b1TRMcdgQUPNmyFEJOB7zLvh0O/Y= github.com/chromedp/cdproto v0.0.0-20200116234248-4da64dd111ac/go.mod h1:PfAWWKJqjlGFYJEidUM6aVIWPr0EpobeyVWEEmplX7g=
github.com/chromedp/cdproto v0.0.0-20210113043257-dabd2f2e7693/go.mod h1:55pim6Ht4LJKdVLlyFJV/g++HsEA1hQxPbB5JyNdZC0= github.com/chromedp/cdproto v0.0.0-20200709115526-d1f6fc58448b h1:LF+GRwyzxrO3MUzPvejv+yBup0lNG+/QdIRrkxOPseA=
github.com/chromedp/chromedp v0.6.4 h1:Gx7ZkRyrSVmbbDDja/ieNgNGJIvElroPOyeqYQGVDSY= github.com/chromedp/cdproto v0.0.0-20200709115526-d1f6fc58448b/go.mod h1:E6LPWRdIJc11h/di5p0rwvRmUYbhGpBEH7ZbPfzDIOE=
github.com/chromedp/chromedp v0.6.4/go.mod h1:vodUdJf5dF/b8n0UBJv6NeM/QK28RjP3j+eM7fq4+84= github.com/chromedp/chromedp v0.5.4-0.20200303084119-2bb39134ab9e h1:Hv0JVyHhbIXb9NiYQe4NsrfgrSofAp0q2FnhhJOXgi8=
github.com/chromedp/sysutil v1.0.0 h1:+ZxhTpfpZlmchB58ih/LBHX52ky7w2VhQVKQMucy3Ic= github.com/chromedp/chromedp v0.5.4-0.20200303084119-2bb39134ab9e/go.mod h1:vmQMRHFZrY3T+Jv51T0n87OK/i6bK+5P9a+Fg5jPwgQ=
github.com/chromedp/sysutil v1.0.0/go.mod h1:kgWmDdq8fTzXYcKIBqIYvRRTnYb9aNS9moAV0xufSww=
github.com/creack/goselect v0.1.1 h1:tiSSgKE1eJtxs1h/VgGQWuXUP0YS4CDIFMp6vaI1ls0= github.com/creack/goselect v0.1.1 h1:tiSSgKE1eJtxs1h/VgGQWuXUP0YS4CDIFMp6vaI1ls0=
github.com/creack/goselect v0.1.1/go.mod h1:a/NhLweNvqIYMuxcMOuWY516Cimucms3DglDzQP3hKY= github.com/creack/goselect v0.1.1/go.mod h1:a/NhLweNvqIYMuxcMOuWY516Cimucms3DglDzQP3hKY=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/gobwas/httphead v0.1.0 h1:exrUm0f4YX0L7EBwZHuCF4GDp8aJfVeBrlLQrs6NqWU= github.com/gobwas/httphead v0.0.0-20180130184737-2c6c146eadee h1:s+21KNqlpePfkah2I+gwHF8xmJWRjooY+5248k6m4A0=
github.com/gobwas/httphead v0.1.0/go.mod h1:O/RXo79gxV8G+RqlR/otEwx4Q36zl9rqC5u12GKvMCM= github.com/gobwas/httphead v0.0.0-20180130184737-2c6c146eadee/go.mod h1:L0fX3K22YWvt/FAX9NnzrNzcI4wNYi9Yku4O0LKYflo=
github.com/gobwas/pool v0.2.1 h1:xfeeEhW7pwmX8nuLVlqbzVc7udMDrwetjEv+TZIz1og= github.com/gobwas/pool v0.2.0 h1:QEmUOlnSjWtnpRGHF3SauEiOsy82Cup83Vf2LcMlnc8=
github.com/gobwas/pool v0.2.1/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw= github.com/gobwas/pool v0.2.0/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw=
github.com/gobwas/ws v1.0.4 h1:5eXU1CZhpQdq5kXbKb+sECH5Ia5KiO6CYzIzdlVx6Bs= github.com/gobwas/ws v1.0.3 h1:ZOigqf7iBxkA4jdQ3am7ATzdlOFp9YzA6NmuvEEZc9g=
github.com/gobwas/ws v1.0.4/go.mod h1:szmBTxLgaFppYjEmNtny/v3w89xOydFnnZMcgRRu/EM= github.com/gobwas/ws v1.0.3/go.mod h1:szmBTxLgaFppYjEmNtny/v3w89xOydFnnZMcgRRu/EM=
github.com/google/shlex v0.0.0-20181106134648-c34317bd91bf h1:7+FW5aGwISbqUtkfmIpZJGRgNFg2ioYPvFaUxdqpDsg= github.com/google/shlex v0.0.0-20181106134648-c34317bd91bf h1:7+FW5aGwISbqUtkfmIpZJGRgNFg2ioYPvFaUxdqpDsg=
github.com/google/shlex v0.0.0-20181106134648-c34317bd91bf/go.mod h1:RpwtwJQFrIEPstU94h88MWPXP2ektJZ8cZ0YntAmXiE= github.com/google/shlex v0.0.0-20181106134648-c34317bd91bf/go.mod h1:RpwtwJQFrIEPstU94h88MWPXP2ektJZ8cZ0YntAmXiE=
github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= github.com/knq/sysutil v0.0.0-20191005231841-15668db23d08 h1:V0an7KRw92wmJysvFvtqtKMAPmvS5O0jtB0nYo6t+gs=
github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= github.com/knq/sysutil v0.0.0-20191005231841-15668db23d08/go.mod h1:dFWs1zEqDjFtnBXsd1vPOZaLsESovai349994nHx3e0=
github.com/mailru/easyjson v0.7.6 h1:8yTIVnZgCoiM1TgqoeTl+LfU5Jg6/xL3QhGQnimLYnA= github.com/mailru/easyjson v0.7.0/go.mod h1:KAzv3t3aY1NaHWoQz1+4F1ccyAH66Jk7yos7ldAVICs=
github.com/mailru/easyjson v0.7.6/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= github.com/mailru/easyjson v0.7.1 h1:mdxE1MF9o53iCb2Ghj1VfWvh7ZOwHpnVG/xwXrV90U8=
github.com/mailru/easyjson v0.7.1/go.mod h1:KAzv3t3aY1NaHWoQz1+4F1ccyAH66Jk7yos7ldAVICs=
github.com/marcinbor85/gohex v0.0.0-20200531091804-343a4b548892 h1:6J+qramlHVLmiBOgRiBOnQkno8uprqG6YFFQTt6uYIw= github.com/marcinbor85/gohex v0.0.0-20200531091804-343a4b548892 h1:6J+qramlHVLmiBOgRiBOnQkno8uprqG6YFFQTt6uYIw=
github.com/marcinbor85/gohex v0.0.0-20200531091804-343a4b548892/go.mod h1:Pb6XcsXyropB9LNHhnqaknG/vEwYztLkQzVCHv8sQ3M= github.com/marcinbor85/gohex v0.0.0-20200531091804-343a4b548892/go.mod h1:Pb6XcsXyropB9LNHhnqaknG/vEwYztLkQzVCHv8sQ3M=
github.com/mattn/go-colorable v0.1.8 h1:c1ghPdyEDarC70ftn0y+A/Ee++9zz8ljHG1b13eJ0s8=
github.com/mattn/go-colorable v0.1.8/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY=
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
go.bug.st/serial v1.0.0 h1:ogEPzrllCsnG00EqKRjeYvPRsO7NJW6DqykzkdD6E/k= go.bug.st/serial v1.0.0 h1:ogEPzrllCsnG00EqKRjeYvPRsO7NJW6DqykzkdD6E/k=
go.bug.st/serial v1.0.0/go.mod h1:rpXPISGjuNjPTRTcMlxi9lN6LoIPxd1ixVjBd8aSk/Q= go.bug.st/serial v1.0.0/go.mod h1:rpXPISGjuNjPTRTcMlxi9lN6LoIPxd1ixVjBd8aSk/Q=
go.bug.st/serial v1.1.2 h1:6xDpbta8KJ+VLRTeM8ghhxXRMLE/Lr8h9iDKwydarAY=
go.bug.st/serial v1.1.2/go.mod h1:VmYBeyJWp5BnJ0tw2NUJHZdJTGl2ecBGABHlzRK1knY=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee h1:WG0RUwxtNT4qqaXX3DPA8zHFNm/D9xaBpxzHt1WcA/E= golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee h1:WG0RUwxtNT4qqaXX3DPA8zHFNm/D9xaBpxzHt1WcA/E=
@@ -45,11 +39,8 @@ golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5h
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191128015809-6d18c012aee9 h1:ZBzSG/7F4eNKz2L3GE9o300RX0Az1Bw5HF7PDraD+qU= golang.org/x/sys v0.0.0-20191128015809-6d18c012aee9 h1:ZBzSG/7F4eNKz2L3GE9o300RX0Az1Bw5HF7PDraD+qU=
golang.org/x/sys v0.0.0-20191128015809-6d18c012aee9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191128015809-6d18c012aee9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200116001909-b77594299b42 h1:vEOn+mP2zCOVzKckCZy6YsCtDblrpj/w7B9nxGNELpg=
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200909081042-eff7692f9009/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210113181707-4bcb84eeeb78 h1:nVuTkr9L6Bq62qpUqKo/RnZCFfzDBL0bYo6w9OJUqZY=
golang.org/x/sys v0.0.0-20210113181707-4bcb84eeeb78/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/tools v0.0.0-20200216192241-b320d3a0f5a2 h1:0sfSpGSa544Fwnbot3Oxq/U6SXqjty6Jy/3wRhVS7ig= golang.org/x/tools v0.0.0-20200216192241-b320d3a0f5a2 h1:0sfSpGSa544Fwnbot3Oxq/U6SXqjty6Jy/3wRhVS7ig=
golang.org/x/tools v0.0.0-20200216192241-b320d3a0f5a2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200216192241-b320d3a0f5a2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
@@ -57,7 +48,5 @@ golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898 h1:/atklqdjdhuosWIl6AIbO
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
tinygo.org/x/go-llvm v0.0.0-20210308112806-9ef958b6bed4 h1:CMUHxVTb+UuUePuMf8vkWjZ3gTp9BBK91KrgOCwoNHs= tinygo.org/x/go-llvm v0.0.0-20201104183921-570e7a6841d9 h1:l2kTQOhqEoeDTK3ckUnwReOQwMPUmURMIdjJbeAuDT4=
tinygo.org/x/go-llvm v0.0.0-20210308112806-9ef958b6bed4/go.mod h1:fv1F0BSNpxMfCL0zF3M4OPFbgYHnhtB6ST0HvUtu/LE= tinygo.org/x/go-llvm v0.0.0-20201104183921-570e7a6841d9/go.mod h1:fv1F0BSNpxMfCL0zF3M4OPFbgYHnhtB6ST0HvUtu/LE=
tinygo.org/x/go-llvm v0.0.0-20210325115028-e7b85195e81c h1:vn9IPshzYmzZis10UEVrsIBRv9FpykADw6M3/tHHROg=
tinygo.org/x/go-llvm v0.0.0-20210325115028-e7b85195e81c/go.mod h1:fv1F0BSNpxMfCL0zF3M4OPFbgYHnhtB6ST0HvUtu/LE=
+4 -24
View File
@@ -142,11 +142,10 @@ func getHomeDir() string {
// getGoroot returns an appropriate GOROOT from various sources. If it can't be // getGoroot returns an appropriate GOROOT from various sources. If it can't be
// found, it returns an empty string. // found, it returns an empty string.
func getGoroot() string { func getGoroot() string {
// An explicitly set GOROOT always has preference.
goroot := os.Getenv("GOROOT") goroot := os.Getenv("GOROOT")
if goroot != "" { if goroot != "" {
// Convert to the standard GOROOT being referenced, if it's a TinyGo cache. // An explicitly set GOROOT always has preference.
return getStandardGoroot(goroot) return goroot
} }
// Check for the location of the 'go' binary and base GOROOT on that. // Check for the location of the 'go' binary and base GOROOT on that.
@@ -171,9 +170,8 @@ func getGoroot() string {
switch runtime.GOOS { switch runtime.GOOS {
case "linux": case "linux":
candidates = []string{ candidates = []string{
"/usr/local/go", // manually installed "/usr/local/go", // manually installed
"/usr/lib/go", // from the distribution "/usr/lib/go", // from the distribution
"/snap/go/current/", // installed using snap
} }
case "darwin": case "darwin":
candidates = []string{ candidates = []string{
@@ -197,21 +195,3 @@ func isGoroot(goroot string) bool {
_, err := os.Stat(filepath.Join(goroot, "src", "runtime", "internal", "sys", "zversion.go")) _, err := os.Stat(filepath.Join(goroot, "src", "runtime", "internal", "sys", "zversion.go"))
return err == nil return err == nil
} }
// getStandardGoroot returns the physical path to a real, standard Go GOROOT
// implied by the given path.
// If the given path appears to be a TinyGo cached GOROOT, it returns the path
// referenced by symlinks contained in the cache. Otherwise, it returns the
// given path as-is.
func getStandardGoroot(path string) string {
// Check if the "bin" subdirectory of our given GOROOT is a symlink, and then
// return the _parent_ directory of its destination.
if dest, err := os.Readlink(filepath.Join(path, "bin")); nil == err {
// Clean the destination to remove any trailing slashes, so that
// filepath.Dir will always return the parent.
// (because both "/foo" and "/foo/" are valid symlink destinations,
// but filepath.Dir would return "/" and "/foo", respectively)
return filepath.Dir(filepath.Clean(dest))
}
return path
}
+1 -1
View File
@@ -12,7 +12,7 @@ import (
// Version of TinyGo. // Version of TinyGo.
// Update this value before release of new version of software. // Update this value before release of new version of software.
const Version = "0.18.0-dev" const Version = "0.17.0-dev"
// GetGorootVersion returns the major and minor version for a given GOROOT path. // GetGorootVersion returns the major and minor version for a given GOROOT path.
// If the goroot cannot be determined, (0, 0) is returned. // If the goroot cannot be determined, (0, 0) is returned.
+1 -17
View File
@@ -25,7 +25,6 @@ type function struct {
// basicBlock represents a LLVM basic block and contains a slice of // basicBlock represents a LLVM basic block and contains a slice of
// instructions. The last instruction must be a terminator instruction. // instructions. The last instruction must be a terminator instruction.
type basicBlock struct { type basicBlock struct {
phiNodes []instruction
instructions []instruction instructions []instruction
} }
@@ -136,15 +135,6 @@ func (r *runner) compileFunction(llvmFn llvm.Value) *function {
default: default:
panic("unknown number of operands") panic("unknown number of operands")
} }
case llvm.Switch:
// A switch is an array of (value, label) pairs, of which the
// first one indicates the to-switch value and the default
// label.
numOperands := llvmInst.OperandsCount()
for i := 0; i < numOperands; i += 2 {
inst.operands = append(inst.operands, r.getValue(llvmInst.Operand(i)))
inst.operands = append(inst.operands, literalValue{uint32(blockIndices[llvmInst.Operand(i+1)])})
}
case llvm.PHI: case llvm.PHI:
inst.name = llvmInst.Name() inst.name = llvmInst.Name()
incomingCount := inst.llvmInst.IncomingCount() incomingCount := inst.llvmInst.IncomingCount()
@@ -347,13 +337,7 @@ func (r *runner) compileFunction(llvmFn llvm.Value) *function {
// This error is handled when actually trying to interpret this // This error is handled when actually trying to interpret this
// instruction (to not trigger on code that won't be executed). // instruction (to not trigger on code that won't be executed).
} }
if inst.opcode == llvm.PHI { bb.instructions = append(bb.instructions, inst)
// PHI nodes need to be treated specially, see the comment in
// interpreter.go for an explanation.
bb.phiNodes = append(bb.phiNodes, inst)
} else {
bb.instructions = append(bb.instructions, inst)
}
} }
} }
return fn return fn
-5
View File
@@ -20,11 +20,6 @@ var (
errMapAlreadyCreated = errors.New("interp: map already created") errMapAlreadyCreated = errors.New("interp: map already created")
) )
// This is one of the errors that can be returned from toLLVMValue when the
// passed type does not fit the data to serialize. It is recoverable by
// serializing without a type (using rawValue.rawLLVMValue).
var errInvalidPtrToIntSize = errors.New("interp: ptrtoint integer size does not equal pointer size")
func isRecoverableError(err error) bool { func isRecoverableError(err error) bool {
return err == errIntegerAsPointer || err == errUnsupportedInst || err == errUnsupportedRuntimeInst || err == errMapAlreadyCreated return err == errIntegerAsPointer || err == errUnsupportedInst || err == errUnsupportedRuntimeInst || err == errMapAlreadyCreated
} }
+5 -124
View File
@@ -11,12 +11,6 @@ import (
"tinygo.org/x/go-llvm" "tinygo.org/x/go-llvm"
) )
// Version of the interp package. It must be incremented whenever the interp
// package is changed in a way that affects the output so that cached package
// builds will be invalidated.
// This version is independent of the TinyGo version number.
const Version = 1
// Enable extra checks, which should be disabled by default. // Enable extra checks, which should be disabled by default.
// This may help track down bugs by adding a few more sanity checks. // This may help track down bugs by adding a few more sanity checks.
const checks = true const checks = true
@@ -38,7 +32,9 @@ type runner struct {
callsExecuted uint64 callsExecuted uint64
} }
func newRunner(mod llvm.Module, debug bool) *runner { // Run evaluates runtime.initAll function as much as possible at compile time.
// Set debug to true if it should print output while running.
func Run(mod llvm.Module, debug bool) error {
r := runner{ r := runner{
mod: mod, mod: mod,
targetData: llvm.NewTargetData(mod.DataLayout()), targetData: llvm.NewTargetData(mod.DataLayout()),
@@ -51,13 +47,6 @@ func newRunner(mod llvm.Module, debug bool) *runner {
r.pointerSize = uint32(r.targetData.PointerSize()) r.pointerSize = uint32(r.targetData.PointerSize())
r.i8ptrType = llvm.PointerType(mod.Context().Int8Type(), 0) r.i8ptrType = llvm.PointerType(mod.Context().Int8Type(), 0)
r.maxAlign = r.targetData.PrefTypeAlignment(r.i8ptrType) // assume pointers are maximally aligned (this is not always the case) r.maxAlign = r.targetData.PrefTypeAlignment(r.i8ptrType) // assume pointers are maximally aligned (this is not always the case)
return &r
}
// Run evaluates runtime.initAll function as much as possible at compile time.
// Set debug to true if it should print output while running.
func Run(mod llvm.Module, debug bool) error {
r := newRunner(mod, debug)
initAll := mod.NamedFunction("runtime.initAll") initAll := mod.NamedFunction("runtime.initAll")
bb := initAll.EntryBasicBlock() bb := initAll.EntryBasicBlock()
@@ -128,7 +117,7 @@ func Run(mod llvm.Module, debug bool) error {
r.pkgName = "" r.pkgName = ""
// Update all global variables in the LLVM module. // Update all global variables in the LLVM module.
mem := memoryView{r: r} mem := memoryView{r: &r}
for _, obj := range r.objects { for _, obj := range r.objects {
if obj.llvmGlobal.IsNil() { if obj.llvmGlobal.IsNil() {
continue continue
@@ -136,34 +125,7 @@ func Run(mod llvm.Module, debug bool) error {
if obj.buffer == nil { if obj.buffer == nil {
continue continue
} }
initializer, err := obj.buffer.toLLVMValue(obj.llvmGlobal.Type().ElementType(), &mem) initializer := obj.buffer.toLLVMValue(obj.llvmGlobal.Type().ElementType(), &mem)
if err == errInvalidPtrToIntSize {
// This can happen when a previous interp run did not have the
// correct LLVM type for a global and made something up. In that
// case, some fields could be written out as a series of (null)
// bytes even though they actually contain a pointer value.
// As a fallback, use asRawValue to get something of the correct
// memory layout.
initializer, err := obj.buffer.asRawValue(r).rawLLVMValue(&mem)
if err != nil {
return err
}
initializerType := initializer.Type()
newGlobal := llvm.AddGlobal(mod, initializerType, obj.llvmGlobal.Name()+".tmp")
newGlobal.SetInitializer(initializer)
newGlobal.SetLinkage(obj.llvmGlobal.Linkage())
newGlobal.SetAlignment(obj.llvmGlobal.Alignment())
// TODO: copy debug info, unnamed_addr, ...
bitcast := llvm.ConstBitCast(newGlobal, obj.llvmGlobal.Type())
obj.llvmGlobal.ReplaceAllUsesWith(bitcast)
name := obj.llvmGlobal.Name()
obj.llvmGlobal.EraseFromParentAsGlobal()
newGlobal.SetName(name)
continue
}
if err != nil {
return err
}
if checks && initializer.Type() != obj.llvmGlobal.Type().ElementType() { if checks && initializer.Type() != obj.llvmGlobal.Type().ElementType() {
panic("initializer type mismatch") panic("initializer type mismatch")
} }
@@ -173,87 +135,6 @@ func Run(mod llvm.Module, debug bool) error {
return nil return nil
} }
// RunFunc evaluates a single package initializer at compile time.
// Set debug to true if it should print output while running.
func RunFunc(fn llvm.Value, debug bool) error {
// Create and initialize *runner object.
mod := fn.GlobalParent()
r := newRunner(mod, debug)
initName := fn.Name()
if !strings.HasSuffix(initName, ".init") {
return errorAt(fn, "interp: unexpected function name (expected *.init)")
}
r.pkgName = initName[:len(initName)-len(".init")]
// Create new function with the interp result.
newFn := llvm.AddFunction(mod, fn.Name()+".tmp", fn.Type().ElementType())
newFn.SetLinkage(fn.Linkage())
newFn.SetVisibility(fn.Visibility())
entry := mod.Context().AddBasicBlock(newFn, "entry")
// Create a builder, to insert instructions that could not be evaluated at
// compile time.
r.builder = mod.Context().NewBuilder()
defer r.builder.Dispose()
r.builder.SetInsertPointAtEnd(entry)
// Copy debug information.
subprogram := fn.Subprogram()
if !subprogram.IsNil() {
newFn.SetSubprogram(subprogram)
r.builder.SetCurrentDebugLocation(subprogram.SubprogramLine(), 0, subprogram, llvm.Metadata{})
}
// Run the initializer, filling the .init.tmp function.
if r.debug {
fmt.Fprintln(os.Stderr, "interp:", fn.Name())
}
_, pkgMem, callErr := r.run(r.getFunction(fn), nil, nil, " ")
if callErr != nil {
if isRecoverableError(callErr.Err) {
// Could not finish, but could recover from it.
if r.debug {
fmt.Fprintln(os.Stderr, "not interpreting", r.pkgName, "because of error:", callErr.Error())
}
newFn.EraseFromParentAsFunction()
return nil
}
return callErr
}
for index, obj := range pkgMem.objects {
r.objects[index] = obj
}
// Update globals with values determined while running the initializer above.
mem := memoryView{r: r}
for _, obj := range r.objects {
if obj.llvmGlobal.IsNil() {
continue
}
if obj.buffer == nil {
continue
}
initializer, err := obj.buffer.toLLVMValue(obj.llvmGlobal.Type().ElementType(), &mem)
if err != nil {
return err
}
if checks && initializer.Type() != obj.llvmGlobal.Type().ElementType() {
panic("initializer type mismatch")
}
obj.llvmGlobal.SetInitializer(initializer)
}
// Finalize: remove the old init function and replace it with the new
// (.init.tmp) function.
r.builder.CreateRetVoid()
fnName := fn.Name()
fn.ReplaceAllUsesWith(newFn)
fn.EraseFromParentAsFunction()
newFn.SetName(fnName)
return nil
}
// getFunction returns the compiled version of the given LLVM function. It // getFunction returns the compiled version of the given LLVM function. It
// compiles the function if necessary and caches the result. // compiles the function if necessary and caches the result.
func (r *runner) getFunction(llvmFn llvm.Value) *function { func (r *runner) getFunction(llvmFn llvm.Value) *function {
+1 -1
View File
@@ -13,9 +13,9 @@ import (
func TestInterp(t *testing.T) { func TestInterp(t *testing.T) {
for _, name := range []string{ for _, name := range []string{
"basic", "basic",
"phi",
"slice-copy", "slice-copy",
"consteval", "consteval",
"map",
"interface", "interface",
} { } {
name := name // make tc local to this closure name := name // make tc local to this closure
+93 -153
View File
@@ -33,50 +33,6 @@ func (r *runner) run(fn *function, params []value, parentMem *memoryView, indent
lastBB := -1 // last basic block is undefined, only defined after a branch lastBB := -1 // last basic block is undefined, only defined after a branch
var operands []value var operands []value
for instIndex := 0; instIndex < len(bb.instructions); instIndex++ { for instIndex := 0; instIndex < len(bb.instructions); instIndex++ {
if instIndex == 0 {
// This is the start of a new basic block.
// There may be PHI nodes that need to be resolved. Resolve all PHI
// nodes before continuing with regular instructions.
// PHI nodes need to be treated specially because they can have a
// mutual dependency:
// for.loop:
// %a = phi i8 [ 1, %entry ], [ %b, %for.loop ]
// %b = phi i8 [ 3, %entry ], [ %a, %for.loop ]
// If these PHI nodes are processed like a regular instruction, %a
// and %b are both 3 on the second iteration of the loop because %b
// loads the value of %a from the second iteration, while it should
// load the value from the previous iteration. The correct behavior
// is that these two values swap each others place on each
// iteration.
var phiValues []value
var phiIndices []int
for _, inst := range bb.phiNodes {
var result value
for i := 0; i < len(inst.operands); i += 2 {
if int(inst.operands[i].(literalValue).value.(uint32)) == lastBB {
incoming := inst.operands[i+1]
if local, ok := incoming.(localValue); ok {
result = locals[fn.locals[local.value]]
} else {
result = incoming
}
break
}
}
if r.debug {
fmt.Fprintln(os.Stderr, indent+"phi", inst.operands, "->", result)
}
if result == nil {
panic("could not find PHI input")
}
phiValues = append(phiValues, result)
phiIndices = append(phiIndices, inst.localIndex)
}
for i, value := range phiValues {
locals[phiIndices[i]] = value
}
}
inst := bb.instructions[instIndex] inst := bb.instructions[instIndex]
operands = operands[:0] operands = operands[:0]
isRuntimeInst := false isRuntimeInst := false
@@ -147,24 +103,27 @@ func (r *runner) run(fn *function, params []value, parentMem *memoryView, indent
default: default:
panic("unknown operands length") panic("unknown operands length")
} }
case llvm.Switch: break // continue with next block
// Switch statement: [value, defaultLabel, case0, label0, case1, label1, ...] case llvm.PHI:
value := operands[0].Uint() var result value
targetLabel := operands[1].Uint() // default label for i := 0; i < len(inst.operands); i += 2 {
// Do a lazy switch by iterating over all cases. if int(inst.operands[i].(literalValue).value.(uint32)) == lastBB {
for i := 2; i < len(operands); i += 2 { incoming := inst.operands[i+1]
if value == operands[i].Uint() { if local, ok := incoming.(localValue); ok {
targetLabel = operands[i+1].Uint() result = locals[fn.locals[local.value]]
} else {
result = incoming
}
break break
} }
} }
lastBB = currentBB
currentBB = int(targetLabel)
bb = fn.blocks[currentBB]
instIndex = -1 // start at 0 the next cycle
if r.debug { if r.debug {
fmt.Fprintln(os.Stderr, indent+"switch", operands, "->", currentBB) fmt.Fprintln(os.Stderr, indent+"phi", inst.operands, "->", result)
} }
if result == nil {
panic("could not find PHI input")
}
locals[inst.localIndex] = result
case llvm.Select: case llvm.Select:
// Select is much like a ternary operator: it picks a result from // Select is much like a ternary operator: it picks a result from
// the second and third operand based on the boolean first operand. // the second and third operand based on the boolean first operand.
@@ -196,17 +155,17 @@ func (r *runner) run(fn *function, params []value, parentMem *memoryView, indent
// which case this call won't even get to this point but will // which case this call won't even get to this point but will
// already be emitted in initAll. // already be emitted in initAll.
continue continue
case strings.HasPrefix(callFn.name, "runtime.print") || callFn.name == "runtime._panic" || callFn.name == "runtime.hashmapGet" || callFn.name == "os.runtime_args": case callFn.name == "(reflect.Type).Elem" || strings.HasPrefix(callFn.name, "runtime.print") || callFn.name == "runtime._panic" || callFn.name == "runtime.hashmapGet":
// These functions should be run at runtime. Specifically: // These functions should be run at runtime. Specifically:
// * (reflect.Type).Elem is a special function. It should
// eventually be interpreted, but fall back to a runtime call
// for now.
// * Print and panic functions are best emitted directly without // * Print and panic functions are best emitted directly without
// interpreting them, otherwise we get a ton of putchar (etc.) // interpreting them, otherwise we get a ton of putchar (etc.)
// calls. // calls.
// * runtime.hashmapGet tries to access the map value directly. // * runtime.hashmapGet tries to access the map value directly.
// This is not possible as the map value is treated as a special // This is not possible as the map value is treated as a special
// kind of object in this package. // kind of object in this package.
// * os.runtime_args reads globals that are initialized outside
// the view of the interp package so it always needs to be run
// at runtime.
err := r.runAtRuntime(fn, inst, locals, &mem, indent) err := r.runAtRuntime(fn, inst, locals, &mem, indent)
if err != nil { if err != nil {
return nil, mem, err return nil, mem, err
@@ -321,58 +280,26 @@ func (r *runner) run(fn *function, params []value, parentMem *memoryView, indent
copy(dstBuf.buf[dst.offset():dst.offset()+nBytes], srcBuf.buf[src.offset():]) copy(dstBuf.buf[dst.offset():dst.offset()+nBytes], srcBuf.buf[src.offset():])
dstObj.buffer = dstBuf dstObj.buffer = dstBuf
mem.put(dst.index(), dstObj) mem.put(dst.index(), dstObj)
case callFn.name == "(reflect.rawType).elem":
if r.debug {
fmt.Fprintln(os.Stderr, indent+"call (reflect.rawType).elem:", operands[1:])
}
// Extract the type code global from the first parameter.
typecodeIDPtrToInt, err := operands[1].toLLVMValue(inst.llvmInst.Operand(0).Type(), &mem)
if err != nil {
return nil, mem, r.errorAt(inst, err)
}
typecodeID := typecodeIDPtrToInt.Operand(0)
// Get the type class.
// See also: getClassAndValueFromTypeCode in transform/reflect.go.
typecodeName := typecodeID.Name()
const prefix = "reflect/types.type:"
if !strings.HasPrefix(typecodeName, prefix) {
panic("unexpected typecode name: " + typecodeName)
}
id := typecodeName[len(prefix):]
class := id[:strings.IndexByte(id, ':')]
value := id[len(class)+1:]
if class == "named" {
// Get the underlying type.
class = value[:strings.IndexByte(value, ':')]
value = value[len(class)+1:]
}
// Elem() is only valid for certain type classes.
switch class {
case "chan", "pointer", "slice", "array":
elementType := llvm.ConstExtractValue(typecodeID.Initializer(), []uint32{0})
uintptrType := r.mod.Context().IntType(int(mem.r.pointerSize) * 8)
locals[inst.localIndex] = r.getValue(llvm.ConstPtrToInt(elementType, uintptrType))
default:
return nil, mem, r.errorAt(inst, fmt.Errorf("(reflect.Type).Elem() called on %s type", class))
}
case callFn.name == "runtime.typeAssert": case callFn.name == "runtime.typeAssert":
// This function must be implemented manually as it is normally // This function must be implemented manually as it is normally
// implemented by the interface lowering pass. // implemented by the interface lowering pass.
if r.debug { if r.debug {
fmt.Fprintln(os.Stderr, indent+"typeassert:", operands[1:]) fmt.Fprintln(os.Stderr, indent+"typeassert:", operands[1:])
} }
assertedType, err := operands[2].toLLVMValue(inst.llvmInst.Operand(1).Type(), &mem) typeInInterfacePtr, err := operands[1].asPointer(r)
if err != nil { if err != nil {
return nil, mem, r.errorAt(inst, err) return nil, mem, r.errorAt(inst, err)
} }
actualTypePtrToInt, err := operands[1].toLLVMValue(inst.llvmInst.Operand(0).Type(), &mem) actualType, err := mem.load(typeInInterfacePtr, r.pointerSize).asPointer(r)
if err != nil { if err != nil {
return nil, mem, r.errorAt(inst, err) return nil, mem, r.errorAt(inst, err)
} }
actualType := actualTypePtrToInt.Operand(0) assertedType, err := operands[2].asPointer(r)
if strings.TrimPrefix(actualType.Name(), "reflect/types.type:") == strings.TrimPrefix(assertedType.Name(), "reflect/types.typeid:") { if err != nil {
return nil, mem, r.errorAt(inst, err)
}
result := assertedType.asRawValue(r).equal(actualType.asRawValue(r))
if result {
locals[inst.localIndex] = literalValue{uint8(1)} locals[inst.localIndex] = literalValue{uint8(1)}
} else { } else {
locals[inst.localIndex] = literalValue{uint8(0)} locals[inst.localIndex] = literalValue{uint8(0)}
@@ -383,11 +310,11 @@ func (r *runner) run(fn *function, params []value, parentMem *memoryView, indent
} }
// Load various values for the interface implements check below. // Load various values for the interface implements check below.
typecodePtr, err := operands[1].asPointer(r) typeInInterfacePtr, err := operands[1].asPointer(r)
if err != nil { if err != nil {
return nil, mem, r.errorAt(inst, err) return nil, mem, r.errorAt(inst, err)
} }
methodSetPtr, err := mem.load(typecodePtr.addOffset(r.pointerSize*2), r.pointerSize).asPointer(r) methodSetPtr, err := mem.load(typeInInterfacePtr.addOffset(r.pointerSize), r.pointerSize).asPointer(r)
if err != nil { if err != nil {
return nil, mem, r.errorAt(inst, err) return nil, mem, r.errorAt(inst, err)
} }
@@ -422,43 +349,74 @@ func (r *runner) run(fn *function, params []value, parentMem *memoryView, indent
} }
// If assertOk is still 1, the assertion succeeded. // If assertOk is still 1, the assertion succeeded.
locals[inst.localIndex] = literalValue{assertOk} locals[inst.localIndex] = literalValue{assertOk}
case callFn.name == "runtime.interfaceMethod": case callFn.name == "runtime.hashmapMake":
// This builtin returns the function (which may be a thunk) to // Create a new map.
// invoke a method on an interface. It does not call the method. hashmapPointerType := inst.llvmInst.Type()
if r.debug { keySize := uint32(operands[1].Uint())
fmt.Fprintln(os.Stderr, indent+"interface method:", operands[1:]) valueSize := uint32(operands[2].Uint())
m := newMapValue(r, hashmapPointerType, keySize, valueSize)
alloc := object{
llvmType: hashmapPointerType,
globalName: r.pkgName + "$map",
buffer: m,
size: m.len(r),
} }
index := len(r.objects)
r.objects = append(r.objects, alloc)
// Load the first param, which is the type code (ptrtoint of the // Create a pointer to this map. Maps are reference types, so
// type code global). // are implemented as pointers.
typecodeIDPtrToInt, err := operands[1].toLLVMValue(inst.llvmInst.Operand(0).Type(), &mem) ptr := newPointerValue(r, index, 0)
if r.debug {
fmt.Fprintln(os.Stderr, indent+"runtime.hashmapMake:", keySize, valueSize, "->", ptr)
}
locals[inst.localIndex] = ptr
case callFn.name == "runtime.hashmapBinarySet":
// Do a mapassign operation with a binary key (that is, without
// a string key).
if r.debug {
fmt.Fprintln(os.Stderr, indent+"runtime.hashmapBinarySet:", operands[1:])
}
mapPtr, err := operands[1].asPointer(r)
if err != nil { if err != nil {
return nil, mem, r.errorAt(inst, err) return nil, mem, r.errorAt(inst, err)
} }
typecodeID := typecodeIDPtrToInt.Operand(0).Initializer() m := mem.getWritable(mapPtr.index()).buffer.(*mapValue)
keyPtr, err := operands[2].asPointer(r)
// Load the method set, which is part of the typecodeID object. if err != nil {
methodSet := llvm.ConstExtractValue(typecodeID, []uint32{2}).Operand(0).Initializer() return nil, mem, r.errorAt(inst, err)
// We don't need to load the interface method set.
// Load the signature of the to-be-called function.
signature := inst.llvmInst.Operand(2)
// Iterate through all methods, looking for the one method that
// should be returned.
numMethods := methodSet.Type().ArrayLength()
var method llvm.Value
for i := 0; i < numMethods; i++ {
methodSignature := llvm.ConstExtractValue(methodSet, []uint32{uint32(i), 0})
if methodSignature == signature {
method = llvm.ConstExtractValue(methodSet, []uint32{uint32(i), 1}).Operand(0)
}
} }
if method.IsNil() { valuePtr, err := operands[3].asPointer(r)
return nil, mem, r.errorAt(inst, errors.New("could not find method: "+signature.Name())) if err != nil {
return nil, mem, r.errorAt(inst, err)
}
err = m.putBinary(&mem, keyPtr, valuePtr)
if err != nil {
return nil, mem, r.errorAt(inst, err)
}
case callFn.name == "runtime.hashmapStringSet":
// Do a mapassign operation with a string key.
if r.debug {
fmt.Fprintln(os.Stderr, indent+"runtime.hashmapBinarySet:", operands[1:])
}
mapPtr, err := operands[1].asPointer(r)
if err != nil {
return nil, mem, r.errorAt(inst, err)
}
m := mem.getWritable(mapPtr.index()).buffer.(*mapValue)
stringPtr, err := operands[2].asPointer(r)
if err != nil {
return nil, mem, r.errorAt(inst, err)
}
stringLen := operands[3].Uint()
valuePtr, err := operands[4].asPointer(r)
if err != nil {
return nil, mem, r.errorAt(inst, err)
}
err = m.putString(&mem, stringPtr, stringLen, valuePtr)
if err != nil {
return nil, mem, r.errorAt(inst, err)
} }
locals[inst.localIndex] = r.getValue(method)
default: default:
if len(callFn.blocks) == 0 { if len(callFn.blocks) == 0 {
// Call to a function declaration without a definition // Call to a function declaration without a definition
@@ -524,13 +482,6 @@ func (r *runner) run(fn *function, params []value, parentMem *memoryView, indent
continue continue
} }
result := mem.load(ptr, uint32(size)) result := mem.load(ptr, uint32(size))
if result == nil {
err := r.runAtRuntime(fn, inst, locals, &mem, indent)
if err != nil {
return nil, mem, err
}
continue
}
if r.debug { if r.debug {
fmt.Fprintln(os.Stderr, indent+"load:", ptr, "->", result) fmt.Fprintln(os.Stderr, indent+"load:", ptr, "->", result)
} }
@@ -553,14 +504,7 @@ func (r *runner) run(fn *function, params []value, parentMem *memoryView, indent
if r.debug { if r.debug {
fmt.Fprintln(os.Stderr, indent+"store:", val, ptr) fmt.Fprintln(os.Stderr, indent+"store:", val, ptr)
} }
ok := mem.store(val, ptr) mem.store(val, ptr)
if !ok {
// Could not store the value, do it at runtime.
err := r.runAtRuntime(fn, inst, locals, &mem, indent)
if err != nil {
return nil, mem, err
}
}
case llvm.Alloca: case llvm.Alloca:
// Alloca normally allocates some stack memory. In the interpreter, // Alloca normally allocates some stack memory. In the interpreter,
// it allocates a global instead. // it allocates a global instead.
@@ -892,11 +836,7 @@ func (r *runner) runAtRuntime(fn *function, inst instruction, locals []value, me
for i := 0; i < numOperands; i++ { for i := 0; i < numOperands; i++ {
operand := inst.llvmInst.Operand(i) operand := inst.llvmInst.Operand(i)
if !operand.IsAInstruction().IsNil() || !operand.IsAArgument().IsNil() { if !operand.IsAInstruction().IsNil() || !operand.IsAArgument().IsNil() {
var err error operand = locals[fn.locals[operand]].toLLVMValue(operand.Type(), mem)
operand, err = locals[fn.locals[operand]].toLLVMValue(operand.Type(), mem)
if err != nil {
return r.errorAt(inst, err)
}
} }
operands[i] = operand operands[i] = operand
} }
+323 -90
View File
@@ -259,17 +259,12 @@ func (mv *memoryView) put(index uint32, obj object) {
mv.objects[index] = obj mv.objects[index] = obj
} }
// Load the value behind the given pointer. Returns nil if the pointer points to // Load the value behind the given pointer.
// an external global.
func (mv *memoryView) load(p pointerValue, size uint32) value { func (mv *memoryView) load(p pointerValue, size uint32) value {
if checks && mv.hasExternalStore(p) { if checks && mv.hasExternalStore(p) {
panic("interp: load from object with external store") panic("interp: load from object with external store")
} }
obj := mv.get(p.index()) obj := mv.get(p.index())
if obj.buffer == nil {
// External global, return nil.
return nil
}
if p.offset() == 0 && size == obj.size { if p.offset() == 0 && size == obj.size {
return obj.buffer.clone() return obj.buffer.clone()
} }
@@ -285,17 +280,12 @@ func (mv *memoryView) load(p pointerValue, size uint32) value {
// Store to the value behind the given pointer. This overwrites the value in the // Store to the value behind the given pointer. This overwrites the value in the
// memory view, so that the changed value is discarded when the memory view is // memory view, so that the changed value is discarded when the memory view is
// reverted. Returns true on success, false if the object to store to is // reverted.
// external. func (mv *memoryView) store(v value, p pointerValue) {
func (mv *memoryView) store(v value, p pointerValue) bool {
if checks && mv.hasExternalLoadOrStore(p) { if checks && mv.hasExternalLoadOrStore(p) {
panic("interp: store to object with external load/store") panic("interp: store to object with external load/store")
} }
obj := mv.get(p.index()) obj := mv.get(p.index())
if obj.buffer == nil {
// External global, return false (for a failure).
return false
}
if checks && p.offset()+v.len(mv.r) > obj.size { if checks && p.offset()+v.len(mv.r) > obj.size {
panic("interp: store out of bounds") panic("interp: store out of bounds")
} }
@@ -311,7 +301,6 @@ func (mv *memoryView) store(v value, p pointerValue) bool {
} }
} }
mv.put(p.index(), obj) mv.put(p.index(), obj)
return true // success
} }
// value is some sort of value, comparable to a LLVM constant. It can be // value is some sort of value, comparable to a LLVM constant. It can be
@@ -325,7 +314,7 @@ type value interface {
asRawValue(*runner) rawValue asRawValue(*runner) rawValue
Uint() uint64 Uint() uint64
Int() int64 Int() int64
toLLVMValue(llvm.Type, *memoryView) (llvm.Value, error) toLLVMValue(llvm.Type, *memoryView) llvm.Value
String() string String() string
} }
@@ -416,25 +405,25 @@ func (v literalValue) Int() int64 {
} }
} }
func (v literalValue) toLLVMValue(llvmType llvm.Type, mem *memoryView) (llvm.Value, error) { func (v literalValue) toLLVMValue(llvmType llvm.Type, mem *memoryView) llvm.Value {
switch llvmType.TypeKind() { switch llvmType.TypeKind() {
case llvm.IntegerTypeKind: case llvm.IntegerTypeKind:
switch value := v.value.(type) { switch value := v.value.(type) {
case uint64: case uint64:
return llvm.ConstInt(llvmType, value, false), nil return llvm.ConstInt(llvmType, value, false)
case uint32: case uint32:
return llvm.ConstInt(llvmType, uint64(value), false), nil return llvm.ConstInt(llvmType, uint64(value), false)
case uint16: case uint16:
return llvm.ConstInt(llvmType, uint64(value), false), nil return llvm.ConstInt(llvmType, uint64(value), false)
case uint8: case uint8:
return llvm.ConstInt(llvmType, uint64(value), false), nil return llvm.ConstInt(llvmType, uint64(value), false)
default: default:
return llvm.Value{}, errors.New("interp: unknown literal type") panic("inpterp: unknown literal type")
} }
case llvm.DoubleTypeKind: case llvm.DoubleTypeKind:
return llvm.ConstFloat(llvmType, math.Float64frombits(v.value.(uint64))), nil return llvm.ConstFloat(llvmType, math.Float64frombits(v.value.(uint64)))
case llvm.FloatTypeKind: case llvm.FloatTypeKind:
return llvm.ConstFloat(llvmType, float64(math.Float32frombits(v.value.(uint32)))), nil return llvm.ConstFloat(llvmType, float64(math.Float32frombits(v.value.(uint32))))
default: default:
return v.asRawValue(mem.r).toLLVMValue(llvmType, mem) return v.asRawValue(mem.r).toLLVMValue(llvmType, mem)
} }
@@ -518,7 +507,7 @@ func (v pointerValue) llvmValue(mem *memoryView) llvm.Value {
// toLLVMValue returns the LLVM value for this pointer, which may be a GEP or // toLLVMValue returns the LLVM value for this pointer, which may be a GEP or
// bitcast. The llvm.Type parameter is optional, if omitted the pointer type may // bitcast. The llvm.Type parameter is optional, if omitted the pointer type may
// be different than expected. // be different than expected.
func (v pointerValue) toLLVMValue(llvmType llvm.Type, mem *memoryView) (llvm.Value, error) { func (v pointerValue) toLLVMValue(llvmType llvm.Type, mem *memoryView) llvm.Value {
// Obtain the llvmValue, creating it if it doesn't exist yet. // Obtain the llvmValue, creating it if it doesn't exist yet.
llvmValue := v.llvmValue(mem) llvmValue := v.llvmValue(mem)
if llvmValue.IsNil() { if llvmValue.IsNil() {
@@ -529,10 +518,7 @@ func (v pointerValue) toLLVMValue(llvmType llvm.Type, mem *memoryView) (llvm.Val
if obj.llvmType.IsNil() { if obj.llvmType.IsNil() {
// Create an initializer without knowing the global type. // Create an initializer without knowing the global type.
// This is probably the result of a runtime.alloc call. // This is probably the result of a runtime.alloc call.
initializer, err := obj.buffer.asRawValue(mem.r).rawLLVMValue(mem) initializer := obj.buffer.asRawValue(mem.r).rawLLVMValue(mem)
if err != nil {
return llvm.Value{}, err
}
globalType := initializer.Type() globalType := initializer.Type()
llvmValue = llvm.AddGlobal(mem.r.mod, globalType, obj.globalName) llvmValue = llvm.AddGlobal(mem.r.mod, globalType, obj.globalName)
llvmValue.SetInitializer(initializer) llvmValue.SetInitializer(initializer)
@@ -551,12 +537,9 @@ func (v pointerValue) toLLVMValue(llvmType llvm.Type, mem *memoryView) (llvm.Val
// Set the initializer for the global. Do this after creation to avoid // Set the initializer for the global. Do this after creation to avoid
// infinite recursion between creating the global and creating the // infinite recursion between creating the global and creating the
// contents of the global (if the global contains itself). // contents of the global (if the global contains itself).
initializer, err := obj.buffer.toLLVMValue(globalType, mem) initializer := obj.buffer.toLLVMValue(globalType, mem)
if err != nil {
return llvm.Value{}, err
}
if checks && initializer.Type() != globalType { if checks && initializer.Type() != globalType {
return llvm.Value{}, errors.New("interp: allocated value does not match allocated type") panic("allocated value does not match allocated type")
} }
llvmValue.SetInitializer(initializer) llvmValue.SetInitializer(initializer)
} }
@@ -569,7 +552,7 @@ func (v pointerValue) toLLVMValue(llvmType llvm.Type, mem *memoryView) (llvm.Val
} }
if llvmType.IsNil() { if llvmType.IsNil() {
return llvmValue, nil return llvmValue
} }
if llvmType.TypeKind() != llvm.PointerTypeKind { if llvmType.TypeKind() != llvm.PointerTypeKind {
@@ -581,7 +564,7 @@ func (v pointerValue) toLLVMValue(llvmType llvm.Type, mem *memoryView) (llvm.Val
// This can be worked around by simply converting to a raw value, // This can be worked around by simply converting to a raw value,
// rawValue knows how to create such structs. // rawValue knows how to create such structs.
if v.offset() != 0 { if v.offset() != 0 {
return llvm.Value{}, errors.New("interp: offset set without known pointer type") panic("offset set without known pointer type")
} }
return v.asRawValue(mem.r).toLLVMValue(llvmType, mem) return v.asRawValue(mem.r).toLLVMValue(llvmType, mem)
} }
@@ -592,14 +575,14 @@ func (v pointerValue) toLLVMValue(llvmType llvm.Type, mem *memoryView) (llvm.Val
if v.offset() != 0 { if v.offset() != 0 {
// This should never happen, if offset is non-zero, the types // This should never happen, if offset is non-zero, the types
// shouldn't match. // shouldn't match.
return llvm.Value{}, errors.New("interp: offset set while there is no way to convert the type") panic("offset set while there is no way to convert the type")
} }
return llvmValue, nil return llvmValue
} }
if v.offset() == 0 { if v.offset() == 0 {
// Offset is zero, so we can just bitcast to get a correct pointer. // Offset is zero, so we can just bitcast to get a correct pointer.
return llvm.ConstBitCast(llvmValue, llvmType), nil return llvm.ConstBitCast(llvmValue, llvmType)
} }
// We need to make a constant GEP for pointer arithmetic. // We need to make a constant GEP for pointer arithmetic.
@@ -623,11 +606,11 @@ func (v pointerValue) toLLVMValue(llvmType llvm.Type, mem *memoryView) (llvm.Val
offset -= int64(mem.r.targetData.ElementOffset(objectElementType, element)) offset -= int64(mem.r.targetData.ElementOffset(objectElementType, element))
objectElementType = objectElementType.StructElementTypes()[element] objectElementType = objectElementType.StructElementTypes()[element]
default: default:
return llvm.Value{}, errors.New("interp: pointer index with something other than a struct or array?") panic("pointer index with something other than a struct or array?")
} }
} }
if offset < 0 { if offset < 0 {
return llvm.Value{}, errors.New("interp: offset has somehow gone negative, this should be impossible") panic("offset has somehow gone negative, this should be impossible")
} }
// Finally do the gep, using the above computed indices. // Finally do the gep, using the above computed indices.
@@ -635,9 +618,283 @@ func (v pointerValue) toLLVMValue(llvmType llvm.Type, mem *memoryView) (llvm.Val
// the bits of the pointer are now correct, just not the type). // the bits of the pointer are now correct, just not the type).
gep := llvm.ConstInBoundsGEP(llvmValue, indices) gep := llvm.ConstInBoundsGEP(llvmValue, indices)
if gep.Type() != llvmType { if gep.Type() != llvmType {
return llvm.ConstBitCast(gep, llvmType), nil return llvm.ConstBitCast(gep, llvmType)
} }
return gep, nil return gep
}
// mapValue implements a Go map which is created at compile time and stored as a
// global variable.
// The value itself is only used as part of an object (object.buffer). Maps are
// reference types aka pointers, so it can only be used as a pointerValue, not
// directly.
type mapValue struct {
r *runner
pkgName string
size uint32 // byte size of runtime.hashmap
hashmap llvm.Value
keyIsString bool
keys []interface{} // either rawValue (for binary key) or mapStringKey (for string key)
values []rawValue
keySize uint32
valueSize uint32
}
type mapStringKey struct {
buf pointerValue
size uint64
data []uint64
}
func newMapValue(r *runner, hashmapPointerType llvm.Type, keySize, valueSize uint32) *mapValue {
size := uint32(r.targetData.TypeAllocSize(hashmapPointerType.ElementType()))
return &mapValue{
r: r,
pkgName: r.pkgName,
size: size,
keySize: keySize,
valueSize: valueSize,
}
}
func (v *mapValue) len(r *runner) uint32 {
return v.size
}
func (v *mapValue) clone() value {
// Return a copy of mapValue.
clone := *v
clone.keys = append([]interface{}{}, clone.keys...)
clone.values = append([]rawValue{}, clone.values...)
return &clone
}
func (v *mapValue) asPointer(r *runner) (pointerValue, error) {
panic("interp: mapValue.asPointer")
}
func (v *mapValue) asRawValue(r *runner) rawValue {
panic("interp: mapValue.asRawValue")
}
func (v *mapValue) Uint() uint64 {
panic("interp: mapValue.Uint")
}
func (v *mapValue) Int() int64 {
panic("interp: mapValue.Int")
}
// Temporary struct to collect data before turning this into a hashmap bucket
// LLVM value.
type mapBucket struct {
m *mapValue
tophash [8]uint8
keys []rawValue // can have up to 8 keys
values []rawValue // can have up to 8 values, len(keys) == len(values)
}
// create returns a (pointer to a) buffer structurally equivalent to
// runtime.hashmapBucket.
func (b *mapBucket) create(ctx llvm.Context, nextBucket llvm.Value, mem *memoryView) llvm.Value {
// Create tophash array.
int8Type := ctx.Int8Type()
tophashValues := make([]llvm.Value, 8)
for i := range tophashValues {
tophashValues[i] = llvm.ConstInt(int8Type, uint64(b.tophash[i]), false)
}
tophash := llvm.ConstArray(int8Type, tophashValues)
// Create next pointer (if not set).
if nextBucket.IsNil() {
nextBucket = llvm.ConstNull(llvm.PointerType(int8Type, 0))
}
// Create data for keys.
var keyValues []llvm.Value
for _, key := range b.keys {
keyValues = append(keyValues, key.rawLLVMValue(mem))
}
if len(b.keys) < 8 {
keyValues = append(keyValues, llvm.ConstNull(llvm.ArrayType(int8Type, int(b.m.keySize)*(8-len(b.keys)))))
}
keyValue := ctx.ConstStruct(keyValues, false)
if checks && uint32(b.m.r.targetData.TypeAllocSize(keyValue.Type())) != b.m.keySize*8 {
panic("key size invalid")
}
// Create data for values.
var valueValues []llvm.Value
for _, value := range b.values {
valueValues = append(valueValues, value.rawLLVMValue(mem))
}
if len(b.values) < 8 {
valueValues = append(valueValues, llvm.ConstNull(llvm.ArrayType(int8Type, int(b.m.valueSize)*(8-len(b.values)))))
}
valueValue := ctx.ConstStruct(valueValues, false)
if checks && uint32(b.m.r.targetData.TypeAllocSize(valueValue.Type())) != b.m.valueSize*8 {
panic("value size invalid")
}
// Create the bucket.
bucketInitializer := ctx.ConstStruct([]llvm.Value{
tophash,
nextBucket,
keyValue,
valueValue,
}, false)
bucket := llvm.AddGlobal(b.m.r.mod, bucketInitializer.Type(), b.m.pkgName+"$mapbucket")
bucket.SetInitializer(bucketInitializer)
bucket.SetLinkage(llvm.InternalLinkage)
bucket.SetUnnamedAddr(true)
return bucket
}
func (v *mapValue) toLLVMValue(hashmapType llvm.Type, mem *memoryView) llvm.Value {
if !v.hashmap.IsNil() {
return v.hashmap
}
// Create a slice of buckets with all the keys and values in the hashmap.
var buckets []*mapBucket
var bucket *mapBucket
for i, key := range v.keys {
var data []uint64
var keyValue rawValue
switch key := key.(type) {
case mapStringKey:
data = key.data
keyValue = newRawValue(v.keySize)
// runtime._string is {ptr, length}
for i := uint32(0); i < v.keySize/2; i++ {
keyValue.buf[i] = key.buf.pointer
}
copy(keyValue.buf[v.keySize/2:], literalValue{key.size}.asRawValue(v.r).buf)
case rawValue:
if key.hasPointer() {
panic("todo: map key with pointer")
}
data = key.buf
keyValue = key
default:
panic("unknown map key type")
}
buf := make([]byte, len(data))
for i, p := range data {
buf[i] = byte(p)
}
hash := v.hash(buf)
if i%8 == 0 {
bucket = &mapBucket{m: v}
buckets = append(buckets, bucket)
}
bucket.tophash[i%8] = v.topHash(hash)
bucket.keys = append(bucket.keys, keyValue)
bucket.values = append(bucket.values, v.values[i])
}
// Convert these buckets into LLVM global variables.
ctx := v.r.mod.Context()
var nextBucket llvm.Value
for i := len(buckets) - 1; i >= 0; i-- {
bucket = buckets[i]
bucketValue := bucket.create(ctx, nextBucket, mem)
nextBucket = bucketValue
}
firstBucket := nextBucket
if firstBucket.IsNil() {
firstBucket = llvm.ConstNull(mem.r.i8ptrType)
} else {
firstBucket = llvm.ConstBitCast(firstBucket, mem.r.i8ptrType)
}
// Create the hashmap itself, pointing to these buckets.
hashmapPointerType := llvm.PointerType(hashmapType, 0)
hashmap := llvm.ConstNamedStruct(hashmapType, []llvm.Value{
llvm.ConstPointerNull(hashmapPointerType), // next
firstBucket, // buckets
llvm.ConstInt(hashmapType.StructElementTypes()[2], uint64(len(v.keys)), false), // count
llvm.ConstInt(ctx.Int8Type(), uint64(v.keySize), false), // keySize
llvm.ConstInt(ctx.Int8Type(), uint64(v.valueSize), false), // valueSize
llvm.ConstInt(ctx.Int8Type(), 0, false), // bucketBits
})
v.hashmap = hashmap
return v.hashmap
}
// putString does a map assign operation, assuming that the map is of type
// map[string]T.
func (v *mapValue) putString(mem *memoryView, stringBuf pointerValue, stringLen uint64, valuePtr pointerValue) error {
if !v.hashmap.IsNil() {
return errMapAlreadyCreated
}
value := mem.load(valuePtr, v.valueSize)
stringValue := mem.load(stringBuf, uint32(stringLen)).asRawValue(v.r)
if stringValue.hasPointer() {
panic("interp: string contains pointer")
}
// TODO: avoid duplicate keys
v.keys = append(v.keys, mapStringKey{stringBuf, stringLen, stringValue.buf})
v.values = append(v.values, value.asRawValue(v.r))
v.keyIsString = true
return nil
}
// putBinary does a map assign operation for binary data (e.g. [3]int etc). The
// key must not contain pointer values.
func (v *mapValue) putBinary(mem *memoryView, keyPtr, valuePtr pointerValue) error {
if !v.hashmap.IsNil() {
return errMapAlreadyCreated
}
key := mem.load(keyPtr, v.keySize)
value := mem.load(valuePtr, v.valueSize)
// Sanity checks.
if v.keySize != key.len(mem.r) || v.valueSize != value.len(mem.r) {
// This is a bug (not unhandled input), so panic.
panic("interp: key or value size mismatch")
}
if v.keyIsString {
panic("cannot put binary keys in string map")
}
// TODO: avoid duplicate keys
v.keys = append(v.keys, key.asRawValue(v.r))
v.values = append(v.values, value.asRawValue(v.r))
return nil
}
// 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 (v *mapValue) hash(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 (v *mapValue) topHash(hash uint32) uint8 {
tophash := uint8(hash >> 24)
if tophash < 1 {
// 0 means empty slot, so make it bigger.
tophash++
}
return tophash
}
func (v *mapValue) String() string {
return "<map keySize=" + strconv.Itoa(int(v.keySize)) + " valueSize=" + strconv.Itoa(int(v.valueSize)) + ">"
} }
// rawValue is a raw memory buffer that can store either pointers or regular // rawValue is a raw memory buffer that can store either pointers or regular
@@ -761,7 +1018,7 @@ func (v rawValue) equal(rhs rawValue) bool {
// goes. The resulting value does not have a specified type, but it will be the // goes. The resulting value does not have a specified type, but it will be the
// same size and have the same bytes if it was created with a provided LLVM type // same size and have the same bytes if it was created with a provided LLVM type
// (through toLLVMValue). // (through toLLVMValue).
func (v rawValue) rawLLVMValue(mem *memoryView) (llvm.Value, error) { func (v rawValue) rawLLVMValue(mem *memoryView) llvm.Value {
var structFields []llvm.Value var structFields []llvm.Value
ctx := mem.r.mod.Context() ctx := mem.r.mod.Context()
int8Type := ctx.Int8Type() int8Type := ctx.Int8Type()
@@ -785,16 +1042,13 @@ func (v rawValue) rawLLVMValue(mem *memoryView) (llvm.Value, error) {
for i := uint32(0); i < uint32(len(v.buf)); { for i := uint32(0); i < uint32(len(v.buf)); {
if v.buf[i] > 255 { if v.buf[i] > 255 {
addBytes() addBytes()
field, err := pointerValue{v.buf[i]}.toLLVMValue(llvm.Type{}, mem) field := pointerValue{v.buf[i]}.toLLVMValue(llvm.Type{}, mem)
if err != nil {
return llvm.Value{}, err
}
elementType := field.Type().ElementType() elementType := field.Type().ElementType()
if elementType.TypeKind() == llvm.StructTypeKind { if elementType.TypeKind() == llvm.StructTypeKind {
// There are some special pointer types that should be used as a // There are some special pointer types that should be used as a
// ptrtoint, so that they can be used in certain optimizations. // ptrtoint, so that they can be used in certain optimizations.
name := elementType.StructName() name := elementType.StructName()
if name == "runtime.typecodeID" || name == "runtime.funcValueWithSignature" { if name == "runtime.typeInInterface" || name == "runtime.funcValueWithSignature" {
uintptrType := ctx.IntType(int(mem.r.pointerSize) * 8) uintptrType := ctx.IntType(int(mem.r.pointerSize) * 8)
field = llvm.ConstPtrToInt(field, uintptrType) field = llvm.ConstPtrToInt(field, uintptrType)
} }
@@ -811,12 +1065,12 @@ func (v rawValue) rawLLVMValue(mem *memoryView) (llvm.Value, error) {
// Return the created data. // Return the created data.
if len(structFields) == 1 { if len(structFields) == 1 {
return structFields[0], nil return structFields[0]
} }
return ctx.ConstStruct(structFields, false), nil return ctx.ConstStruct(structFields, false)
} }
func (v rawValue) toLLVMValue(llvmType llvm.Type, mem *memoryView) (llvm.Value, error) { func (v rawValue) toLLVMValue(llvmType llvm.Type, mem *memoryView) llvm.Value {
isZero := true isZero := true
for _, p := range v.buf { for _, p := range v.buf {
if p != 0 { if p != 0 {
@@ -825,7 +1079,7 @@ func (v rawValue) toLLVMValue(llvmType llvm.Type, mem *memoryView) (llvm.Value,
} }
} }
if isZero { if isZero {
return llvm.ConstNull(llvmType), nil return llvm.ConstNull(llvmType)
} }
switch llvmType.TypeKind() { switch llvmType.TypeKind() {
case llvm.IntegerTypeKind: case llvm.IntegerTypeKind:
@@ -834,17 +1088,7 @@ func (v rawValue) toLLVMValue(llvmType llvm.Type, mem *memoryView) (llvm.Value,
if err != nil { if err != nil {
panic(err) panic(err)
} }
if checks && mem.r.targetData.TypeAllocSize(llvmType) != mem.r.targetData.TypeAllocSize(mem.r.i8ptrType) { return llvm.ConstPtrToInt(ptr.toLLVMValue(llvm.Type{}, mem), llvmType)
// Probably trying to serialize a pointer to a byte array,
// perhaps as a result of rawLLVMValue() in a previous interp
// run.
return llvm.Value{}, errInvalidPtrToIntSize
}
v, err := ptr.toLLVMValue(llvm.Type{}, mem)
if err != nil {
return llvm.Value{}, err
}
return llvm.ConstPtrToInt(v, llvmType), nil
} }
var n uint64 var n uint64
switch llvmType.IntTypeWidth() { switch llvmType.IntTypeWidth() {
@@ -864,7 +1108,7 @@ func (v rawValue) toLLVMValue(llvmType llvm.Type, mem *memoryView) (llvm.Value,
default: default:
panic("unknown integer size") panic("unknown integer size")
} }
return llvm.ConstInt(llvmType, n, false), nil return llvm.ConstInt(llvmType, n, false)
case llvm.StructTypeKind: case llvm.StructTypeKind:
fieldTypes := llvmType.StructElementTypes() fieldTypes := llvmType.StructElementTypes()
fields := make([]llvm.Value, len(fieldTypes)) fields := make([]llvm.Value, len(fieldTypes))
@@ -873,16 +1117,12 @@ func (v rawValue) toLLVMValue(llvmType llvm.Type, mem *memoryView) (llvm.Value,
field := rawValue{ field := rawValue{
buf: v.buf[offset:], buf: v.buf[offset:],
} }
var err error fields[i] = field.toLLVMValue(fieldType, mem)
fields[i], err = field.toLLVMValue(fieldType, mem)
if err != nil {
return llvm.Value{}, err
}
} }
if llvmType.StructName() != "" { if llvmType.StructName() != "" {
return llvm.ConstNamedStruct(llvmType, fields), nil return llvm.ConstNamedStruct(llvmType, fields)
} }
return llvmType.Context().ConstStruct(fields, false), nil return llvmType.Context().ConstStruct(fields, false)
case llvm.ArrayTypeKind: case llvm.ArrayTypeKind:
numElements := llvmType.ArrayLength() numElements := llvmType.ArrayLength()
childType := llvmType.ElementType() childType := llvmType.ElementType()
@@ -893,34 +1133,27 @@ func (v rawValue) toLLVMValue(llvmType llvm.Type, mem *memoryView) (llvm.Value,
field := rawValue{ field := rawValue{
buf: v.buf[offset:], buf: v.buf[offset:],
} }
var err error fields[i] = field.toLLVMValue(childType, mem)
fields[i], err = field.toLLVMValue(childType, mem)
if err != nil {
return llvm.Value{}, err
}
if checks && fields[i].Type() != childType { if checks && fields[i].Type() != childType {
panic("child type doesn't match") panic("child type doesn't match")
} }
} }
return llvm.ConstArray(childType, fields), nil return llvm.ConstArray(childType, fields)
case llvm.PointerTypeKind: case llvm.PointerTypeKind:
if v.buf[0] > 255 { if v.buf[0] > 255 {
// This is a regular pointer. // This is a regular pointer.
llvmValue, err := pointerValue{v.buf[0]}.toLLVMValue(llvm.Type{}, mem) llvmValue := pointerValue{v.buf[0]}.toLLVMValue(llvm.Type{}, mem)
if err != nil {
return llvm.Value{}, err
}
if llvmValue.Type() != llvmType { if llvmValue.Type() != llvmType {
llvmValue = llvm.ConstBitCast(llvmValue, llvmType) llvmValue = llvm.ConstBitCast(llvmValue, llvmType)
} }
return llvmValue, nil return llvmValue
} }
// This is either a null pointer or a raw pointer for memory-mapped I/O // This is either a null pointer or a raw pointer for memory-mapped I/O
// (such as 0xe000ed00). // (such as 0xe000ed00).
ptr := rawValue{v.buf[:mem.r.pointerSize]}.Uint() ptr := rawValue{v.buf[:mem.r.pointerSize]}.Uint()
if ptr == 0 { if ptr == 0 {
// Null pointer. // Null pointer.
return llvm.ConstNull(llvmType), nil return llvm.ConstNull(llvmType)
} }
var ptrValue llvm.Value // the underlying int var ptrValue llvm.Value // the underlying int
switch mem.r.pointerSize { switch mem.r.pointerSize {
@@ -931,19 +1164,19 @@ func (v rawValue) toLLVMValue(llvmType llvm.Type, mem *memoryView) (llvm.Value,
case 2: case 2:
ptrValue = llvm.ConstInt(llvmType.Context().Int16Type(), ptr, false) ptrValue = llvm.ConstInt(llvmType.Context().Int16Type(), ptr, false)
default: default:
return llvm.Value{}, errors.New("interp: unknown pointer size") panic("unknown pointer size")
} }
return llvm.ConstIntToPtr(ptrValue, llvmType), nil return llvm.ConstIntToPtr(ptrValue, llvmType)
case llvm.DoubleTypeKind: case llvm.DoubleTypeKind:
b := rawValue{v.buf[:8]}.Uint() b := rawValue{v.buf[:8]}.Uint()
f := math.Float64frombits(b) f := math.Float64frombits(b)
return llvm.ConstFloat(llvmType, f), nil return llvm.ConstFloat(llvmType, f)
case llvm.FloatTypeKind: case llvm.FloatTypeKind:
b := uint32(rawValue{v.buf[:4]}.Uint()) b := uint32(rawValue{v.buf[:4]}.Uint())
f := math.Float32frombits(b) f := math.Float32frombits(b)
return llvm.ConstFloat(llvmType, float64(f)), nil return llvm.ConstFloat(llvmType, float64(f))
default: default:
return llvm.Value{}, errors.New("interp: todo: raw value to LLVM value: " + llvmType.String()) panic("todo: raw value to LLVM value: " + llvmType.String())
} }
} }
@@ -1132,8 +1365,8 @@ func (v localValue) Int() int64 {
panic("interp: localValue.Int") panic("interp: localValue.Int")
} }
func (v localValue) toLLVMValue(llvmType llvm.Type, mem *memoryView) (llvm.Value, error) { func (v localValue) toLLVMValue(llvmType llvm.Type, mem *memoryView) llvm.Value {
return v.value, nil return v.value
} }
func (r *runner) getValue(llvmValue llvm.Value) value { func (r *runner) getValue(llvmValue llvm.Value) value {
-25
View File
@@ -65,12 +65,6 @@ entry:
call void @modifyExternal(i32* bitcast (void ()* @willModifyGlobal to i32*)) call void @modifyExternal(i32* bitcast (void ()* @willModifyGlobal to i32*))
store i16 7, i16* @main.exposedValue2 store i16 7, i16* @main.exposedValue2
; Test switch statement.
%switch1 = call i64 @testSwitch(i64 1) ; 1 returns 6
%switch2 = call i64 @testSwitch(i64 9) ; 9 returns the default value -1
call void @runtime.printint64(i64 %switch1)
call void @runtime.printint64(i64 %switch2)
ret void ret void
} }
@@ -93,22 +87,3 @@ entry:
store i16 8, i16* @main.exposedValue2 store i16 8, i16* @main.exposedValue2
ret void ret void
} }
define i64 @testSwitch(i64 %val) {
entry:
; Test switch statement.
switch i64 %val, label %otherwise [ i64 0, label %zero
i64 1, label %one
i64 2, label %two ]
zero:
ret i64 5
one:
ret i64 6
two:
ret i64 7
otherwise:
ret i64 -1
}
-23
View File
@@ -25,8 +25,6 @@ entry:
store i16 5, i16* @main.exposedValue1 store i16 5, i16* @main.exposedValue1
call void @modifyExternal(i32* bitcast (void ()* @willModifyGlobal to i32*)) call void @modifyExternal(i32* bitcast (void ()* @willModifyGlobal to i32*))
store i16 7, i16* @main.exposedValue2 store i16 7, i16* @main.exposedValue2
call void @runtime.printint64(i64 6)
call void @runtime.printint64(i64 -1)
ret void ret void
} }
@@ -46,24 +44,3 @@ entry:
store i16 8, i16* @main.exposedValue2 store i16 8, i16* @main.exposedValue2
ret void ret void
} }
define i64 @testSwitch(i64 %val) local_unnamed_addr {
entry:
switch i64 %val, label %otherwise [
i64 0, label %zero
i64 1, label %one
i64 2, label %two
]
zero: ; preds = %entry
ret i64 5
one: ; preds = %entry
ret i64 6
two: ; preds = %entry
ret i64 7
otherwise: ; preds = %entry
ret i64 -1
}
+6 -5
View File
@@ -1,16 +1,17 @@
target datalayout = "e-m:e-i64:64-f80:128-n8:16:32:64-S128" target datalayout = "e-m:e-i64:64-f80:128-n8:16:32:64-S128"
target triple = "x86_64--linux" target triple = "x86_64--linux"
%runtime.typecodeID = type { %runtime.typecodeID*, i64, %runtime.interfaceMethodInfo* } %runtime.typecodeID = type { %runtime.typecodeID*, i64 }
%runtime.interfaceMethodInfo = type { i8*, i64 } %runtime.interfaceMethodInfo = type { i8*, i64 }
%runtime.typeInInterface = type { %runtime.typecodeID*, %runtime.interfaceMethodInfo* }
@main.v1 = global i1 0 @main.v1 = global i1 0
@"reflect/types.type:named:main.foo" = private constant %runtime.typecodeID { %runtime.typecodeID* @"reflect/types.type:basic:int", i64 0, %runtime.interfaceMethodInfo* null } @"reflect/types.type:named:main.foo" = private constant %runtime.typecodeID { %runtime.typecodeID* @"reflect/types.type:basic:int", i64 0 }
@"reflect/types.typeid:named:main.foo" = external constant i8
@"reflect/types.type:basic:int" = external constant %runtime.typecodeID @"reflect/types.type:basic:int" = external constant %runtime.typecodeID
@"typeInInterface:reflect/types.type:named:main.foo" = private constant %runtime.typeInInterface { %runtime.typecodeID* @"reflect/types.type:named:main.foo", %runtime.interfaceMethodInfo* null }
declare i1 @runtime.typeAssert(i64, i8*, i8*, i8*) declare i1 @runtime.typeAssert(i64, %runtime.typecodeID*, i8*, i8*)
define void @runtime.initAll() unnamed_addr { define void @runtime.initAll() unnamed_addr {
entry: entry:
@@ -21,7 +22,7 @@ entry:
define internal void @main.init() unnamed_addr { define internal void @main.init() unnamed_addr {
entry: entry:
; Test type asserts. ; Test type asserts.
%typecode = call i1 @runtime.typeAssert(i64 ptrtoint (%runtime.typecodeID* @"reflect/types.type:named:main.foo" to i64), i8* @"reflect/types.typeid:named:main.foo", i8* undef, i8* null) %typecode = call i1 @runtime.typeAssert(i64 ptrtoint (%runtime.typeInInterface* @"typeInInterface:reflect/types.type:named:main.foo" to i64), %runtime.typecodeID* @"reflect/types.type:named:main.foo", i8* undef, i8* null)
store i1 %typecode, i1* @main.v1 store i1 %typecode, i1* @main.v1
ret void ret void
} }
+74
View File
@@ -0,0 +1,74 @@
target datalayout = "e-m:e-p:32:32-Fi8-i64:64-v128:64:128-a:0:32-n32-S64"
target triple = "armv6m-none-eabi"
%runtime._string = type { i8*, i32 }
%runtime.hashmap = type { %runtime.hashmap*, i8*, i32, i8, i8, i8 }
@main.m = global %runtime.hashmap* null
@main.binaryMap = global %runtime.hashmap* null
@main.stringMap = global %runtime.hashmap* null
@main.init.string = internal unnamed_addr constant [7 x i8] c"CONNECT"
declare %runtime.hashmap* @runtime.hashmapMake(i8, i8, i32, i8* %context, i8* %parentHandle)
declare void @runtime.hashmapBinarySet(%runtime.hashmap*, i8*, i8*, i8* %context, i8* %parentHandle)
declare void @runtime.hashmapStringSet(%runtime.hashmap*, i8*, i32, i8*, i8* %context, i8* %parentHandle)
declare void @llvm.lifetime.end.p0i8(i64, i8*)
declare void @llvm.lifetime.start.p0i8(i64, i8*)
define void @runtime.initAll() unnamed_addr {
entry:
call void @main.init(i8* undef, i8* null)
ret void
}
define internal void @main.init(i8* %context, i8* %parentHandle) unnamed_addr {
entry:
; Test that hashmap optimizations generally work (even with lifetimes).
%hashmap.key = alloca i8
%hashmap.value = alloca %runtime._string
%0 = call %runtime.hashmap* @runtime.hashmapMake(i8 1, i8 8, i32 1, i8* undef, i8* null)
%hashmap.value.bitcast = bitcast %runtime._string* %hashmap.value to i8*
call void @llvm.lifetime.start.p0i8(i64 8, i8* %hashmap.value.bitcast)
store %runtime._string { i8* getelementptr inbounds ([7 x i8], [7 x i8]* @main.init.string, i32 0, i32 0), i32 7 }, %runtime._string* %hashmap.value
call void @llvm.lifetime.start.p0i8(i64 1, i8* %hashmap.key)
store i8 1, i8* %hashmap.key
call void @runtime.hashmapBinarySet(%runtime.hashmap* %0, i8* %hashmap.key, i8* %hashmap.value.bitcast, i8* undef, i8* null)
call void @llvm.lifetime.end.p0i8(i64 1, i8* %hashmap.key)
call void @llvm.lifetime.end.p0i8(i64 8, i8* %hashmap.value.bitcast)
store %runtime.hashmap* %0, %runtime.hashmap** @main.m
; Other tests, that can be done in a separate function.
call void @main.testNonConstantBinarySet()
call void @main.testNonConstantStringSet()
ret void
}
; Test that a map loaded from a global can still be used for mapassign
; operations (with binary keys).
define internal void @main.testNonConstantBinarySet() {
%hashmap.key = alloca i8
%hashmap.value = alloca i8
; Create hashmap from global.
%map.new = call %runtime.hashmap* @runtime.hashmapMake(i8 1, i8 1, i32 1, i8* undef, i8* null)
store %runtime.hashmap* %map.new, %runtime.hashmap** @main.binaryMap
%map = load %runtime.hashmap*, %runtime.hashmap** @main.binaryMap
; Do the binary set to the newly loaded map.
store i8 1, i8* %hashmap.key
store i8 2, i8* %hashmap.value
call void @runtime.hashmapBinarySet(%runtime.hashmap* %map, i8* %hashmap.key, i8* %hashmap.value, i8* undef, i8* null)
ret void
}
; Test that a map loaded from a global can still be used for mapassign
; operations (with string keys).
define internal void @main.testNonConstantStringSet() {
%hashmap.value = alloca i8
; Create hashmap from global.
%map.new = call %runtime.hashmap* @runtime.hashmapMake(i8 8, i8 1, i32 1, i8* undef, i8* null)
store %runtime.hashmap* %map.new, %runtime.hashmap** @main.stringMap
%map = load %runtime.hashmap*, %runtime.hashmap** @main.stringMap
; Do the string set to the newly loaded map.
store i8 2, i8* %hashmap.value
call void @runtime.hashmapStringSet(%runtime.hashmap* %map, i8* getelementptr inbounds ([7 x i8], [7 x i8]* @main.init.string, i32 0, i32 0), i32 7, i8* %hashmap.value, i8* undef, i8* null)
ret void
}
+20
View File
@@ -0,0 +1,20 @@
target datalayout = "e-m:e-p:32:32-Fi8-i64:64-v128:64:128-a:0:32-n32-S64"
target triple = "armv6m-none-eabi"
%runtime.hashmap = type { %runtime.hashmap*, i8*, i32, i8, i8, i8 }
@main.m = local_unnamed_addr global %runtime.hashmap* @"main$map"
@main.binaryMap = local_unnamed_addr global %runtime.hashmap* @"main$map.1"
@main.stringMap = local_unnamed_addr global %runtime.hashmap* @"main$map.3"
@main.init.string = internal unnamed_addr constant [7 x i8] c"CONNECT"
@"main$map" = internal global %runtime.hashmap { %runtime.hashmap* null, i8* getelementptr inbounds ({ [8 x i8], i8*, { i8, [7 x i8] }, { { [7 x i8]*, [4 x i8] }, [56 x i8] } }, { [8 x i8], i8*, { i8, [7 x i8] }, { { [7 x i8]*, [4 x i8] }, [56 x i8] } }* @"main$mapbucket", i32 0, i32 0, i32 0), i32 1, i8 1, i8 8, i8 0 }
@"main$mapbucket" = internal unnamed_addr global { [8 x i8], i8*, { i8, [7 x i8] }, { { [7 x i8]*, [4 x i8] }, [56 x i8] } } { [8 x i8] c"\04\00\00\00\00\00\00\00", i8* null, { i8, [7 x i8] } { i8 1, [7 x i8] zeroinitializer }, { { [7 x i8]*, [4 x i8] }, [56 x i8] } { { [7 x i8]*, [4 x i8] } { [7 x i8]* @main.init.string, [4 x i8] c"\07\00\00\00" }, [56 x i8] zeroinitializer } }
@"main$map.1" = internal global %runtime.hashmap { %runtime.hashmap* null, i8* getelementptr inbounds ({ [8 x i8], i8*, { i8, [7 x i8] }, { i8, [7 x i8] } }, { [8 x i8], i8*, { i8, [7 x i8] }, { i8, [7 x i8] } }* @"main$mapbucket.2", i32 0, i32 0, i32 0), i32 1, i8 1, i8 1, i8 0 }
@"main$mapbucket.2" = internal unnamed_addr global { [8 x i8], i8*, { i8, [7 x i8] }, { i8, [7 x i8] } } { [8 x i8] c"\04\00\00\00\00\00\00\00", i8* null, { i8, [7 x i8] } { i8 1, [7 x i8] zeroinitializer }, { i8, [7 x i8] } { i8 2, [7 x i8] zeroinitializer } }
@"main$map.3" = internal global %runtime.hashmap { %runtime.hashmap* null, i8* getelementptr inbounds ({ [8 x i8], i8*, { { [7 x i8]*, [4 x i8] }, [56 x i8] }, { i8, [7 x i8] } }, { [8 x i8], i8*, { { [7 x i8]*, [4 x i8] }, [56 x i8] }, { i8, [7 x i8] } }* @"main$mapbucket.4", i32 0, i32 0, i32 0), i32 1, i8 8, i8 1, i8 0 }
@"main$mapbucket.4" = internal unnamed_addr global { [8 x i8], i8*, { { [7 x i8]*, [4 x i8] }, [56 x i8] }, { i8, [7 x i8] } } { [8 x i8] c"x\00\00\00\00\00\00\00", i8* null, { { [7 x i8]*, [4 x i8] }, [56 x i8] } { { [7 x i8]*, [4 x i8] } { [7 x i8]* @main.init.string, [4 x i8] c"\07\00\00\00" }, [56 x i8] zeroinitializer }, { i8, [7 x i8] } { i8 2, [7 x i8] zeroinitializer } }
define void @runtime.initAll() unnamed_addr {
entry:
ret void
}
-31
View File
@@ -1,31 +0,0 @@
target datalayout = "e-m:e-i64:64-f80:128-n8:16:32:64-S128"
target triple = "x86_64--linux"
@main.phiNodesResultA = global i8 0
@main.phiNodesResultB = global i8 0
define void @runtime.initAll() {
call void @main.init()
ret void
}
; PHI nodes always use the value from the previous block, even in a loop. This
; means that the loop below should swap the values %a and %b on each iteration.
; Previously there was a bug which resulted in %b getting the value 3 on the
; second iteration while it should have gotten 1 (from the first iteration of
; %for.loop).
define internal void @main.init() {
entry:
br label %for.loop
for.loop:
%a = phi i8 [ 1, %entry ], [ %b, %for.loop ]
%b = phi i8 [ 3, %entry ], [ %a, %for.loop ]
%icmp = icmp eq i8 %a, 3
br i1 %icmp, label %for.done, label %for.loop
for.done:
store i8 %a, i8* @main.phiNodesResultA
store i8 %b, i8* @main.phiNodesResultB
ret void
}
-9
View File
@@ -1,9 +0,0 @@
target datalayout = "e-m:e-i64:64-f80:128-n8:16:32:64-S128"
target triple = "x86_64--linux"
@main.phiNodesResultA = local_unnamed_addr global i8 3
@main.phiNodesResultB = local_unnamed_addr global i8 1
define void @runtime.initAll() local_unnamed_addr {
ret void
}
+271
View File
@@ -0,0 +1,271 @@
package ir
import (
"go/ast"
"go/types"
"sort"
"strings"
"github.com/tinygo-org/tinygo/loader"
"golang.org/x/tools/go/ssa"
"tinygo.org/x/go-llvm"
)
// This file provides a wrapper around go/ssa values and adds extra
// functionality to them.
// View on all functions, types, and globals in a program, with analysis
// results.
type Program struct {
Program *ssa.Program
LoaderProgram *loader.Program
mainPkg *ssa.Package
Functions []*Function
functionMap map[*ssa.Function]*Function
}
// Function or method.
type Function struct {
*ssa.Function
LLVMFn llvm.Value
module string // go:wasm-module
linkName string // go:linkname, go:export
exported bool // go:export
nobounds bool // go:nobounds
flag bool // used by dead code elimination
inline InlineType // go:inline
}
// Interface type that is at some point used in a type assert (to check whether
// it implements another interface).
type Interface struct {
Num int
Type *types.Interface
}
type InlineType int
// How much to inline.
const (
// Default behavior. The compiler decides for itself whether any given
// function will be inlined. Whether any function is inlined depends on the
// optimization level.
InlineDefault InlineType = iota
// Inline hint, just like the C inline keyword (signalled using
// //go:inline). The compiler will be more likely to inline this function,
// but it is not a guarantee.
InlineHint
// Don't inline, just like the GCC noinline attribute. Signalled using
// //go:noinline.
InlineNone
)
// Create and initialize a new *Program from a *ssa.Program.
func NewProgram(lprogram *loader.Program) *Program {
program := lprogram.LoadSSA()
program.Build()
mainPkg := program.ImportedPackage(lprogram.MainPkg().ImportPath)
if mainPkg == nil {
panic("could not find main package")
}
p := &Program{
Program: program,
LoaderProgram: lprogram,
mainPkg: mainPkg,
functionMap: make(map[*ssa.Function]*Function),
}
for _, pkg := range lprogram.Sorted() {
p.AddPackage(program.ImportedPackage(pkg.ImportPath))
}
return p
}
// Add a package to this Program. All packages need to be added first before any
// analysis is done for correct results.
func (p *Program) AddPackage(pkg *ssa.Package) {
memberNames := make([]string, 0)
for name := range pkg.Members {
memberNames = append(memberNames, name)
}
sort.Strings(memberNames)
for _, name := range memberNames {
member := pkg.Members[name]
switch member := member.(type) {
case *ssa.Function:
p.addFunction(member)
case *ssa.Type:
methods := getAllMethods(pkg.Prog, member.Type())
if !types.IsInterface(member.Type()) {
// named type
for _, method := range methods {
p.addFunction(pkg.Prog.MethodValue(method))
}
}
case *ssa.Global:
// Ignore. Globals are not handled here.
case *ssa.NamedConst:
// Ignore: these are already resolved.
default:
panic("unknown member type: " + member.String())
}
}
}
func (p *Program) addFunction(ssaFn *ssa.Function) {
if _, ok := p.functionMap[ssaFn]; ok {
return
}
f := &Function{Function: ssaFn}
f.parsePragmas()
p.Functions = append(p.Functions, f)
p.functionMap[ssaFn] = f
for _, anon := range ssaFn.AnonFuncs {
p.addFunction(anon)
}
}
// Return true if this package imports "unsafe", false otherwise.
func hasUnsafeImport(pkg *types.Package) bool {
for _, imp := range pkg.Imports() {
if imp == types.Unsafe {
return true
}
}
return false
}
func (p *Program) GetFunction(ssaFn *ssa.Function) *Function {
return p.functionMap[ssaFn]
}
func (p *Program) MainPkg() *ssa.Package {
return p.mainPkg
}
// Parse compiler directives in the preceding comments.
func (f *Function) parsePragmas() {
if f.Syntax() == nil {
return
}
if decl, ok := f.Syntax().(*ast.FuncDecl); ok && decl.Doc != nil {
for _, comment := range decl.Doc.List {
text := comment.Text
if strings.HasPrefix(text, "//export ") {
// Rewrite '//export' to '//go:export' for compatibility with
// gc.
text = "//go:" + text[2:]
}
if !strings.HasPrefix(text, "//go:") {
continue
}
parts := strings.Fields(text)
switch parts[0] {
case "//go:export":
if len(parts) != 2 {
continue
}
f.linkName = parts[1]
f.exported = true
case "//go:wasm-module":
// Alternative comment for setting the import module.
if len(parts) != 2 {
continue
}
f.module = parts[1]
case "//go:inline":
f.inline = InlineHint
case "//go:noinline":
f.inline = InlineNone
case "//go:linkname":
if len(parts) != 3 || parts[1] != f.Name() {
continue
}
// Only enable go:linkname when the package imports "unsafe".
// This is a slightly looser requirement than what gc uses: gc
// requires the file to import "unsafe", not the package as a
// whole.
if hasUnsafeImport(f.Pkg.Pkg) {
f.linkName = parts[2]
}
case "//go:nobounds":
// Skip bounds checking in this function. Useful for some
// runtime functions.
// This is somewhat dangerous and thus only imported in packages
// that import unsafe.
if hasUnsafeImport(f.Pkg.Pkg) {
f.nobounds = true
}
}
}
}
}
func (f *Function) IsNoBounds() bool {
return f.nobounds
}
// Return true iff this function is externally visible.
func (f *Function) IsExported() bool {
return f.exported || f.CName() != ""
}
// Return the inline directive of this function.
func (f *Function) Inline() InlineType {
return f.inline
}
// Return the module name if not the default.
func (f *Function) Module() string {
return f.module
}
// Return the link name for this function.
func (f *Function) LinkName() string {
if f.linkName != "" {
return f.linkName
}
if f.Signature.Recv() != nil {
// Method on a defined type (which may be a pointer).
return f.RelString(nil)
} else {
// Bare function.
if name := f.CName(); name != "" {
// Name CGo functions directly.
return name
} else {
return f.RelString(nil)
}
}
}
// Return the name of the C function if this is a CGo wrapper. Otherwise, return
// a zero-length string.
func (f *Function) CName() string {
name := f.Name()
if strings.HasPrefix(name, "_Cfunc_") {
// emitted by `go tool cgo`
return name[len("_Cfunc_"):]
}
if strings.HasPrefix(name, "C.") {
// created by ../loader/cgo.go
return name[2:]
}
return ""
}
// Get all methods of a type.
func getAllMethods(prog *ssa.Program, typ types.Type) []*types.Selection {
ms := prog.MethodSets.MethodSet(typ)
methods := make([]*types.Selection, ms.Len())
for i := 0; i < ms.Len(); i++ {
methods[i] = ms.At(i)
}
return methods
}
+149
View File
@@ -0,0 +1,149 @@
package ir
import (
"errors"
"go/types"
"golang.org/x/tools/go/ssa"
)
// This file implements several optimization passes (analysis + transform) to
// optimize code in SSA form before it is compiled to LLVM IR. It is based on
// the IR defined in ir.go.
// Make a readable version of a method signature (including the function name,
// excluding the receiver name). This string is used internally to match
// interfaces and to call the correct method on an interface. Examples:
//
// String() string
// Read([]byte) (int, error)
func MethodSignature(method *types.Func) string {
return method.Name() + signature(method.Type().(*types.Signature))
}
// Make a readable version of a function (pointer) signature.
// Examples:
//
// () string
// (string, int) (int, error)
func signature(sig *types.Signature) string {
s := ""
if sig.Params().Len() == 0 {
s += "()"
} else {
s += "("
for i := 0; i < sig.Params().Len(); i++ {
if i > 0 {
s += ", "
}
s += sig.Params().At(i).Type().String()
}
s += ")"
}
if sig.Results().Len() == 0 {
// keep as-is
} else if sig.Results().Len() == 1 {
s += " " + sig.Results().At(0).Type().String()
} else {
s += " ("
for i := 0; i < sig.Results().Len(); i++ {
if i > 0 {
s += ", "
}
s += sig.Results().At(i).Type().String()
}
s += ")"
}
return s
}
// Simple pass that removes dead code. This pass makes later analysis passes
// more useful.
func (p *Program) SimpleDCE() error {
// Unmark all functions.
for _, f := range p.Functions {
f.flag = false
}
// Initial set of live functions. Include main.main, *.init and runtime.*
// functions.
main, ok := p.mainPkg.Members["main"].(*ssa.Function)
if !ok {
if p.mainPkg.Members["main"] == nil {
return errors.New("function main is undeclared in the main package")
} else {
return errors.New("cannot declare main - must be func")
}
}
runtimePkg := p.Program.ImportedPackage("runtime")
mathPkg := p.Program.ImportedPackage("math")
taskPkg := p.Program.ImportedPackage("internal/task")
p.GetFunction(main).flag = true
worklist := []*ssa.Function{main}
for _, f := range p.Functions {
if f.exported || f.Synthetic == "package initializer" || f.Pkg == runtimePkg || f.Pkg == taskPkg || (f.Pkg == mathPkg && f.Pkg != nil) {
if f.flag {
continue
}
f.flag = true
worklist = append(worklist, f.Function)
}
}
// Mark all called functions recursively.
for len(worklist) != 0 {
f := worklist[len(worklist)-1]
worklist = worklist[:len(worklist)-1]
for _, block := range f.Blocks {
for _, instr := range block.Instrs {
if instr, ok := instr.(*ssa.MakeInterface); ok {
for _, sel := range getAllMethods(p.Program, instr.X.Type()) {
fn := p.Program.MethodValue(sel)
callee := p.GetFunction(fn)
if callee == nil {
// TODO: why is this necessary?
p.addFunction(fn)
callee = p.GetFunction(fn)
}
if !callee.flag {
callee.flag = true
worklist = append(worklist, callee.Function)
}
}
}
for _, operand := range instr.Operands(nil) {
if operand == nil || *operand == nil {
continue
}
switch operand := (*operand).(type) {
case *ssa.Function:
f := p.GetFunction(operand)
if f == nil {
// FIXME HACK: this function should have been
// discovered already. It is not for bound methods.
p.addFunction(operand)
f = p.GetFunction(operand)
}
if !f.flag {
f.flag = true
worklist = append(worklist, operand)
}
}
}
}
}
}
// Remove unmarked functions.
livefunctions := []*Function{}
for _, f := range p.Functions {
if f.flag {
livefunctions = append(livefunctions, f)
} else {
delete(p.functionMap, f.Function)
}
}
p.Functions = livefunctions
return nil
}
Submodule lib/stm32-svd deleted from 3a0b0829b7
+26 -43
View File
@@ -2,7 +2,6 @@ package loader
import ( import (
"bytes" "bytes"
"crypto/sha512"
"encoding/json" "encoding/json"
"errors" "errors"
"fmt" "fmt"
@@ -12,12 +11,12 @@ import (
"go/token" "go/token"
"go/types" "go/types"
"io" "io"
"io/ioutil"
"os" "os"
"os/exec" "os/exec"
"path/filepath" "path/filepath"
"strconv" "strconv"
"strings" "strings"
"syscall"
"github.com/tinygo-org/tinygo/cgo" "github.com/tinygo-org/tinygo/cgo"
"github.com/tinygo-org/tinygo/compileopts" "github.com/tinygo-org/tinygo/compileopts"
@@ -44,7 +43,6 @@ type Program struct {
type PackageJSON struct { type PackageJSON struct {
Dir string Dir string
ImportPath string ImportPath string
Name string
ForTest string ForTest string
// Source files // Source files
@@ -53,8 +51,7 @@ type PackageJSON struct {
CFiles []string CFiles []string
// Dependency information // Dependency information
Imports []string Imports []string
ImportMap map[string]string
// Error information // Error information
Error *struct { Error *struct {
@@ -68,12 +65,10 @@ type PackageJSON struct {
type Package struct { type Package struct {
PackageJSON PackageJSON
program *Program program *Program
Files []*ast.File Files []*ast.File
FileHashes map[string][]byte Pkg *types.Package
CFlags []string // CFlags used during CGo preprocessing (only set if CGo is used) info types.Info
Pkg *types.Package
info types.Info
} }
// Load loads the given package with all dependencies (including the runtime // Load loads the given package with all dependencies (including the runtime
@@ -113,7 +108,10 @@ func Load(config *compileopts.Config, inputPkgs []string, clangHeaders string, t
err = cmd.Run() err = cmd.Run()
if err != nil { if err != nil {
if exitErr, ok := err.(*exec.ExitError); ok { if exitErr, ok := err.(*exec.ExitError); ok {
os.Exit(exitErr.ExitCode()) if status, ok := exitErr.Sys().(syscall.WaitStatus); ok {
os.Exit(status.ExitStatus())
}
os.Exit(1)
} }
return nil, fmt.Errorf("failed to run `go list`: %s", err) return nil, fmt.Errorf("failed to run `go list`: %s", err)
} }
@@ -122,8 +120,7 @@ func Load(config *compileopts.Config, inputPkgs []string, clangHeaders string, t
decoder := json.NewDecoder(buf) decoder := json.NewDecoder(buf)
for { for {
pkg := &Package{ pkg := &Package{
program: p, program: p,
FileHashes: make(map[string][]byte),
info: types.Info{ info: types.Info{
Types: make(map[ast.Expr]types.TypeAndValue), Types: make(map[ast.Expr]types.TypeAndValue),
Defs: make(map[*ast.Ident]types.Object), Defs: make(map[*ast.Ident]types.Object),
@@ -282,15 +279,17 @@ func (p *Program) Parse() error {
} }
// parseFile is a wrapper around parser.ParseFile. // parseFile is a wrapper around parser.ParseFile.
func (p *Package) parseFile(path string, mode parser.Mode) (*ast.File, error) { func (p *Program) parseFile(path string, mode parser.Mode) (*ast.File, error) {
originalPath := p.program.getOriginalPath(path) if p.fset == nil {
data, err := ioutil.ReadFile(path) p.fset = token.NewFileSet()
}
rd, err := os.Open(path)
if err != nil { if err != nil {
return nil, err return nil, err
} }
sum := sha512.Sum512_224(data) defer rd.Close()
p.FileHashes[originalPath] = sum[:] return parser.ParseFile(p.fset, p.getOriginalPath(path), rd, mode)
return parser.ParseFile(p.program.fset, originalPath, data, mode)
} }
// Parse parses and typechecks this package. // Parse parses and typechecks this package.
@@ -336,16 +335,7 @@ func (p *Package) Check() error {
// Do typechecking of the package. // Do typechecking of the package.
checker.Importer = p checker.Importer = p
packageName := p.ImportPath typesPkg, err := checker.Check(p.ImportPath, p.program.fset, p.Files, &p.info)
if p.Name == "main" {
// The main package normally has a different import path, such as
// "command-line-arguments" or "./testdata/cgo". Therefore, use the name
// "main" in such a case: this package isn't imported from anywhere.
// This is safe as it isn't possible to import a package with the name
// "main".
packageName = "main"
}
typesPkg, err := checker.Check(packageName, p.program.fset, p.Files, &p.info)
if err != nil { if err != nil {
if err, ok := err.(Errors); ok { if err, ok := err.(Errors); ok {
return err return err
@@ -366,7 +356,7 @@ func (p *Package) parseFiles() ([]*ast.File, error) {
if !filepath.IsAbs(file) { if !filepath.IsAbs(file) {
file = filepath.Join(p.Dir, file) file = filepath.Join(p.Dir, file)
} }
f, err := p.parseFile(file, parser.ParseComments) f, err := p.program.parseFile(file, parser.ParseComments)
if err != nil { if err != nil {
fileErrs = append(fileErrs, err) fileErrs = append(fileErrs, err)
return return
@@ -382,17 +372,13 @@ func (p *Package) parseFiles() ([]*ast.File, error) {
// Do CGo processing. // Do CGo processing.
if len(p.CgoFiles) != 0 { if len(p.CgoFiles) != 0 {
var initialCFlags []string var cflags []string
initialCFlags = append(initialCFlags, p.program.config.CFlags()...) cflags = append(cflags, p.program.config.CFlags()...)
initialCFlags = append(initialCFlags, "-I"+p.Dir) cflags = append(cflags, "-I"+p.Dir)
if p.program.clangHeaders != "" { if p.program.clangHeaders != "" {
initialCFlags = append(initialCFlags, "-Xclang", "-internal-isystem", "-Xclang", p.program.clangHeaders) cflags = append(cflags, "-Xclang", "-internal-isystem", "-Xclang", p.program.clangHeaders)
}
generated, cflags, ldflags, accessedFiles, errs := cgo.Process(files, p.program.workingDir, p.program.fset, initialCFlags)
p.CFlags = append(initialCFlags, cflags...)
for path, hash := range accessedFiles {
p.FileHashes[path] = hash
} }
generated, ldflags, errs := cgo.Process(files, p.program.workingDir, p.program.fset, cflags)
if errs != nil { if errs != nil {
fileErrs = append(fileErrs, errs...) fileErrs = append(fileErrs, errs...)
} }
@@ -415,9 +401,6 @@ func (p *Package) Import(to string) (*types.Package, error) {
if to == "unsafe" { if to == "unsafe" {
return types.Unsafe, nil return types.Unsafe, nil
} }
if newTo, ok := p.ImportMap[to]; ok && !strings.HasSuffix(newTo, ".test]") {
to = newTo
}
if imported, ok := p.program.Packages[to]; ok { if imported, ok := p.program.Packages[to]; ok {
return imported.Pkg, nil return imported.Pkg, nil
} else { } else {
-8
View File
@@ -16,11 +16,3 @@ func (p *Program) LoadSSA() *ssa.Program {
return prog return prog
} }
// LoadSSA constructs the SSA form of this package.
//
// The program must already be parsed and type-checked with the .Parse() method.
func (p *Package) LoadSSA() *ssa.Package {
prog := ssa.NewProgram(p.program.fset, ssa.SanityCheckFunctions|ssa.BareInits|ssa.GlobalDebug)
return prog.CreatePackage(p.Pkg, p.Files, &p.info, true)
}
+110 -186
View File
@@ -13,24 +13,20 @@ import (
"os/exec" "os/exec"
"os/signal" "os/signal"
"path/filepath" "path/filepath"
"regexp"
"runtime" "runtime"
"strconv"
"strings" "strings"
"sync/atomic" "syscall"
"time" "time"
"github.com/google/shlex"
"github.com/mattn/go-colorable"
"github.com/tinygo-org/tinygo/builder" "github.com/tinygo-org/tinygo/builder"
"github.com/tinygo-org/tinygo/compileopts" "github.com/tinygo-org/tinygo/compileopts"
"github.com/tinygo-org/tinygo/goenv" "github.com/tinygo-org/tinygo/goenv"
"github.com/tinygo-org/tinygo/interp" "github.com/tinygo-org/tinygo/interp"
"github.com/tinygo-org/tinygo/loader" "github.com/tinygo-org/tinygo/loader"
"github.com/tinygo-org/tinygo/transform"
"tinygo.org/x/go-llvm" "tinygo.org/x/go-llvm"
"go.bug.st/serial" "go.bug.st/serial"
"go.bug.st/serial/enumerator"
) )
var ( var (
@@ -93,14 +89,6 @@ func copyFile(src, dst string) error {
return err return err
} }
// executeCommand is a simple wrapper to exec.Cmd
func executeCommand(options *compileopts.Options, name string, arg ...string) *exec.Cmd {
if options.PrintCommands {
fmt.Printf("%s %s\n", name, strings.Join(arg, " "))
}
return exec.Command(name, arg...)
}
// Build compiles and links the given package and writes it to outpath. // Build compiles and links the given package and writes it to outpath.
func Build(pkgName, outpath string, options *compileopts.Options) error { func Build(pkgName, outpath string, options *compileopts.Options) error {
config, err := builder.NewConfig(options) config, err := builder.NewConfig(options)
@@ -160,7 +148,7 @@ func Test(pkgName string, options *compileopts.Options, testCompileOnly bool, ou
} }
if len(config.Target.Emulator) == 0 { if len(config.Target.Emulator) == 0 {
// Run directly. // Run directly.
cmd := executeCommand(config.Options, result.Binary) cmd := exec.Command(result.Binary)
cmd.Stdout = os.Stdout cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr cmd.Stderr = os.Stderr
cmd.Dir = result.MainDir cmd.Dir = result.MainDir
@@ -168,7 +156,10 @@ func Test(pkgName string, options *compileopts.Options, testCompileOnly bool, ou
if err != nil { if err != nil {
// Propagate the exit code // Propagate the exit code
if err, ok := err.(*exec.ExitError); ok { if err, ok := err.(*exec.ExitError); ok {
os.Exit(err.ExitCode()) if status, ok := err.Sys().(syscall.WaitStatus); ok {
os.Exit(status.ExitStatus())
}
os.Exit(1)
} }
return &commandError{"failed to run compiled binary", result.Binary, err} return &commandError{"failed to run compiled binary", result.Binary, err}
} }
@@ -176,7 +167,7 @@ func Test(pkgName string, options *compileopts.Options, testCompileOnly bool, ou
} else { } else {
// Run in an emulator. // Run in an emulator.
args := append(config.Target.Emulator[1:], result.Binary) args := append(config.Target.Emulator[1:], result.Binary)
cmd := executeCommand(config.Options, config.Target.Emulator[0], args...) cmd := exec.Command(config.Target.Emulator[0], args...)
buf := &bytes.Buffer{} buf := &bytes.Buffer{}
w := io.MultiWriter(os.Stdout, buf) w := io.MultiWriter(os.Stdout, buf)
cmd.Stdout = w cmd.Stdout = w
@@ -243,12 +234,15 @@ func Flash(pkgName, port string, options *compileopts.Options) error {
return builder.Build(pkgName, fileExt, config, func(result builder.BuildResult) error { return builder.Build(pkgName, fileExt, config, func(result builder.BuildResult) error {
// do we need port reset to put MCU into bootloader mode? // do we need port reset to put MCU into bootloader mode?
if config.Target.PortReset == "true" && flashMethod != "openocd" { if config.Target.PortReset == "true" && flashMethod != "openocd" {
port, err := getDefaultPort(strings.FieldsFunc(port, func(c rune) bool { return c == ',' })) if port == "" {
if err != nil { var err error
return err port, err = getDefaultPort()
if err != nil {
return err
}
} }
err = touchSerialPortAt1200bps(port) err := touchSerialPortAt1200bps(port)
if err != nil { if err != nil {
return &commandError{"failed to reset port", result.Binary, err} return &commandError{"failed to reset port", result.Binary, err}
} }
@@ -262,17 +256,17 @@ func Flash(pkgName, port string, options *compileopts.Options) error {
// Create the command. // Create the command.
flashCmd := config.Target.FlashCommand flashCmd := config.Target.FlashCommand
fileToken := "{" + fileExt[1:] + "}" fileToken := "{" + fileExt[1:] + "}"
flashCmd = strings.ReplaceAll(flashCmd, fileToken, result.Binary) flashCmd = strings.Replace(flashCmd, fileToken, result.Binary, -1)
if strings.Contains(flashCmd, "{port}") { if port == "" && strings.Contains(flashCmd, "{port}") {
var err error var err error
port, err = getDefaultPort(strings.FieldsFunc(port, func(c rune) bool { return c == ',' })) port, err = getDefaultPort()
if err != nil { if err != nil {
return err return err
} }
} }
flashCmd = strings.ReplaceAll(flashCmd, "{port}", port) flashCmd = strings.Replace(flashCmd, "{port}", port, -1)
// Execute the command. // Execute the command.
var cmd *exec.Cmd var cmd *exec.Cmd
@@ -282,9 +276,9 @@ func Flash(pkgName, port string, options *compileopts.Options) error {
if len(command) < 2 { if len(command) < 2 {
return errors.New("invalid flash command") return errors.New("invalid flash command")
} }
cmd = executeCommand(config.Options, command[0], command[1:]...) cmd = exec.Command(command[0], command[1:]...)
default: default:
cmd = executeCommand(config.Options, "/bin/sh", "-c", flashCmd) cmd = exec.Command("/bin/sh", "-c", flashCmd)
} }
cmd.Stdout = os.Stdout cmd.Stdout = os.Stdout
@@ -298,13 +292,13 @@ func Flash(pkgName, port string, options *compileopts.Options) error {
case "msd": case "msd":
switch fileExt { switch fileExt {
case ".uf2": case ".uf2":
err := flashUF2UsingMSD(config.Target.FlashVolume, result.Binary, config.Options) err := flashUF2UsingMSD(config.Target.FlashVolume, result.Binary)
if err != nil { if err != nil {
return &commandError{"failed to flash", result.Binary, err} return &commandError{"failed to flash", result.Binary, err}
} }
return nil return nil
case ".hex": case ".hex":
err := flashHexUsingMSD(config.Target.FlashVolume, result.Binary, config.Options) err := flashHexUsingMSD(config.Target.FlashVolume, result.Binary)
if err != nil { if err != nil {
return &commandError{"failed to flash", result.Binary, err} return &commandError{"failed to flash", result.Binary, err}
} }
@@ -318,7 +312,7 @@ func Flash(pkgName, port string, options *compileopts.Options) error {
return err return err
} }
args = append(args, "-c", "program "+filepath.ToSlash(result.Binary)+" reset exit") args = append(args, "-c", "program "+filepath.ToSlash(result.Binary)+" reset exit")
cmd := executeCommand(config.Options, "openocd", args...) cmd := exec.Command("openocd", args...)
cmd.Stdout = os.Stdout cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr cmd.Stderr = os.Stderr
err = cmd.Run() err = cmd.Run()
@@ -344,9 +338,8 @@ func FlashGDB(pkgName string, ocdOutput bool, options *compileopts.Options) erro
if err != nil { if err != nil {
return err return err
} }
gdb, err := config.Target.LookupGDB() if config.Target.GDB == "" {
if err != nil { return errors.New("gdb not configured in the target specification")
return err
} }
return builder.Build(pkgName, "", config, func(result builder.BuildResult) error { return builder.Build(pkgName, "", config, func(result builder.BuildResult) error {
@@ -388,11 +381,11 @@ func FlashGDB(pkgName string, ocdOutput bool, options *compileopts.Options) erro
if err != nil { if err != nil {
return err return err
} }
daemon = executeCommand(config.Options, "openocd", args...) daemon = exec.Command("openocd", args...)
if ocdOutput { if ocdOutput {
// Make it clear which output is from the daemon. // Make it clear which output is from the daemon.
w := &ColorWriter{ w := &ColorWriter{
Out: colorable.NewColorableStderr(), Out: os.Stderr,
Prefix: "openocd: ", Prefix: "openocd: ",
Color: TermColorYellow, Color: TermColorYellow,
} }
@@ -403,11 +396,11 @@ func FlashGDB(pkgName string, ocdOutput bool, options *compileopts.Options) erro
gdbCommands = append(gdbCommands, "target remote :2331", "load", "monitor reset halt") gdbCommands = append(gdbCommands, "target remote :2331", "load", "monitor reset halt")
// We need a separate debugging daemon for on-chip debugging. // We need a separate debugging daemon for on-chip debugging.
daemon = executeCommand(config.Options, "JLinkGDBServer", "-device", config.Target.JLinkDevice) daemon = exec.Command("JLinkGDBServer", "-device", config.Target.JLinkDevice)
if ocdOutput { if ocdOutput {
// Make it clear which output is from the daemon. // Make it clear which output is from the daemon.
w := &ColorWriter{ w := &ColorWriter{
Out: colorable.NewColorableStderr(), Out: os.Stderr,
Prefix: "jlink: ", Prefix: "jlink: ",
Color: TermColorYellow, Color: TermColorYellow,
} }
@@ -419,7 +412,7 @@ func FlashGDB(pkgName string, ocdOutput bool, options *compileopts.Options) erro
// Run in an emulator. // Run in an emulator.
args := append(config.Target.Emulator[1:], result.Binary, "-s", "-S") args := append(config.Target.Emulator[1:], result.Binary, "-s", "-S")
daemon = executeCommand(config.Options, config.Target.Emulator[0], args...) daemon = exec.Command(config.Target.Emulator[0], args...)
daemon.Stdout = os.Stdout daemon.Stdout = os.Stdout
daemon.Stderr = os.Stderr daemon.Stderr = os.Stderr
case "qemu-user": case "qemu-user":
@@ -427,7 +420,7 @@ func FlashGDB(pkgName string, ocdOutput bool, options *compileopts.Options) erro
// Run in an emulator. // Run in an emulator.
args := append(config.Target.Emulator[1:], "-g", "1234", result.Binary) args := append(config.Target.Emulator[1:], "-g", "1234", result.Binary)
daemon = executeCommand(config.Options, config.Target.Emulator[0], args...) daemon = exec.Command(config.Target.Emulator[0], args...)
daemon.Stdout = os.Stdout daemon.Stdout = os.Stdout
daemon.Stderr = os.Stderr daemon.Stderr = os.Stderr
case "mgba": case "mgba":
@@ -435,7 +428,7 @@ func FlashGDB(pkgName string, ocdOutput bool, options *compileopts.Options) erro
// Run in an emulator. // Run in an emulator.
args := append(config.Target.Emulator[1:], result.Binary, "-g") args := append(config.Target.Emulator[1:], result.Binary, "-g")
daemon = executeCommand(config.Options, config.Target.Emulator[0], args...) daemon = exec.Command(config.Target.Emulator[0], args...)
daemon.Stdout = os.Stdout daemon.Stdout = os.Stdout
daemon.Stderr = os.Stderr daemon.Stderr = os.Stderr
case "simavr": case "simavr":
@@ -443,7 +436,7 @@ func FlashGDB(pkgName string, ocdOutput bool, options *compileopts.Options) erro
// Run in an emulator. // Run in an emulator.
args := append(config.Target.Emulator[1:], "-g", result.Binary) args := append(config.Target.Emulator[1:], "-g", result.Binary)
daemon = executeCommand(config.Options, config.Target.Emulator[0], args...) daemon = exec.Command(config.Target.Emulator[0], args...)
daemon.Stdout = os.Stdout daemon.Stdout = os.Stdout
daemon.Stderr = os.Stderr daemon.Stderr = os.Stderr
case "msd": case "msd":
@@ -464,15 +457,8 @@ func FlashGDB(pkgName string, ocdOutput bool, options *compileopts.Options) erro
} }
defer func() { defer func() {
daemon.Process.Signal(os.Interrupt) daemon.Process.Signal(os.Interrupt)
var stopped uint32 // Maybe we should send a .Kill() after x seconds?
go func() {
time.Sleep(time.Millisecond * 100)
if atomic.LoadUint32(&stopped) == 0 {
daemon.Process.Kill()
}
}()
daemon.Wait() daemon.Wait()
atomic.StoreUint32(&stopped, 1)
}() }()
} }
@@ -491,7 +477,7 @@ func FlashGDB(pkgName string, ocdOutput bool, options *compileopts.Options) erro
for _, cmd := range gdbCommands { for _, cmd := range gdbCommands {
params = append(params, "-ex", cmd) params = append(params, "-ex", cmd)
} }
cmd := executeCommand(config.Options, gdb, params...) cmd := exec.Command(config.Target.GDB, params...)
cmd.Stdin = os.Stdin cmd.Stdin = os.Stdin
cmd.Stdout = os.Stdout cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr cmd.Stderr = os.Stderr
@@ -516,7 +502,7 @@ func Run(pkgName string, options *compileopts.Options) error {
return builder.Build(pkgName, ".elf", config, func(result builder.BuildResult) error { return builder.Build(pkgName, ".elf", config, func(result builder.BuildResult) error {
if len(config.Target.Emulator) == 0 { if len(config.Target.Emulator) == 0 {
// Run directly. // Run directly.
cmd := executeCommand(config.Options, result.Binary) cmd := exec.Command(result.Binary)
cmd.Stdout = os.Stdout cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr cmd.Stderr = os.Stderr
err := cmd.Run() err := cmd.Run()
@@ -531,7 +517,7 @@ func Run(pkgName string, options *compileopts.Options) error {
} else { } else {
// Run in an emulator. // Run in an emulator.
args := append(config.Target.Emulator[1:], result.Binary) args := append(config.Target.Emulator[1:], result.Binary)
cmd := executeCommand(config.Options, config.Target.Emulator[0], args...) cmd := exec.Command(config.Target.Emulator[0], args...)
cmd.Stdout = os.Stdout cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr cmd.Stderr = os.Stderr
err := cmd.Run() err := cmd.Run()
@@ -574,7 +560,7 @@ func touchSerialPortAt1200bps(port string) (err error) {
const maxMSDRetries = 10 const maxMSDRetries = 10
func flashUF2UsingMSD(volume, tmppath string, options *compileopts.Options) error { func flashUF2UsingMSD(volume, tmppath string) error {
// find standard UF2 info path // find standard UF2 info path
var infoPath string var infoPath string
switch runtime.GOOS { switch runtime.GOOS {
@@ -583,7 +569,7 @@ func flashUF2UsingMSD(volume, tmppath string, options *compileopts.Options) erro
case "darwin": case "darwin":
infoPath = "/Volumes/" + volume + "/INFO_UF2.TXT" infoPath = "/Volumes/" + volume + "/INFO_UF2.TXT"
case "windows": case "windows":
path, err := windowsFindUSBDrive(volume, options) path, err := windowsFindUSBDrive(volume)
if err != nil { if err != nil {
return err return err
} }
@@ -598,7 +584,7 @@ func flashUF2UsingMSD(volume, tmppath string, options *compileopts.Options) erro
return moveFile(tmppath, filepath.Dir(d)+"/flash.uf2") return moveFile(tmppath, filepath.Dir(d)+"/flash.uf2")
} }
func flashHexUsingMSD(volume, tmppath string, options *compileopts.Options) error { func flashHexUsingMSD(volume, tmppath string) error {
// find expected volume path // find expected volume path
var destPath string var destPath string
switch runtime.GOOS { switch runtime.GOOS {
@@ -607,7 +593,7 @@ func flashHexUsingMSD(volume, tmppath string, options *compileopts.Options) erro
case "darwin": case "darwin":
destPath = "/Volumes/" + volume destPath = "/Volumes/" + volume
case "windows": case "windows":
path, err := windowsFindUSBDrive(volume, options) path, err := windowsFindUSBDrive(volume)
if err != nil { if err != nil {
return err return err
} }
@@ -641,8 +627,8 @@ func locateDevice(volume, path string) (string, error) {
return d[0], nil return d[0], nil
} }
func windowsFindUSBDrive(volume string, options *compileopts.Options) (string, error) { func windowsFindUSBDrive(volume string) (string, error) {
cmd := executeCommand(options, "wmic", cmd := exec.Command("wmic",
"PATH", "Win32_LogicalDisk", "WHERE", "VolumeName = '"+volume+"'", "PATH", "Win32_LogicalDisk", "WHERE", "VolumeName = '"+volume+"'",
"get", "DeviceID,VolumeName,FileSystem,DriveType") "get", "DeviceID,VolumeName,FileSystem,DriveType")
@@ -664,67 +650,66 @@ func windowsFindUSBDrive(volume string, options *compileopts.Options) (string, e
return "", errors.New("unable to locate a USB device to be flashed") return "", errors.New("unable to locate a USB device to be flashed")
} }
// getDefaultPort returns the default serial port depending on the operating system. // parseSize converts a human-readable size (with k/m/g suffix) into a plain
func getDefaultPort(portCandidates []string) (port string, err error) { // number.
if len(portCandidates) == 1 { func parseSize(s string) (int64, error) {
return portCandidates[0], nil s = strings.ToLower(strings.TrimSpace(s))
if len(s) == 0 {
return 0, errors.New("no size provided")
} }
multiply := int64(1)
switch s[len(s)-1] {
case 'k':
multiply = 1 << 10
case 'm':
multiply = 1 << 20
case 'g':
multiply = 1 << 30
}
if multiply != 1 {
s = s[:len(s)-1]
}
n, err := strconv.ParseInt(s, 0, 64)
n *= multiply
return n, err
}
var ports []string // getDefaultPort returns the default serial port depending on the operating system.
func getDefaultPort() (port string, err error) {
var portPath string
switch runtime.GOOS { switch runtime.GOOS {
case "darwin":
portPath = "/dev/cu.usb*"
case "linux":
portPath = "/dev/ttyACM*"
case "freebsd": case "freebsd":
ports, err = filepath.Glob("/dev/cuaU*") portPath = "/dev/cuaU*"
case "darwin", "linux", "windows": case "windows":
var portsList []*enumerator.PortDetails ports, err := serial.GetPortsList()
portsList, err = enumerator.GetDetailedPortsList()
if err != nil { if err != nil {
return "", err return "", err
} }
for _, p := range portsList { if len(ports) == 0 {
ports = append(ports, p.Name) return "", errors.New("no serial ports available")
} else if len(ports) > 1 {
return "", errors.New("multiple serial ports available - use -port flag")
} }
if ports == nil || len(ports) == 0 { return ports[0], nil
// fallback
switch runtime.GOOS {
case "darwin":
ports, err = filepath.Glob("/dev/cu.usb*")
case "linux":
ports, err = filepath.Glob("/dev/ttyACM*")
case "windows":
ports, err = serial.GetPortsList()
}
}
default: default:
return "", errors.New("unable to search for a default USB device to be flashed on this OS") return "", errors.New("unable to search for a default USB device to be flashed on this OS")
} }
d, err := filepath.Glob(portPath)
if err != nil { if err != nil {
return "", err return "", err
} else if ports == nil { }
if d == nil {
return "", errors.New("unable to locate a serial port") return "", errors.New("unable to locate a serial port")
} else if len(ports) == 0 {
return "", errors.New("no serial ports available")
} }
if len(portCandidates) == 0 { return d[0], nil
if len(ports) == 1 {
return ports[0], nil
} else {
return "", errors.New("multiple serial ports available - use -port flag, available ports are " + strings.Join(ports, ", "))
}
}
for _, ps := range portCandidates {
for _, p := range ports {
if p == ps {
return p, nil
}
}
}
return "", errors.New("port you specified '" + strings.Join(portCandidates, ",") + "' does not exist, available ports are " + strings.Join(ports, ", "))
} }
func usage() { func usage() {
@@ -799,15 +784,6 @@ func printCompilerError(logln func(...interface{}), err error) {
logln() logln()
} }
} }
case transform.CoroutinesError:
logln(err.Pos.String() + ": " + err.Msg)
logln("\ntraceback:")
for _, line := range err.Traceback {
logln(line.Name)
if line.Position.IsValid() {
logln("\t" + line.Position.String())
}
}
case loader.Errors: case loader.Errors:
logln("#", err.Pkg.ImportPath) logln("#", err.Pkg.ImportPath)
for _, err := range err.Errs { for _, err := range err.Errs {
@@ -837,52 +813,6 @@ func handleCompilerError(err error) {
} }
} }
// This is a special type for the -X flag to parse the pkgpath.Var=stringVal
// format. It has to be a special type to allow multiple variables to be defined
// this way.
type globalValuesFlag map[string]map[string]string
func (m globalValuesFlag) String() string {
return "pkgpath.Var=value"
}
func (m globalValuesFlag) Set(value string) error {
equalsIndex := strings.IndexByte(value, '=')
if equalsIndex < 0 {
return errors.New("expected format pkgpath.Var=value")
}
pathAndName := value[:equalsIndex]
pointIndex := strings.LastIndexByte(pathAndName, '.')
if pointIndex < 0 {
return errors.New("expected format pkgpath.Var=value")
}
path := pathAndName[:pointIndex]
name := pathAndName[pointIndex+1:]
stringValue := value[equalsIndex+1:]
if m[path] == nil {
m[path] = make(map[string]string)
}
m[path][name] = stringValue
return nil
}
// parseGoLinkFlag parses the -ldflags parameter. Its primary purpose right now
// is the -X flag, for setting the value of global string variables.
func parseGoLinkFlag(flagsString string) (map[string]map[string]string, error) {
set := flag.NewFlagSet("link", flag.ExitOnError)
globalVarValues := make(globalValuesFlag)
set.Var(globalVarValues, "X", "Set the value of the string variable to the given value.")
flags, err := shlex.Split(flagsString)
if err != nil {
return nil, err
}
err = set.Parse(flags)
if err != nil {
return nil, err
}
return map[string]map[string]string(globalVarValues), nil
}
func main() { func main() {
if len(os.Args) < 2 { if len(os.Args) < 2 {
fmt.Fprintln(os.Stderr, "No command-line arguments supplied.") fmt.Fprintln(os.Stderr, "No command-line arguments supplied.")
@@ -902,14 +832,14 @@ func main() {
target := flag.String("target", "", "LLVM target | .json file with TargetSpec") target := flag.String("target", "", "LLVM target | .json file with TargetSpec")
printSize := flag.String("size", "", "print sizes (none, short, full)") printSize := flag.String("size", "", "print sizes (none, short, full)")
printStacks := flag.Bool("print-stacks", false, "print stack sizes of goroutines") printStacks := flag.Bool("print-stacks", false, "print stack sizes of goroutines")
printAllocsString := flag.String("print-allocs", "", "regular expression of functions for which heap allocations should be printed")
printCommands := flag.Bool("x", false, "Print commands")
nodebug := flag.Bool("no-debug", false, "disable DWARF debug symbol generation") nodebug := flag.Bool("no-debug", false, "disable DWARF debug symbol generation")
ocdOutput := flag.Bool("ocd-output", false, "print OCD daemon output during debug") ocdOutput := flag.Bool("ocd-output", false, "print OCD daemon output during debug")
port := flag.String("port", "", "flash port (can specify multiple candidates separated by commas)") port := flag.String("port", "", "flash port")
programmer := flag.String("programmer", "", "which hardware programmer to use") programmer := flag.String("programmer", "", "which hardware programmer to use")
ldflags := flag.String("ldflags", "", "Go link tool compatible ldflags") cFlags := flag.String("cflags", "", "additional cflags for compiler")
ldFlags := flag.String("ldflags", "", "additional ldflags for linker")
wasmAbi := flag.String("wasm-abi", "", "WebAssembly ABI conventions: js (no i64 params) or generic") wasmAbi := flag.String("wasm-abi", "", "WebAssembly ABI conventions: js (no i64 params) or generic")
heapSize := flag.String("heap-size", "1M", "default heap size in bytes (only supported by WebAssembly)")
var flagJSON, flagDeps *bool var flagJSON, flagDeps *bool
if command == "help" || command == "list" { if command == "help" || command == "list" {
@@ -938,19 +868,6 @@ func main() {
} }
flag.CommandLine.Parse(os.Args[2:]) flag.CommandLine.Parse(os.Args[2:])
globalVarValues, err := parseGoLinkFlag(*ldflags)
if err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
var printAllocs *regexp.Regexp
if *printAllocsString != "" {
printAllocs, err = regexp.Compile(*printAllocsString)
if err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
}
options := &compileopts.Options{ options := &compileopts.Options{
Target: *target, Target: *target,
Opt: *opt, Opt: *opt,
@@ -963,14 +880,26 @@ func main() {
Debug: !*nodebug, Debug: !*nodebug,
PrintSizes: *printSize, PrintSizes: *printSize,
PrintStacks: *printStacks, PrintStacks: *printStacks,
PrintAllocs: printAllocs,
PrintCommands: *printCommands,
Tags: *tags, Tags: *tags,
GlobalValues: globalVarValues,
WasmAbi: *wasmAbi, WasmAbi: *wasmAbi,
Programmer: *programmer, Programmer: *programmer,
} }
if *cFlags != "" {
options.CFlags = strings.Split(*cFlags, " ")
}
if *ldFlags != "" {
options.LDFlags = strings.Split(*ldFlags, " ")
}
var err error
if options.HeapSize, err = parseSize(*heapSize); err != nil {
fmt.Fprintln(os.Stderr, "Could not read heap size:", *heapSize)
usage()
os.Exit(1)
}
os.Setenv("CC", "clang -target="+*target) os.Setenv("CC", "clang -target="+*target)
err = options.Verify() err = options.Verify()
@@ -1026,17 +955,9 @@ func main() {
fmt.Fprintf(os.Stderr, "Unknown library: %s\n", name) fmt.Fprintf(os.Stderr, "Unknown library: %s\n", name)
os.Exit(1) os.Exit(1)
} }
tmpdir, err := ioutil.TempDir("", "tinygo*") path, err := lib.Load(*target)
if err != nil {
handleCompilerError(err)
}
defer os.RemoveAll(tmpdir)
path, err := lib.Load(*target, tmpdir)
handleCompilerError(err) handleCompilerError(err)
err = copyFile(path, outpath) copyFile(path, outpath)
if err != nil {
handleCompilerError(err)
}
case "flash", "gdb": case "flash", "gdb":
pkgName := filepath.ToSlash(flag.Arg(0)) pkgName := filepath.ToSlash(flag.Arg(0))
if command == "flash" { if command == "flash" {
@@ -1155,7 +1076,10 @@ func main() {
err = cmd.Run() err = cmd.Run()
if err != nil { if err != nil {
if exitErr, ok := err.(*exec.ExitError); ok { if exitErr, ok := err.(*exec.ExitError); ok {
os.Exit(exitErr.ExitCode()) if status, ok := exitErr.Sys().(syscall.WaitStatus); ok {
os.Exit(status.ExitStatus())
}
os.Exit(1)
} }
fmt.Fprintln(os.Stderr, "failed to run `go list`:", err) fmt.Fprintln(os.Stderr, "failed to run `go list`:", err)
os.Exit(1) os.Exit(1)
+46 -120
View File
@@ -13,6 +13,7 @@ import (
"os/exec" "os/exec"
"path/filepath" "path/filepath"
"runtime" "runtime"
"sort"
"strings" "strings"
"sync" "sync"
"testing" "testing"
@@ -28,43 +29,35 @@ const TESTDATA = "testdata"
var testTarget = flag.String("target", "", "override test target") var testTarget = flag.String("target", "", "override test target")
func TestCompiler(t *testing.T) { func TestCompiler(t *testing.T) {
tests := []string{ matches, err := filepath.Glob(filepath.Join(TESTDATA, "*.go"))
"alias.go", if err != nil {
"atomic.go", t.Fatal("could not read test files:", err)
"binop.go",
"calls.go",
"cgo/",
"channel.go",
"coroutines.go",
"float.go",
"gc.go",
"init.go",
"init_multi.go",
"interface.go",
"json.go",
"map.go",
"math.go",
"print.go",
"reflect.go",
"slice.go",
"sort.go",
"stdlib.go",
"string.go",
"structs.go",
"zeroalloc.go",
} }
dirMatches, err := filepath.Glob(filepath.Join(TESTDATA, "*", "main.go"))
if err != nil {
t.Fatal("could not read test packages:", err)
}
if len(matches) == 0 || len(dirMatches) == 0 {
t.Fatal("no test files found")
}
for _, m := range dirMatches {
matches = append(matches, filepath.Dir(m)+string(filepath.Separator))
}
sort.Strings(matches)
if *testTarget != "" { if *testTarget != "" {
// This makes it possible to run one specific test (instead of all), // This makes it possible to run one specific test (instead of all),
// which is especially useful to quickly check whether some changes // which is especially useful to quickly check whether some changes
// affect a particular target architecture. // affect a particular target architecture.
runPlatTests(*testTarget, tests, t) runPlatTests(*testTarget, matches, t)
return return
} }
if runtime.GOOS != "windows" { if runtime.GOOS != "windows" {
t.Run("Host", func(t *testing.T) { t.Run("Host", func(t *testing.T) {
runPlatTests("", tests, t) runPlatTests("", matches, t)
}) })
} }
@@ -73,26 +66,26 @@ func TestCompiler(t *testing.T) {
} }
t.Run("EmulatedCortexM3", func(t *testing.T) { t.Run("EmulatedCortexM3", func(t *testing.T) {
runPlatTests("cortex-m-qemu", tests, t) runPlatTests("cortex-m-qemu", matches, t)
}) })
if runtime.GOOS == "windows" || runtime.GOOS == "darwin" { if runtime.GOOS == "windows" || runtime.GOOS == "darwin" {
// Note: running only on Windows and macOS because Linux (as of 2020) // Note: running only on Windows and macOS because Linux (as of 2020)
// usually has an outdated QEMU version that doesn't support RISC-V yet. // usually has an outdated QEMU version that doesn't support RISC-V yet.
t.Run("EmulatedRISCV", func(t *testing.T) { t.Run("EmulatedRISCV", func(t *testing.T) {
runPlatTests("riscv-qemu", tests, t) runPlatTests("riscv-qemu", matches, t)
}) })
} }
if runtime.GOOS == "linux" { if runtime.GOOS == "linux" {
t.Run("X86Linux", func(t *testing.T) { t.Run("X86Linux", func(t *testing.T) {
runPlatTests("i386--linux-gnu", tests, t) runPlatTests("i386--linux-gnu", matches, t)
}) })
t.Run("ARMLinux", func(t *testing.T) { t.Run("ARMLinux", func(t *testing.T) {
runPlatTests("arm--linux-gnueabihf", tests, t) runPlatTests("arm--linux-gnueabihf", matches, t)
}) })
t.Run("ARM64Linux", func(t *testing.T) { t.Run("ARM64Linux", func(t *testing.T) {
runPlatTests("aarch64--linux-gnu", tests, t) runPlatTests("aarch64--linux-gnu", matches, t)
}) })
goVersion, err := goenv.GorootVersionString(goenv.Get("GOROOT")) goVersion, err := goenv.GorootVersionString(goenv.Get("GOROOT"))
if err != nil { if err != nil {
@@ -105,72 +98,25 @@ func TestCompiler(t *testing.T) {
// below that are also not supported but still seem to pass, so // below that are also not supported but still seem to pass, so
// include them in the tests for now. // include them in the tests for now.
t.Run("WebAssembly", func(t *testing.T) { t.Run("WebAssembly", func(t *testing.T) {
runPlatTests("wasm", tests, t) runPlatTests("wasm", matches, t)
}) })
} }
t.Run("WASI", func(t *testing.T) { t.Run("WASI", func(t *testing.T) {
runPlatTests("wasi", tests, t) runPlatTests("wasi", matches, t)
}) })
} }
// Test a few build options.
t.Run("build-options", func(t *testing.T) {
if runtime.GOOS == "windows" {
// These tests assume a host that is supported by TinyGo.
t.Skip("can't test build options on Windows")
}
t.Parallel()
// Test with few optimizations enabled (no inlining, etc).
t.Run("opt=1", func(t *testing.T) {
t.Parallel()
runTestWithConfig("stdlib.go", "", t, &compileopts.Options{
Opt: "1",
}, nil, nil)
})
// Test with only the bare minimum of optimizations enabled.
// TODO: fix this for stdlib.go, which currently fails.
t.Run("opt=0", func(t *testing.T) {
t.Parallel()
runTestWithConfig("print.go", "", t, &compileopts.Options{
Opt: "0",
}, nil, nil)
})
t.Run("ldflags", func(t *testing.T) {
t.Parallel()
runTestWithConfig("ldflags.go", "", t, &compileopts.Options{
Opt: "z",
GlobalValues: map[string]map[string]string{
"main": {
"someGlobal": "foobar",
},
},
}, nil, nil)
})
})
} }
func runPlatTests(target string, tests []string, t *testing.T) { func runPlatTests(target string, matches []string, t *testing.T) {
t.Parallel() t.Parallel()
for _, name := range tests { for _, path := range matches {
name := name // redefine to avoid race condition path := path // redefine to avoid race condition
t.Run(name, func(t *testing.T) {
t.Run(filepath.Base(path), func(t *testing.T) {
t.Parallel() t.Parallel()
runTest(name, target, t, nil, nil) runTest(path, target, t)
})
}
if target == "wasi" || target == "" {
t.Run("filesystem.go", func(t *testing.T) {
t.Parallel()
runTest("filesystem.go", target, t, nil, nil)
})
t.Run("env.go", func(t *testing.T) {
t.Parallel()
runTest("env.go", target, t, []string{"first", "second"}, []string{"ENV1=VALUE1", "ENV2=VALUE2"})
}) })
} }
} }
@@ -187,28 +133,10 @@ func runBuild(src, out string, opts *compileopts.Options) error {
return Build(src, out, opts) return Build(src, out, opts)
} }
func runTest(name, target string, t *testing.T, cmdArgs, environmentVars []string) { func runTest(path, target string, t *testing.T) {
options := &compileopts.Options{
Target: target,
Opt: "z",
PrintIR: false,
DumpSSA: false,
VerifyIR: true,
Debug: true,
PrintSizes: "",
WasmAbi: "",
}
runTestWithConfig(name, target, t, options, cmdArgs, environmentVars)
}
func runTestWithConfig(name, target string, t *testing.T, options *compileopts.Options, cmdArgs, environmentVars []string) {
// Get the expected output for this test.
// Note: not using filepath.Join as it strips the path separator at the end
// of the path.
path := TESTDATA + "/" + name
// Get the expected output for this test. // Get the expected output for this test.
txtpath := path[:len(path)-3] + ".txt" txtpath := path[:len(path)-3] + ".txt"
if path[len(path)-1] == '/' { if path[len(path)-1] == os.PathSeparator {
txtpath = path + "out.txt" txtpath = path + "out.txt"
} }
expected, err := ioutil.ReadFile(txtpath) expected, err := ioutil.ReadFile(txtpath)
@@ -229,8 +157,19 @@ func runTestWithConfig(name, target string, t *testing.T, options *compileopts.O
}() }()
// Build the test binary. // Build the test binary.
config := &compileopts.Options{
Target: target,
Opt: "z",
PrintIR: false,
DumpSSA: false,
VerifyIR: true,
Debug: true,
PrintSizes: "",
WasmAbi: "",
}
binary := filepath.Join(tmpdir, "test") binary := filepath.Join(tmpdir, "test")
err = runBuild("./"+path, binary, options) err = runBuild("./"+path, binary, config)
if err != nil { if err != nil {
printCompilerError(t.Log, err) printCompilerError(t.Log, err)
t.Fail() t.Fail()
@@ -243,8 +182,6 @@ func runTestWithConfig(name, target string, t *testing.T, options *compileopts.O
ranTooLong := false ranTooLong := false
if target == "" { if target == "" {
cmd = exec.Command(binary) cmd = exec.Command(binary)
cmd.Env = append(cmd.Env, environmentVars...)
cmd.Args = append(cmd.Args, cmdArgs...)
} else { } else {
spec, err := compileopts.LoadTarget(target) spec, err := compileopts.LoadTarget(target)
if err != nil { if err != nil {
@@ -256,17 +193,6 @@ func runTestWithConfig(name, target string, t *testing.T, options *compileopts.O
args := append(spec.Emulator[1:], binary) args := append(spec.Emulator[1:], binary)
cmd = exec.Command(spec.Emulator[0], args...) cmd = exec.Command(spec.Emulator[0], args...)
} }
if len(spec.Emulator) != 0 && spec.Emulator[0] == "wasmtime" {
// Allow reading from the current directory.
cmd.Args = append(cmd.Args, "--dir=.")
for _, v := range environmentVars {
cmd.Args = append(cmd.Args, "--env", v)
}
cmd.Args = append(cmd.Args, cmdArgs...)
} else {
cmd.Env = append(cmd.Env, environmentVars...)
}
} }
stdout := &bytes.Buffer{} stdout := &bytes.Buffer{}
cmd.Stdout = stdout cmd.Stdout = stdout
-71
View File
@@ -1,71 +0,0 @@
// Hand created file. DO NOT DELETE.
// atsamd51x bitfield definitions that are not auto-generated by gen-device-svd.go
// +build sam,atsame5x
// These are the supported pchctrl function numberings on the atsamd51x
// See http://ww1.microchip.com/downloads/en/DeviceDoc/SAM_D5xE5x_Family_Data_Sheet_DS60001507F.pdf
// table 14-9
package sam
const (
PCHCTRL_GCLK_OSCCTRL_DFLL48 = 0 // DFLL48 input clock source
PCHCTRL_GCLK_OSCCTRL_FDPLL0 = 1 // Reference clock for FDPLL0
PCHCTRL_GCLK_OSCCTRL_FDPLL1 = 2 // Reference clock for FDPLL1
PCHCTRL_GCLK_OSCCTRL_FDPLL0_32K = 3 // FDPLL0 = 3 // 32KHz clock for internal lock timer
PCHCTRL_GCLK_OSCCTRL_FDPLL1_32K = 3 // FDPLL1 = 3 // 32KHz clock for internal lock timer
PCHCTRL_GCLK_SDHC0_SLOW = 3 // SDHC0 = 3 // Slow
PCHCTRL_GCLK_SDHC1_SLOW = 3 // SDHC1 = 3 // Slow
PCHCTRL_GCLK_SERCOMX_SLOW = 3 // GCLK_SERCOM[0..7]_SLOW = 3
PCHCTRL_GCLK_EIC = 4
PCHCTRL_GCLK_FREQM_MSR = 5 // FREQM Measure
PCHCTRL_GCLK_FREQM_REF = 6 // FREQM Reference
PCHCTRL_GCLK_SERCOM0_CORE = 7 // SERCOM0 Core
PCHCTRL_GCLK_SERCOM1_CORE = 8 // SERCOM1 Core
PCHCTRL_GCLK_TC0 = 9
PCHCTRL_GCLK_TC1 = 9 // TC0, TC1
PCHCTRL_GCLK_USB = 10 // USB
PCHCTRL_GCLK_EVSYS0 = 11
PCHCTRL_GCLK_EVSYS1 = 12
PCHCTRL_GCLK_EVSYS2 = 13
PCHCTRL_GCLK_EVSYS3 = 14
PCHCTRL_GCLK_EVSYS4 = 15
PCHCTRL_GCLK_EVSYS5 = 16
PCHCTRL_GCLK_EVSYS6 = 17
PCHCTRL_GCLK_EVSYS7 = 18
PCHCTRL_GCLK_EVSYS8 = 19
PCHCTRL_GCLK_EVSYS9 = 20
PCHCTRL_GCLK_EVSYS10 = 21
PCHCTRL_GCLK_EVSYS11 = 22
PCHCTRL_GCLK_SERCOM2_CORE = 23 // SERCOM2 Core
PCHCTRL_GCLK_SERCOM3_CORE = 24 // SERCOM3 Core
PCHCTRL_GCLK_TCC0 = 25
PCHCTRL_GCLK_TCC1 = 25 // TCC0, TCC1
PCHCTRL_GCLK_TC2 = 26
PCHCTRL_GCLK_TC3 = 26 // TC2, TC3
PCHCTRL_GCLK_CAN0 = 27 // CAN0
PCHCTRL_GCLK_CAN1 = 28 // CAN1
PCHCTRL_GCLK_TCC2 = 29
PCHCTRL_GCLK_TCC3 = 29 // TCC2, TCC3
PCHCTRL_GCLK_TC4 = 30
PCHCTRL_GCLK_TC5 = 30 // TC4, TC5
PCHCTRL_GCLK_PDEC = 31 // PDEC
PCHCTRL_GCLK_AC = 32 // AC
PCHCTRL_GCLK_CCL = 33 // CCL
PCHCTRL_GCLK_SERCOM4_CORE = 34 // SERCOM4 Core
PCHCTRL_GCLK_SERCOM5_CORE = 35 // SERCOM5 Core
PCHCTRL_GCLK_SERCOM6_CORE = 36 // SERCOM6 Core
PCHCTRL_GCLK_SERCOM7_CORE = 37 // SERCOM7 Core
PCHCTRL_GCLK_TCC4 = 38 // TCC4
PCHCTRL_GCLK_TC6 = 39
PCHCTRL_GCLK_TC7 = 39 // TC6, TC7
PCHCTRL_GCLK_ADC0 = 40 // ADC0
PCHCTRL_GCLK_ADC1 = 41 // ADC1
PCHCTRL_GCLK_DAC = 42 // DAC
PCHCTRL_GCLK_I2S0 = 43
PCHCTRL_GCLK_I2S1 = 44
PCHCTRL_GCLK_SDHC0 = 45 // SDHC0
PCHCTRL_GCLK_SDHC1 = 46 // SDHC1
PCHCTRL_GCLK_CM4_TRACE = 47 // CM4 Trace
)
+57
View File
@@ -0,0 +1,57 @@
// Hand created file. DO NOT DELETE.
// STM32FXXX (except stm32f1xx) bitfield definitions that are not
// auto-generated by gen-device-svd.go
// These apply to the stm32 families that use the MODER, OTYPE amd PUPDR
// registers for managing GPIO functionality.
// Add in other families that use the same settings, e.g. stm32f0xx, etc
// +build stm32,!stm32f103xx
package stm32
// AltFunc represents the alternate function peripherals that can be mapped to
// the GPIO ports. Since these differ by what is supported on the stm32 family
// they are defined in the more specific files
type AltFunc uint8
// Family-wide common post-reset AltFunc. This represents
// normal GPIO operation of the pins
const AF0_SYSTEM AltFunc = 0
const (
// Register values for the chip
// GPIOx_MODER
GPIOModeInput = 0
GPIOModeOutputGeneral = 1
GPIOModeOutputAltFunc = 2
GPIOModeAnalog = 3
// GPIOx_OTYPER
GPIOOutputTypePushPull = 0
GPIOOutputTypeOpenDrain = 1
// GPIOx_OSPEEDR
GPIOSpeedLow = 0
GPIOSpeedMid = 1
GPIOSpeedHigh = 2 // Note: this is also low speed on stm32f0, see RM0091
GPIOSpeedVeryHigh = 3
// GPIOx_PUPDR
GPIOPUPDRFloating = 0
GPIOPUPDRPullUp = 1
GPIOPUPDRPullDown = 2
)
// SPI prescaler values fPCLK / X
const (
SPI_PCLK_2 = 0
SPI_PCLK_4 = 1
SPI_PCLK_8 = 2
SPI_PCLK_16 = 3
SPI_PCLK_32 = 4
SPI_PCLK_64 = 5
SPI_PCLK_128 = 6
SPI_PCLK_256 = 7
)
+88
View File
@@ -0,0 +1,88 @@
// Hand created file. DO NOT DELETE.
// STM32F103XX bitfield definitions that are not auto-generated by gen-device-svd.go
// +build stm32,stm32f103xx
package stm32
const (
// Flash Access Control Register flag values.
FLASH_ACR_LATENCY_0 = 0x00000001
FLASH_ACR_LATENCY_1 = 0x00000002
FLASH_ACR_LATENCY_2 = 0x00000004
// Reset and Clock Control Control Register flag values.
// System Clock source
RCC_CFGR_SW_HSI = 0
RCC_CFGR_SW_HSE = 1
RCC_CFGR_SW_PLL = 2
// Flags for when System Clock source is set.
RCC_CFGR_SWS_HSI = 0x00000000
RCC_CFGR_SWS_HSE = 0x00000004
RCC_CFGR_SWS_PLL = 0x00000008
// Sets PCLK1
RCC_CFGR_PPRE1_DIV_NONE = 0x00000000
RCC_CFGR_PPRE1_DIV_2 = 0x00000400
RCC_CFGR_PPRE1_DIV_4 = 0x00000500
RCC_CFGR_PPRE1_DIV_8 = 0x00000600
RCC_CFGR_PPRE1_DIV_16 = 0x00000700
// Sets PCLK2
RCC_CFGR_PPRE2_DIV_NONE = 0x00000000
RCC_CFGR_PPRE2_DIV_2 = 0x00002000
RCC_CFGR_PPRE2_DIV_4 = 0x00002800
RCC_CFGR_PPRE2_DIV_8 = 0x00003000
RCC_CFGR_PPRE2_DIV_16 = 0x00003800
// Sets PLL multiplier
RCC_CFGR_PLLMUL_2 = 0x00000000
RCC_CFGR_PLLMUL_3 = 0x00040000
RCC_CFGR_PLLMUL_4 = 0x00080000
RCC_CFGR_PLLMUL_5 = 0x000C0000
RCC_CFGR_PLLMUL_6 = 0x00100000
RCC_CFGR_PLLMUL_7 = 0x00140000
RCC_CFGR_PLLMUL_8 = 0x00180000
RCC_CFGR_PLLMUL_9 = 0x001C0000
RCC_CFGR_PLLMUL_10 = 0x00200000
RCC_CFGR_PLLMUL_11 = 0x00240000
RCC_CFGR_PLLMUL_12 = 0x00280000
RCC_CFGR_PLLMUL_13 = 0x002C0000
RCC_CFGR_PLLMUL_14 = 0x00300000
RCC_CFGR_PLLMUL_15 = 0x00340000
RCC_CFGR_PLLMUL_16 = 0x00380000
// RTC clock source
RCC_RTCCLKSource_LSE = 0x00000100
RCC_RTCCLKSource_LSI = 0x00000200
RCC_RTCCLKSource_HSE_Div128 = 0x00000300
// SPI settings
SPI_FirstBit_MSB = 0x0000
SPI_FirstBit_LSB = 0x0080
SPI_BaudRatePrescaler_2 = 0x0000
SPI_BaudRatePrescaler_4 = 0x0008
SPI_BaudRatePrescaler_8 = 0x0010
SPI_BaudRatePrescaler_16 = 0x0018
SPI_BaudRatePrescaler_32 = 0x0020
SPI_BaudRatePrescaler_64 = 0x0028
SPI_BaudRatePrescaler_128 = 0x0030
SPI_BaudRatePrescaler_256 = 0x0038
SPI_Direction_2Lines_FullDuplex = 0x0000
SPI_Direction_2Lines_RxOnly = 0x0400
SPI_Direction_1Line_Rx = 0x8000
SPI_Direction_1Line_Tx = 0xC000
SPI_Mode_Master = 0x0104
SPI_Mode_Slave = 0x0000
SPI_NSS_Soft = 0x0200
SPI_NSS_Hard = 0x0000
SPI_NSSInternalSoft_Set = 0x0100
SPI_NSSInternalSoft_Reset = 0xFEFF
)
@@ -0,0 +1,28 @@
// Hand created file. DO NOT DELETE.
// STM32FXXX (except stm32f1xx) bitfield definitions that are not
// auto-generated by gen-device-svd.go
// +build stm32f4
// Alternate function settings on the stm32f4 series
package stm32
const (
// Alternative peripheral pin functions
// AF0_SYSTEM is defined im the common bitfields package
AF1_TIM1_2 AltFunc = 1
AF2_TIM3_4_5 = 2
AF3_TIM8_9_10_11 = 3
AF4_I2C1_2_3 = 4
AF5_SPI1_SPI2 = 5
AF6_SPI3 = 6
AF7_USART1_2_3 = 7
AF8_USART4_5_6 = 8
AF9_CAN1_CAN2_TIM12_13_14 = 9
AF10_OTG_FS_OTG_HS = 10
AF11_ETH = 11
AF12_FSMC_SDIO_OTG_HS_1 = 12
AF13_DCMI = 13
AF14 = 14
AF15_EVENTOUT = 15
)
+1 -1
View File
@@ -15,7 +15,7 @@ func main() {
led.Configure(machine.PinConfig{Mode: machine.PinOutput}) led.Configure(machine.PinConfig{Mode: machine.PinOutput})
sensor := machine.ADC{machine.ADC2} sensor := machine.ADC{machine.ADC2}
sensor.Configure(machine.ADCConfig{}) sensor.Configure()
for { for {
val := sensor.Get() val := sensor.Get()
-12
View File
@@ -1,12 +0,0 @@
// +build arduino_mega1280
package main
import "machine"
var (
// Configuration on an Arduino Uno.
pwm = machine.Timer3
pinA = machine.PH3 // pin 6 on the Mega
pinB = machine.PH4 // pin 7 on the Mega
)
-12
View File
@@ -1,12 +0,0 @@
// +build arduino
package main
import "machine"
var (
// Configuration on an Arduino Uno.
pwm = machine.Timer2
pinA = machine.PB3 // pin 11 on the Uno
pinB = machine.PD3 // pin 3 on the Uno
)
-11
View File
@@ -1,11 +0,0 @@
// +build feather_m4
package main
import "machine"
var (
pwm = machine.TCC0
pinA = machine.D12
pinB = machine.D13
)
-11
View File
@@ -1,11 +0,0 @@
// +build itsybitsy_m0
package main
import "machine"
var (
pwm = machine.TCC0
pinA = machine.D3
pinB = machine.D4
)
-11
View File
@@ -1,11 +0,0 @@
// +build itsybitsy_m4
package main
import "machine"
var (
pwm = machine.TCC0
pinA = machine.D12
pinB = machine.D13
)
+54 -64
View File
@@ -1,74 +1,64 @@
package main package main
// This example demonstrates some features of the PWM support.
import ( import (
"machine" "machine"
"time" "time"
) )
const delayBetweenPeriods = time.Second * 5 // This example assumes that an RGB LED is connected to pins 3, 5 and 6 on an Arduino.
// Change the values below to use different pins.
const (
redPin = machine.D4
greenPin = machine.D5
bluePin = machine.D6
)
func main() { // cycleColor is just a placeholder until math/rand or some equivalent is working.
// Delay a bit on startup to easily catch the first messages. func cycleColor(color uint8) uint8 {
time.Sleep(time.Second * 2) if color < 10 {
return color + 1
// Configure the PWM with the given period. } else if color < 200 {
err := pwm.Configure(machine.PWMConfig{ return color + 10
Period: 16384e3, // 16.384ms } else {
}) return 0
if err != nil { }
println("failed to configure PWM") }
return
} func main() {
machine.InitPWM()
// The top value is the highest value that can be passed to PWMChannel.Set.
// It is usually an even number. red := machine.PWM{redPin}
println("top:", pwm.Top()) err := red.Configure()
checkError(err, "failed to configure red pin")
// Configure the two channels we'll use as outputs.
channelA, err := pwm.Channel(pinA) green := machine.PWM{greenPin}
if err != nil { err = green.Configure()
println("failed to configure channel A") checkError(err, "failed to configure green pin")
return
} blue := machine.PWM{bluePin}
channelB, err := pwm.Channel(pinB) err = blue.Configure()
if err != nil { checkError(err, "failed to configure blue pin")
println("failed to configure channel B")
return var rc uint8
} var gc uint8 = 20
var bc uint8 = 30
// Invert one of the channels to demonstrate output polarity.
pwm.SetInverting(channelB, true) for {
rc = cycleColor(rc)
// Test out various frequencies below, including some edge cases. gc = cycleColor(gc)
bc = cycleColor(bc)
println("running at 0% duty cycle")
pwm.Set(channelA, 0) red.Set(uint16(rc) << 8)
pwm.Set(channelB, 0) green.Set(uint16(gc) << 8)
time.Sleep(delayBetweenPeriods) blue.Set(uint16(bc) << 8)
println("running at 1") time.Sleep(time.Millisecond * 500)
pwm.Set(channelA, 1) }
pwm.Set(channelB, 1) }
time.Sleep(delayBetweenPeriods)
func checkError(err error, msg string) {
println("running at 25% duty cycle") if err != nil {
pwm.Set(channelA, pwm.Top()/4) print(msg, ": ", err.Error())
pwm.Set(channelB, pwm.Top()/4) println()
time.Sleep(delayBetweenPeriods)
println("running at top-1")
pwm.Set(channelA, pwm.Top()-1)
pwm.Set(channelB, pwm.Top()-1)
time.Sleep(delayBetweenPeriods)
println("running at 100% duty cycle")
pwm.Set(channelA, pwm.Top())
pwm.Set(channelB, pwm.Top())
time.Sleep(delayBetweenPeriods)
for {
time.Sleep(time.Second)
} }
} }
+15 -2
View File
@@ -11,7 +11,7 @@ import (
type rawState uint8 type rawState uint8
//export llvm.coro.resume //export llvm.coro.resume
func coroResume(*rawState) func (s *rawState) resume()
type state struct{ *rawState } type state struct{ *rawState }
@@ -20,7 +20,7 @@ func noopState() *rawState
// Resume the task until it pauses or completes. // Resume the task until it pauses or completes.
func (t *Task) Resume() { func (t *Task) Resume() {
coroResume(t.state.rawState) t.state.resume()
} }
// setState is used by the compiler to set the state of the function at the beginning of a function call. // setState is used by the compiler to set the state of the function at the beginning of a function call.
@@ -77,9 +77,22 @@ func Current() *Task
// This is implemented inside the compiler. // This is implemented inside the compiler.
func Pause() func Pause()
type taskHolder interface {
setState(*rawState) *rawState
returnTo(*rawState)
returnCurrent()
setReturnPtr(unsafe.Pointer)
getReturnPtr() unsafe.Pointer
}
// If there are no direct references to the task methods, they will not be discovered by the compiler, and this will trigger a compiler error.
// Instantiating this interface forces discovery of these methods.
var _ = taskHolder((*Task)(nil))
func fake() { func fake() {
// Hack to ensure intrinsics are discovered. // Hack to ensure intrinsics are discovered.
Current() Current()
go func() {}()
Pause() Pause()
} }

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