Compare commits

..

1 Commits

Author SHA1 Message Date
sago35 13c0714fda test: remove thread-safe wrapper 2021-04-06 21:46:13 +09:00
563 changed files with 4587 additions and 21946 deletions
+44 -49
View File
@@ -68,17 +68,14 @@ commands:
steps: steps:
- restore_cache: - restore_cache:
keys: keys:
- llvm-source-11-v2 - llvm-source-11-v1
- run: - run:
name: "Fetch LLVM source" name: "Fetch LLVM source"
command: make llvm-source command: make llvm-source
- save_cache: - save_cache:
key: llvm-source-11-v2 key: llvm-source-11-v1
paths: paths:
- llvm-project/clang/lib/Headers - llvm-project
- llvm-project/clang/include
- llvm-project/lld/include
- llvm-project/llvm/include
build-wasi-libc: build-wasi-libc:
steps: steps:
- restore_cache: - restore_cache:
@@ -157,15 +154,12 @@ commands:
- llvm-source-linux - llvm-source-linux
- restore_cache: - restore_cache:
keys: keys:
- llvm-build-11-linux-v4-assert - llvm-build-11-linux-v2-assert
- run: - run:
name: "Build LLVM" name: "Build LLVM"
command: | command: |
if [ ! -f llvm-build/lib/liblldELF.a ] if [ ! -f llvm-build/lib/liblldELF.a ]
then then
# fetch LLVM source
rm -rf llvm-project
make llvm-source
# install dependencies # install dependencies
sudo apt-get install cmake ninja-build sudo apt-get install cmake ninja-build
# hack ninja to use less jobs # hack ninja to use less jobs
@@ -173,22 +167,20 @@ commands:
chmod +x /go/bin/ninja chmod +x /go/bin/ninja
# build! # build!
make ASSERT=1 llvm-build make ASSERT=1 llvm-build
find llvm-build -name CMakeFiles -prune -exec rm -r '{}' \;
fi fi
- save_cache: - save_cache:
key: llvm-build-11-linux-v4-assert key: llvm-build-11-linux-v2-assert
paths: paths:
llvm-build llvm-build
- run: make ASSERT=1 - run: |
# Note: -p=2 limits parallelism to two jobs at a time, which is
# necessary to keep memory consumption down and avoid OOM (for a
# 2CPU/4GB executor).
GOFLAGS="-p=2" make ASSERT=1
- build-wasi-libc - build-wasi-libc
- run: - run:
name: "Test TinyGo" name: "Test TinyGo"
command: make ASSERT=1 test command: make ASSERT=1 test
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:
@@ -225,15 +217,12 @@ commands:
- llvm-source-linux - llvm-source-linux
- restore_cache: - restore_cache:
keys: keys:
- llvm-build-11-linux-v4-noassert - llvm-build-11-linux-v2-noassert
- run: - run:
name: "Build LLVM" name: "Build LLVM"
command: | command: |
if [ ! -f llvm-build/lib/liblldELF.a ] if [ ! -f llvm-build/lib/liblldELF.a ]
then then
# fetch LLVM source
rm -rf llvm-project
make llvm-source
# install dependencies # install dependencies
sudo apt-get install cmake ninja-build sudo apt-get install cmake ninja-build
# hack ninja to use less jobs # hack ninja to use less jobs
@@ -241,10 +230,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-v4-noassert key: llvm-build-11-linux-v2-noassert
paths: paths:
llvm-build llvm-build
- build-wasi-libc - build-wasi-libc
@@ -286,58 +274,51 @@ commands:
- run: - run:
name: "Install dependencies" name: "Install dependencies"
command: | command: |
curl https://dl.google.com/go/go1.17.darwin-amd64.tar.gz -o go1.17.darwin-amd64.tar.gz curl https://dl.google.com/go/go1.16.darwin-amd64.tar.gz -o go1.16.darwin-amd64.tar.gz
sudo tar -C /usr/local -xzf go1.17.darwin-amd64.tar.gz sudo tar -C /usr/local -xzf go1.16.darwin-amd64.tar.gz
ln -s /usr/local/go/bin/go /usr/local/bin/go ln -s /usr/local/go/bin/go /usr/local/bin/go
HOMEBREW_NO_AUTO_UPDATE=1 brew install qemu HOMEBREW_NO_AUTO_UPDATE=1 brew install qemu
- install-xtensa-toolchain: - install-xtensa-toolchain:
variant: "macos" variant: "macos"
- restore_cache: - restore_cache:
keys: keys:
- go-cache-macos-v3-{{ checksum "go.mod" }}-{{ .Environment.CIRCLE_PREVIOUS_BUILD_NUM }} - go-cache-macos-v2-{{ checksum "go.mod" }}-{{ .Environment.CIRCLE_PREVIOUS_BUILD_NUM }}
- go-cache-macos-v3-{{ checksum "go.mod" }} - go-cache-macos-v2-{{ checksum "go.mod" }}
- restore_cache: - restore_cache:
keys: keys:
- llvm-source-11-macos-v3 - llvm-source-11-macos-v1
- run: - run:
name: "Fetch LLVM source" name: "Fetch LLVM source"
command: make llvm-source command: make llvm-source
- save_cache: - save_cache:
key: llvm-source-11-macos-v3 key: llvm-source-11-macos-v1
paths: paths:
- llvm-project/clang/lib/Headers - llvm-project
- llvm-project/clang/include
- llvm-project/lld/include
- llvm-project/llvm/include
- restore_cache: - restore_cache:
keys: keys:
- llvm-build-11-macos-v5 - llvm-build-11-macos-v2
- run: - run:
name: "Build LLVM" name: "Build LLVM"
command: | command: |
if [ ! -f llvm-build/lib/liblldELF.a ] if [ ! -f llvm-build/lib/liblldELF.a ]
then then
# fetch LLVM source
rm -rf llvm-project
make llvm-source
# install dependencies # install dependencies
HOMEBREW_NO_AUTO_UPDATE=1 brew install cmake ninja HOMEBREW_NO_AUTO_UPDATE=1 brew install cmake ninja
# build! # build!
make llvm-build make llvm-build
find llvm-build -name CMakeFiles -prune -exec rm -r '{}' \;
fi fi
- save_cache: - save_cache:
key: llvm-build-11-macos-v5 key: llvm-build-11-macos-v2
paths: paths:
llvm-build llvm-build
- restore_cache: - restore_cache:
keys: keys:
- wasi-libc-sysroot-macos-v4 - wasi-libc-sysroot-macos-v3
- run: - run:
name: "Build wasi-libc" name: "Build wasi-libc"
command: make wasi-libc command: make wasi-libc
- save_cache: - save_cache:
key: wasi-libc-sysroot-macos-v4 key: wasi-libc-sysroot-macos-v3
paths: paths:
- lib/wasi-libc/sysroot - lib/wasi-libc/sysroot
- run: - run:
@@ -359,18 +340,30 @@ commands:
tinygo version tinygo version
- run: make smoketest AVR=0 - run: make smoketest AVR=0
- save_cache: - save_cache:
key: go-cache-macos-v3-{{ checksum "go.mod" }}-{{ .Environment.CIRCLE_BUILD_NUM }} key: go-cache-macos-v2-{{ checksum "go.mod" }}-{{ .Environment.CIRCLE_BUILD_NUM }}
paths: paths:
- ~/.cache/go-build - ~/.cache/go-build
- /go/pkg/mod - /go/pkg/mod
jobs: jobs:
test-llvm10-go115: test-llvm10-go113:
docker:
- image: circleci/golang:1.13-buster
steps:
- test-linux:
llvm: "10"
test-llvm10-go114:
docker:
- image: circleci/golang:1.14-buster
steps:
- test-linux:
llvm: "10"
test-llvm11-go115:
docker: docker:
- image: circleci/golang:1.15-buster - image: circleci/golang:1.15-buster
steps: steps:
- test-linux: - test-linux:
llvm: "10" llvm: "11"
test-llvm11-go116: test-llvm11-go116:
docker: docker:
- image: circleci/golang:1.16-buster - image: circleci/golang:1.16-buster
@@ -379,17 +372,17 @@ jobs:
llvm: "11" llvm: "11"
assert-test-linux: assert-test-linux:
docker: docker:
- image: circleci/golang:1.17-stretch - image: circleci/golang:1.14-stretch
steps: steps:
- assert-test-linux - assert-test-linux
build-linux: build-linux:
docker: docker:
- image: circleci/golang:1.17-stretch - image: circleci/golang:1.14-stretch
steps: steps:
- build-linux - build-linux
build-macos: build-macos:
macos: macos:
xcode: "11.1.0" # macOS 10.14 xcode: "10.1.0"
steps: steps:
- build-macos - build-macos
@@ -398,7 +391,9 @@ jobs:
workflows: workflows:
test-all: test-all:
jobs: jobs:
- test-llvm10-go115 - test-llvm10-go113
- test-llvm10-go114
- test-llvm11-go115
- test-llvm11-go116 - test-llvm11-go116
- build-linux - build-linux
- build-macos - build-macos
-3
View File
@@ -1,3 +0,0 @@
build/
llvm-*/
@@ -1,45 +0,0 @@
name: CI for tinygo-dev docker container
on:
push:
branches: [ dev, fix-docker-llvm-build ]
jobs:
push_to_registry:
name: Push Docker image to GHCR/Docker Hub
runs-on: ubuntu-latest
permissions:
packages: write
contents: read
steps:
- name: Check out the repo
uses: actions/checkout@v2
with:
submodules: recursive
- name: Docker meta
id: meta
uses: docker/metadata-action@v3
with:
images: |
tinygo/tinygo-dev
ghcr.io/${{ github.repository }}/tinygo-dev
tags: |
type=sha,format=long
type=raw,value=latest
- name: Log in to Docker Hub
uses: docker/login-action@v1
with:
username: ${{ secrets.DOCKER_HUB_USERNAME }}
password: ${{ secrets.DOCKER_HUB_ACCESS_TOKEN }}
- name: Log in to Github Container Registry
uses: docker/login-action@v1
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Build and push
uses: docker/build-push-action@v2
with:
context: .
push: true
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
-2
View File
@@ -16,8 +16,6 @@ src/device/stm32/*.go
src/device/stm32/*.s src/device/stm32/*.s
src/device/kendryte/*.go src/device/kendryte/*.go
src/device/kendryte/*.s src/device/kendryte/*.s
src/device/rp/*.go
src/device/rp/*.s
vendor vendor
llvm-build llvm-build
llvm-project llvm-project
+1 -1
View File
@@ -23,7 +23,7 @@ different guide:
LLVM, Clang and LLD are quite light on dependencies, requiring only standard LLVM, Clang and LLD are quite light on dependencies, requiring only standard
build tools to be built. Go is of course necessary to build TinyGo itself. build tools to be built. Go is of course necessary to build TinyGo itself.
* Go (1.15+) * Go (1.13+)
* Standard build tools (gcc/clang) * Standard build tools (gcc/clang)
* git * git
* CMake * CMake
-258
View File
@@ -1,261 +1,3 @@
0.20.0
---
* **command line**
- add support for Go 1.17
- improve Go version detection
- add support for the Black Magic Probe (BMP)
- add a flag for creating cpu profiles
* **compiler**
- `builder:` list libraries at the end of the linker command
- `builder:` strip debug information at link time instead of at compile time
- `builder:` add missing error check for `ioutil.TempFile()`
- `builder:` simplify running of jobs
- `compiler:` move LLVM math builtin support into the compiler
- `compiler:` move math aliases from the runtime to the compiler
- `compiler:` add aliases for many hashing packages
- `compiler:` add `*ssa.MakeSlice` bounds tests
- `compiler:` fix max possible slice
- `compiler:` add support for new language features of Go 1.17
- `compiler:` fix equally named structs in different scopes
- `compiler:` avoid zero-sized alloca in channel operations
- `interp:` don't ignore array indices for untyped objects
- `interp:` keep reverted package initializers in order
- `interp:` fix bug in compiler-time/run-time package initializers
- `loader:` fix panic in CGo files with syntax errors
- `transform:` improve GC stack slot pass to work around a bug
* **standard library**
- `crypto/rand`: switch to `arc4random_buf`
- `math:` fix `math.Max` and `math.Min`
- `math/big`: fix undefined symbols error
- `net:` add MAC address implementation
- `os:` implement `os.Executable`
- `os:` add `SEEK_SET`, `SEEK_CUR`, and `SEEK_END`
- `reflect:` add StructField.IsExported method
- `runtime:` reset heapptr to heapStart after preinit()
- `runtime:` add `subsections_via_symbols` to assembly files on darwin
- `testing:` add subset implementation of Benchmark
- `testing:` test testing package using `tinygo test`
- `testing:` add support for the `-test.v` flag
* **targets**
- `386:` bump minimum requirement to the Pentium 4
- `arm:` switch to Thumb instruction set on ARM
- `atsamd:` fix copy-paste error for atsamd21/51 calibTrim block
- `baremetal`,`wasm`: support command line params and environment variables
- `cortexm:` fix stack overflow because of unaligned stacks
- `esp32c3:` add support for the ESP32-C3 from Espressif
- `nrf52840:` fix ram size
- `nxpmk66f18:` fix a suspicious bitwise operation
- `rp2040:` add SPI support
- `rp2040:` add I2C support
- `rp2040:` add PWM implementation
- `rp2040:` add openocd configuration
- `stm32:` add support for PortMask* functions for WS2812 support
- `unix:` fix time base for time.Now()
- `unix:` check for mmap error and act accordingly
- `wasm:` override dlmalloc heap implementation from wasi-libc
- `wasm:` align heap to 16 bytes
- `wasm:` add support for the crypto/rand package
* **boards**
- add `DefaultUART` to adafruit boards
- `arduino-mkrwifi1010:` add board definition for Arduino MKR WiFi 1010
- `arduino-mkrwifi1010:` fix pin definition of `NINA_RESETN`
- `feather-nrf52:` fix pin definition of uart
- `feather-rp2040:` add pin name definition
- `gameboy-advance:` fix ROM header
- `mdbt50qrx-uf2:` add Raytac MDBT50Q-RX Dongle with TinyUF2
- `nano-rp2040:` define `NINA_SPI` and fix wifinina pins
- `teensy40:` enable hardware UART reconfiguration, fix receive watermark interrupt
0.19.0
---
* **command line**
- don't consider compile-only tests as failing
- add -test flag for `tinygo list`
- escape commands while printing them with the -x flag
- make flash-command portable and safer to use
- use `extended-remote` instead of `remote` in GDB
- detect specific serial port IDs based on USB vid/pid
- add a flag to the command line to select the serial implementation
* **compiler**
- `cgo`: improve constant parser
- `compiler`: support chained interrupt handlers
- `compiler`: add support for running a builtin in a goroutine
- `compiler`: do not emit nil checks for loading closure variables
- `compiler`: skip context parameter when starting regular goroutine
- `compiler`: refactor method names
- `compiler`: add function and global section pragmas
- `compiler`: implement `syscall.rawSyscallNoError` in inline assembly
- `interp`: ignore inline assembly in markExternal
- `interp`: fix a bug in pointer cast workaround
- `loader`: fix testing a main package
* **standard library**
- `crypto/rand`: replace this package with a TinyGo version
- `machine`: make USBCDC global a pointer
- `machine`: make UART objects pointer receivers
- `machine`: define Serial as the default output
- `net`: add initial support for net.IP
- `net`: add more net compatibility
- `os`: add stub for os.ReadDir
- `os`: add FileMode constants from Go 1.16
- `os`: add stubs required for net/http
- `os`: implement process related functions
- `reflect`: implement AppendSlice
- `reflect`: add stubs required for net/http
- `runtime`: make task.Data a 64-bit integer to avoid overflow
- `runtime`: expose memory stats
- `sync`: implement NewCond
- `syscall`: fix int type in libc version
* **targets**
- `cortexm`: do not disable interrupts on abort
- `cortexm`: bump default stack size to 2048 bytes
- `nrf`: avoid heap allocation in waitForEvent
- `nrf`: don't trigger a heap allocation in SPI.Transfer
- `nrf52840`: add support for flashing with the BOSSA tool
- `rp2040`: add support for GPIO input
- `rp2040`: add basic support for ADC
- `rp2040`: gpio and adc pin definitions
- `rp2040`: implement UART
- `rp2040`: patch elf to checksum 2nd stage boot
- `stm32`: add PWM for most chips
- `stm32`: add support for pin interrupts
- `stm32f103`: add support for PinInputPullup / PinInputPulldown
- `wasi`: remove wasm build tag
* **boards**
- `feather-rp2040`: add support for this board
- `feather-nrf52840-sense`: add board definition for this board
- `pca10059`: support flashing from Windows
- `nano-rp2040`: add this board
- `nano-33-ble`: add support for this board
- `pico`: add the Raspberry Pi Pico board with the new RP2040 chip
- `qtpy`: add pin for neopixels
- all: add definition for ws2812 for supported boards
0.18.0
---
* **command line**
- drop support for Go 1.11 and 1.12
- throw an error when no target is specified on Windows
- improve error messages in `getDefaultPort()`, support for multiple ports
- remove `-cflags` and `-ldflags` flags
- implement `-ldflags="-X ..."`
- add `-print-allocs` flag that lets you print all heap allocations
- openocd commands in tinygo command line
- add `-llvm-features` parameter
- match `go test` output
- discover USB ports only, this will ignore f.ex. bluetooth
- use physicmal path instead of cached GOROOT in function getGoroot
- add goroot for snap installs
* **compiler**
- `builder`: add support for `-opt=0`
- `builder`, `compiler`: compile and cache packages in parallel
- `builder`: run interp per package
- `builder`: cache C and assembly file outputs
- `builder`: add support for `-x` flag to print commands
- `builder`: add optsize attribute while building the package
- `builder`: run function passes per package
- `builder`: hard code Clang compiler
- `compiler`: do not use `llvm.GlobalContext()`
- `compiler`: remove SimpleDCE pass
- `compiler`: do not emit nil checks for `*ssa.Alloc` instructions
- `compiler`: merge `runtime.typecodeID` and runtime.typeInInterface
- `compiler`: do not check for impossible type asserts
- `compiler`: fix use of global context: `llvm.Int32Type()`
- `compiler`: add interface IR test
- `compiler`: fix lack of method name in interface matching
- `compiler`: fix "fragment covers entire variable" bug
- `compiler`: optimize string literals and globals
- `compiler`: decouple func lowering from interface type codes
- `compiler`: add function attributes to some runtime calls
- `compiler`: improve position information in error messages
- `cgo`: add support for CFLAGS in .c files
- `interp`: support GEP on fixed (MMIO) addresses
- `interp`: handle `(reflect.Type).Elem()`
- `interp`: add support for runtime.interfaceMethod
- `interp`: make toLLVMValue return an error instead of panicking
- `interp`: add support for switch statement
- `interp`: fix phi instruction
- `interp`: remove map support
- `interp`: support extractvalue/insertvalue with multiple operands
- `transform`: optimize string comparisons against ""
- `transform`: optimize `reflect.Type` `Implements()` method
- `transform`: fix bug in interface lowering when signatures are renamed
- `transform`: don't rely on struct name of `runtime.typecodeID`
- `transform`: use IPSCCP pass instead of the constant propagation pass
- `transform`: fix func lowering assertion failure
- `transform`: do not lower zero-sized alloc to alloca
- `transform`: split interface and reflect lowering
* **standard library**
- `runtime`: add dummy debug package
- `machine`: fix data shift/mask in newUSBSetup
- `machine`: make `machine.I2C0` and similar objects pointers
- `machine`: unify usbcdc code
- `machine`: refactor PWM support
- `machine`: avoid heap allocations in USB code
- `reflect`: let `reflect.Type` be of interface type
- `reflect`: implement a number of stub functions
- `reflect`: check for access in the `Interface` method call
- `reflect`: fix `AssignableTo` and `Implements` methods
- `reflect`: implement `Value.CanAddr`
- `reflect`: implement `Sizeof` and `Alignof` for func values
- `reflect`: implement `New` function
- `runtime`: implement command line arguments in hosted environments
- `runtime`: implement environment variables for Linux
- `runtime`: improve timers on nrf, and samd chips
* **targets**
- all: use -Qunused-arguments only for assembly files
- `atmega1280`: add PWM support
- `attiny`: remove dummy UART
- `atsamd21`: improve SPI
- `atsamd51`: fix PWM support in atsamd51p20
- `atsamd5x`: improve SPI
- `atsamd51`, `atsame5x`: unify samd51 and same5x
- `atsamd51`, `atsamd21`: fix `ADC.Get()` value at 8bit and 10bit
- `atsame5x`: add support for CAN
- `avr`: remove I2C stubs from attiny support
- `cortexm`: check for `arm-none-eabi-gdb` and `gdb-multiarch` commands
- `cortexm`: add `__isr_vector` symbol
- `cortexm`: disable FPU on Cortex-M4
- `cortexm`: clean up Cortex-M target files
- `fe310`: fix SPI read
- `gameboy-advance`: Fix RGBA color interpretation
- `nrf52833`: add PWM support
- `stm32l0`: use unified UART logic
- `stm32`: move f103 (bluepill) to common i2c code
- `stm32`: separate altfunc selection for UART Tx/Rx
- `stm32`: i2c implementation for F7, L5 and L4 MCUs
- `stm32`: make SPI CLK fast to fix data issue
- `stm32`: support SPI on L4 series
- `unix`: avoid possible heap allocation with `-opt=0`
- `unix`: use conservative GC by default
- `unix`: use the tasks scheduler instead of coroutines
- `wasi`: upgrade WASI version to wasi_snapshot_preview1
- `wasi`: darwin: support basic file io based on libc
- `wasm`: only export explicitly exported functions
- `wasm`: use WASI ABI for exit function
- `wasm`: scan globals conservatively
* **boards**
- `arduino-mega1280`: add support for the Arduino Mega 1280
- `arduino-nano-new`: Add Arduino Nano w/ New Bootloader target
- `atsame54-xpro`: add initial support this board
- `feather-m4-can`: add initial support for this board
- `grandcentral-m4`: add board support for Adafruit Grand Central M4 (SAMD51)
- `lgt92`: update to new UART structure
- `microbit`: remove LED constant
- `microbit-v2`: add support for S113 SoftDevice
- `nucleol432`: add support for this board
- `nucleo-l031k6`: add this board
- `pca10059`: initial support for this board
- `qtpy`: fix msd-volume-name
- `qtpy`: fix i2c setting
- `teensy40`: move txBuffer allocation to UART declaration
- `teensy40`: add UART0 as alias for UART1
0.17.0 0.17.0
--- ---
+3 -3
View File
@@ -1,8 +1,8 @@
# TinyGo base stage installs the most recent Go 1.17.x, LLVM 11 and the TinyGo compiler itself. # TinyGo base stage installs the most recent Go 1.15.x, LLVM 11 and the TinyGo compiler itself.
FROM golang:1.17 AS tinygo-base FROM golang:1.15 AS tinygo-base
RUN wget -O- https://apt.llvm.org/llvm-snapshot.gpg.key| apt-key add - && \ RUN wget -O- https://apt.llvm.org/llvm-snapshot.gpg.key| apt-key add - && \
echo "deb http://apt.llvm.org/bullseye/ llvm-toolchain-bullseye-11 main" >> /etc/apt/sources.list && \ echo "deb http://apt.llvm.org/buster/ llvm-toolchain-buster-11 main" >> /etc/apt/sources.list && \
apt-get update && \ apt-get update && \
apt-get install -y llvm-11-dev libclang-11-dev lld-11 git apt-get install -y llvm-11-dev libclang-11-dev lld-11 git
+28 -81
View File
@@ -36,7 +36,7 @@ else
LLVM_OPTION += '-DLLVM_ENABLE_ASSERTIONS=OFF' LLVM_OPTION += '-DLLVM_ENABLE_ASSERTIONS=OFF'
endif endif
.PHONY: all tinygo test $(LLVM_BUILDDIR) llvm-source clean fmt gen-device gen-device-nrf gen-device-nxp gen-device-avr gen-device-rp .PHONY: all tinygo test $(LLVM_BUILDDIR) llvm-source clean fmt gen-device gen-device-nrf gen-device-nxp gen-device-avr
LLVM_COMPONENTS = all-targets analysis asmparser asmprinter bitreader bitwriter codegen core coroutines coverage debuginfodwarf executionengine frontendopenmp instrumentation interpreter ipo irreader linker lto mc mcjit objcarcopts option profiledata scalaropts support target LLVM_COMPONENTS = all-targets analysis asmparser asmprinter bitreader bitwriter codegen core coroutines coverage debuginfodwarf executionengine frontendopenmp instrumentation interpreter ipo irreader linker lto mc mcjit objcarcopts option profiledata scalaropts support target
@@ -71,7 +71,7 @@ else
endif endif
# Libraries that should be linked in for the statically linked Clang. # Libraries that should be linked in for the statically linked Clang.
CLANG_LIB_NAMES = clangAnalysis clangAST clangASTMatchers clangBasic clangCodeGen clangCrossTU clangDriver clangDynamicASTMatchers clangEdit clangFormat clangFrontend clangFrontendTool clangHandleCXX clangHandleLLVM clangIndex clangLex clangParse clangRewrite clangRewriteFrontend clangSema clangSerialization clangTooling clangToolingASTDiff clangToolingCore clangToolingInclusions CLANG_LIB_NAMES = clangAnalysis clangARCMigrate clangAST clangASTMatchers clangBasic clangCodeGen clangCrossTU clangDriver clangDynamicASTMatchers clangEdit clangFormat clangFrontend clangFrontendTool clangHandleCXX clangHandleLLVM clangIndex clangLex clangParse clangRewrite clangRewriteFrontend clangSema clangSerialization clangStaticAnalyzerCheckers clangStaticAnalyzerCore clangStaticAnalyzerFrontend clangTooling clangToolingASTDiff clangToolingCore clangToolingInclusions
CLANG_LIBS = $(START_GROUP) $(addprefix -l,$(CLANG_LIB_NAMES)) $(END_GROUP) -lstdc++ CLANG_LIBS = $(START_GROUP) $(addprefix -l,$(CLANG_LIB_NAMES)) $(END_GROUP) -lstdc++
# Libraries that should be linked in for the statically linked LLD. # Libraries that should be linked in for the statically linked LLD.
@@ -100,14 +100,14 @@ endif
clean: clean:
@rm -rf build @rm -rf build
FMT_PATHS = ./*.go builder cgo compiler interp loader src/device/arm src/examples src/machine src/os src/reflect src/runtime src/sync src/syscall src/testing src/internal/reflectlite transform FMT_PATHS = ./*.go builder cgo compiler interp loader src/device/arm src/examples src/machine src/os src/reflect src/runtime src/sync src/syscall src/internal/reflectlite transform
fmt: fmt:
@gofmt -l -w $(FMT_PATHS) @gofmt -l -w $(FMT_PATHS)
fmt-check: fmt-check:
@unformatted=$$(gofmt -l $(FMT_PATHS)); [ -z "$$unformatted" ] && exit 0; echo "Unformatted:"; for fn in $$unformatted; do echo " $$fn"; done; exit 1 @unformatted=$$(gofmt -l $(FMT_PATHS)); [ -z "$$unformatted" ] && exit 0; echo "Unformatted:"; for fn in $$unformatted; do echo " $$fn"; done; exit 1
gen-device: gen-device-avr gen-device-esp gen-device-nrf gen-device-sam gen-device-sifive gen-device-kendryte gen-device-nxp gen-device-rp gen-device: gen-device-avr gen-device-esp gen-device-nrf gen-device-sam gen-device-sifive gen-device-kendryte gen-device-nxp
ifneq ($(STM32), 0) ifneq ($(STM32), 0)
gen-device: gen-device-stm32 gen-device: gen-device-stm32
endif endif
@@ -124,7 +124,6 @@ build/gen-device-svd: ./tools/gen-device-svd/*.go
gen-device-esp: build/gen-device-svd gen-device-esp: build/gen-device-svd
./build/gen-device-svd -source=https://github.com/posborne/cmsis-svd/tree/master/data/Espressif-Community -interrupts=software lib/cmsis-svd/data/Espressif-Community/ src/device/esp/ ./build/gen-device-svd -source=https://github.com/posborne/cmsis-svd/tree/master/data/Espressif-Community -interrupts=software lib/cmsis-svd/data/Espressif-Community/ src/device/esp/
./build/gen-device-svd -source=https://github.com/posborne/cmsis-svd/tree/master/data/Espressif -interrupts=software lib/cmsis-svd/data/Espressif/ src/device/esp/
GO111MODULE=off $(GO) fmt ./src/device/esp GO111MODULE=off $(GO) fmt ./src/device/esp
gen-device-nrf: build/gen-device-svd gen-device-nrf: build/gen-device-svd
@@ -151,19 +150,16 @@ gen-device-stm32: build/gen-device-svd
./build/gen-device-svd -source=https://github.com/tinygo-org/stm32-svd lib/stm32-svd/svd src/device/stm32/ ./build/gen-device-svd -source=https://github.com/tinygo-org/stm32-svd lib/stm32-svd/svd src/device/stm32/
GO111MODULE=off $(GO) fmt ./src/device/stm32 GO111MODULE=off $(GO) fmt ./src/device/stm32
gen-device-rp: build/gen-device-svd
./build/gen-device-svd -source=https://github.com/posborne/cmsis-svd/tree/master/data/RaspberryPi lib/cmsis-svd/data/RaspberryPi/ src/device/rp/
GO111MODULE=off $(GO) fmt ./src/device/rp
# Get LLVM sources. # Get LLVM sources.
$(LLVM_PROJECTDIR)/llvm: $(LLVM_PROJECTDIR)/README.md:
git clone -b xtensa_release_11.0.0 --depth=1 https://github.com/tinygo-org/llvm-project $(LLVM_PROJECTDIR) git clone -b xtensa_release_11.0.0 --depth=1 https://github.com/tinygo-org/llvm-project $(LLVM_PROJECTDIR)
llvm-source: $(LLVM_PROJECTDIR)/llvm llvm-source: $(LLVM_PROJECTDIR)/README.md
# Configure LLVM. # Configure LLVM.
TINYGO_SOURCE_DIR=$(shell pwd) TINYGO_SOURCE_DIR=$(shell pwd)
$(LLVM_BUILDDIR)/build.ninja: llvm-source $(LLVM_BUILDDIR)/build.ninja: llvm-source
mkdir -p $(LLVM_BUILDDIR); cd $(LLVM_BUILDDIR); cmake -G Ninja $(TINYGO_SOURCE_DIR)/$(LLVM_PROJECTDIR)/llvm "-DLLVM_TARGETS_TO_BUILD=X86;ARM;AArch64;RISCV;WebAssembly" "-DLLVM_EXPERIMENTAL_TARGETS_TO_BUILD=AVR;Xtensa" -DCMAKE_BUILD_TYPE=Release -DLIBCLANG_BUILD_STATIC=ON -DLLVM_ENABLE_TERMINFO=OFF -DLLVM_ENABLE_ZLIB=OFF -DLLVM_ENABLE_LIBEDIT=OFF -DLLVM_ENABLE_Z3_SOLVER=OFF -DLLVM_ENABLE_OCAMLDOC=OFF -DLLVM_ENABLE_LIBXML2=OFF -DLLVM_ENABLE_PROJECTS="clang;lld" -DLLVM_TOOL_CLANG_TOOLS_EXTRA_BUILD=OFF -DCLANG_ENABLE_STATIC_ANALYZER=OFF -DCLANG_ENABLE_ARCMT=OFF $(LLVM_OPTION) mkdir -p $(LLVM_BUILDDIR); cd $(LLVM_BUILDDIR); cmake -G Ninja $(TINYGO_SOURCE_DIR)/$(LLVM_PROJECTDIR)/llvm "-DLLVM_TARGETS_TO_BUILD=X86;ARM;AArch64;RISCV;WebAssembly" "-DLLVM_EXPERIMENTAL_TARGETS_TO_BUILD=AVR;Xtensa" -DCMAKE_BUILD_TYPE=Release -DLIBCLANG_BUILD_STATIC=ON -DLLVM_ENABLE_TERMINFO=OFF -DLLVM_ENABLE_ZLIB=OFF -DLLVM_ENABLE_LIBEDIT=OFF -DLLVM_ENABLE_Z3_SOLVER=OFF -DLLVM_ENABLE_OCAMLDOC=OFF -DLLVM_ENABLE_LIBXML2=OFF -DLLVM_ENABLE_PROJECTS="clang;lld" -DLLVM_TOOL_CLANG_TOOLS_EXTRA_BUILD=OFF $(LLVM_OPTION)
# Build LLVM. # Build LLVM.
$(LLVM_BUILDDIR): $(LLVM_BUILDDIR)/build.ninja $(LLVM_BUILDDIR): $(LLVM_BUILDDIR)/build.ninja
@@ -186,33 +182,25 @@ tinygo:
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 ./builder ./cgo ./compileopts ./compiler ./interp ./transform .
TEST_PACKAGES = \
container/heap \
container/list \
container/ring \
crypto/des \
crypto/md5 \
crypto/sha1 \
crypto/sha256 \
crypto/sha512 \
encoding \
encoding/ascii85 \
encoding/base32 \
encoding/hex \
hash/adler32 \
hash/fnv \
hash/crc64 \
math \
math/cmplx \
testing \
text/scanner \
unicode/utf8 \
# Test known-working standard library packages. # Test known-working standard library packages.
# TODO: parallelize, and only show failing tests (no implied -v flag). # TODO: do this in one command, parallelize, and only show failing tests (no
# implied -v flag).
.PHONY: tinygo-test .PHONY: tinygo-test
tinygo-test: tinygo-test:
$(TINYGO) test $(TEST_PACKAGES) $(TINYGO) test container/heap
$(TINYGO) test container/list
$(TINYGO) test container/ring
$(TINYGO) test crypto/des
$(TINYGO) test encoding/ascii85
$(TINYGO) test encoding/base32
$(TINYGO) test encoding/hex
$(TINYGO) test hash/adler32
$(TINYGO) test hash/fnv
$(TINYGO) test hash/crc64
$(TINYGO) test math
$(TINYGO) test math/cmplx
$(TINYGO) test text/scanner
$(TINYGO) test unicode/utf8
.PHONY: smoketest .PHONY: smoketest
smoketest: smoketest:
@@ -236,10 +224,10 @@ smoketest:
@$(MD5SUM) test.hex @$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=pca10040 examples/mcp3008 $(TINYGO) build -size short -o test.hex -target=pca10040 examples/mcp3008
@$(MD5SUM) test.hex @$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=pca10040 examples/memstats
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=microbit examples/microbit-blink $(TINYGO) build -size short -o test.hex -target=microbit examples/microbit-blink
@$(MD5SUM) test.hex @$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=microbit-v2 examples/microbit-blink
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=pca10040 examples/pininterrupt $(TINYGO) build -size short -o test.hex -target=pca10040 examples/pininterrupt
@$(MD5SUM) test.hex @$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=pca10040 examples/serial $(TINYGO) build -size short -o test.hex -target=pca10040 examples/serial
@@ -268,10 +256,6 @@ smoketest:
@$(MD5SUM) test.hex @$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=microbit-s110v8 examples/echo $(TINYGO) build -size short -o test.hex -target=microbit-s110v8 examples/echo
@$(MD5SUM) test.hex @$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=microbit-v2 examples/microbit-blink
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=microbit-v2-s113v7 examples/microbit-blink
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=nrf52840-mdk examples/blinky1 $(TINYGO) build -size short -o test.hex -target=nrf52840-mdk examples/blinky1
@$(MD5SUM) test.hex @$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=pca10031 examples/blinky1 $(TINYGO) build -size short -o test.hex -target=pca10031 examples/blinky1
@@ -304,8 +288,6 @@ smoketest:
@$(MD5SUM) test.hex @$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.gba -target=gameboy-advance examples/gba-display $(TINYGO) build -size short -o test.gba -target=gameboy-advance examples/gba-display
@$(MD5SUM) test.gba @$(MD5SUM) test.gba
$(TINYGO) build -size short -o test.hex -target=grandcentral-m4 examples/blinky1
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=itsybitsy-m4 examples/blinky1 $(TINYGO) build -size short -o test.hex -target=itsybitsy-m4 examples/blinky1
@$(MD5SUM) test.hex @$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=feather-m4 examples/blinky1 $(TINYGO) build -size short -o test.hex -target=feather-m4 examples/blinky1
@@ -342,11 +324,9 @@ smoketest:
@$(MD5SUM) test.hex @$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=feather-nrf52840 examples/blinky1 $(TINYGO) build -size short -o test.hex -target=feather-nrf52840 examples/blinky1
@$(MD5SUM) test.hex @$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=feather-nrf52840-sense examples/blinky1
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=itsybitsy-nrf52840 examples/blinky1 $(TINYGO) build -size short -o test.hex -target=itsybitsy-nrf52840 examples/blinky1
@$(MD5SUM) test.hex @$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=qtpy examples/serial $(TINYGO) build -size short -o test.hex -target=qtpy examples/blinky1
@$(MD5SUM) test.hex @$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=teensy40 examples/blinky1 $(TINYGO) build -size short -o test.hex -target=teensy40 examples/blinky1
@$(MD5SUM) test.hex @$(MD5SUM) test.hex
@@ -354,26 +334,6 @@ smoketest:
@$(MD5SUM) test.hex @$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=p1am-100 examples/blinky1 $(TINYGO) build -size short -o test.hex -target=p1am-100 examples/blinky1
@$(MD5SUM) test.hex @$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=atsame54-xpro examples/blinky1
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=atsame54-xpro examples/can
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=feather-m4-can examples/blinky1
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=feather-m4-can examples/caninterrupt
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=arduino-nano33 examples/blinky1
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=arduino-mkrwifi1010 examples/blinky1
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=pico examples/blinky1
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=nano-33-ble examples/blinky1
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=nano-rp2040 examples/blinky1
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=feather-rp2040 examples/blinky1
@$(MD5SUM) test.hex
# test pwm # test pwm
$(TINYGO) build -size short -o test.hex -target=itsybitsy-m0 examples/pwm $(TINYGO) build -size short -o test.hex -target=itsybitsy-m0 examples/pwm
@$(MD5SUM) test.hex @$(MD5SUM) test.hex
@@ -381,6 +341,8 @@ smoketest:
@$(MD5SUM) test.hex @$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=feather-m4 examples/pwm $(TINYGO) build -size short -o test.hex -target=feather-m4 examples/pwm
@$(MD5SUM) test.hex @$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=pyportal examples/pwm
@$(MD5SUM) test.hex
ifneq ($(STM32), 0) ifneq ($(STM32), 0)
$(TINYGO) build -size short -o test.hex -target=bluepill examples/blinky1 $(TINYGO) build -size short -o test.hex -target=bluepill examples/blinky1
@$(MD5SUM) test.hex @$(MD5SUM) test.hex
@@ -392,8 +354,6 @@ ifneq ($(STM32), 0)
@$(MD5SUM) test.hex @$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=nucleo-f722ze examples/blinky1 $(TINYGO) build -size short -o test.hex -target=nucleo-f722ze examples/blinky1
@$(MD5SUM) test.hex @$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=nucleo-l031k6 examples/blinky1
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=nucleo-l432kc examples/blinky1 $(TINYGO) build -size short -o test.hex -target=nucleo-l432kc examples/blinky1
@$(MD5SUM) test.hex @$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=nucleo-l552ze examples/blinky1 $(TINYGO) build -size short -o test.hex -target=nucleo-l552ze examples/blinky1
@@ -404,8 +364,6 @@ ifneq ($(STM32), 0)
@$(MD5SUM) test.hex @$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=stm32f4disco-1 examples/blinky1 $(TINYGO) build -size short -o test.hex -target=stm32f4disco-1 examples/blinky1
@$(MD5SUM) test.hex @$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=stm32f4disco-1 examples/pwm
@$(MD5SUM) test.hex
endif endif
ifneq ($(AVR), 0) ifneq ($(AVR), 0)
$(TINYGO) build -size short -o test.hex -target=atmega1284p examples/serial $(TINYGO) build -size short -o test.hex -target=atmega1284p examples/serial
@@ -416,10 +374,6 @@ ifneq ($(AVR), 0)
@$(MD5SUM) test.hex @$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=arduino -scheduler=tasks examples/blinky1 $(TINYGO) build -size short -o test.hex -target=arduino -scheduler=tasks examples/blinky1
@$(MD5SUM) test.hex @$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=arduino-mega1280 examples/blinky1
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=arduino-mega1280 examples/pwm
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=arduino-nano examples/blinky1 $(TINYGO) build -size short -o test.hex -target=arduino-nano examples/blinky1
@$(MD5SUM) test.hex @$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=digispark examples/blinky1 $(TINYGO) build -size short -o test.hex -target=digispark examples/blinky1
@@ -433,8 +387,6 @@ ifneq ($(XTENSA), 0)
$(TINYGO) build -size short -o test.bin -target=nodemcu examples/blinky1 $(TINYGO) build -size short -o test.bin -target=nodemcu examples/blinky1
@$(MD5SUM) test.bin @$(MD5SUM) test.bin
endif endif
$(TINYGO) build -size short -o test.bin -target=esp32c3 examples/serial
@$(MD5SUM) test.bin
$(TINYGO) build -size short -o test.hex -target=hifive1b examples/blinky1 $(TINYGO) build -size short -o test.hex -target=hifive1b examples/blinky1
@$(MD5SUM) test.hex @$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=hifive1-qemu examples/serial $(TINYGO) build -size short -o test.hex -target=hifive1-qemu examples/serial
@@ -448,15 +400,10 @@ endif
@$(MD5SUM) test.hex @$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=pca10040 -opt=1 examples/blinky1 $(TINYGO) build -size short -o test.hex -target=pca10040 -opt=1 examples/blinky1
@$(MD5SUM) test.hex @$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=pca10040 -serial=none examples/echo
@$(MD5SUM) test.hex
$(TINYGO) build -o test.nro -target=nintendoswitch examples/serial $(TINYGO) build -o test.nro -target=nintendoswitch examples/serial
@$(MD5SUM) test.nro @$(MD5SUM) test.nro
$(TINYGO) build -size short -o test.hex -target=pca10040 -opt=0 ./testdata/stdlib.go $(TINYGO) build -size short -o test.hex -target=pca10040 -opt=0 ./testdata/stdlib.go
@$(MD5SUM) test.hex @$(MD5SUM) test.hex
ifneq ($(OS),Windows_NT)
$(TINYGO) build -o test.elf -gc=leaking -scheduler=none examples/serial
endif
wasmtest: wasmtest:
+2 -17
View File
@@ -43,19 +43,15 @@ See the [getting started instructions](https://tinygo.org/getting-started/) for
You can compile TinyGo programs for microcontrollers, WebAssembly and Linux. You can compile TinyGo programs for microcontrollers, WebAssembly and Linux.
The following 70 microcontroller boards are currently supported: The following 55 microcontroller boards are currently supported:
* [Adafruit Circuit Playground Bluefruit](https://www.adafruit.com/product/4333) * [Adafruit Circuit Playground Bluefruit](https://www.adafruit.com/product/4333)
* [Adafruit Circuit Playground Express](https://www.adafruit.com/product/3333) * [Adafruit Circuit Playground Express](https://www.adafruit.com/product/3333)
* [Adafruit CLUE](https://www.adafruit.com/product/4500) * [Adafruit CLUE](https://www.adafruit.com/product/4500)
* [Adafruit Feather M0](https://www.adafruit.com/product/2772) * [Adafruit Feather M0](https://www.adafruit.com/product/2772)
* [Adafruit Feather M4](https://www.adafruit.com/product/3857) * [Adafruit Feather M4](https://www.adafruit.com/product/3857)
* [Adafruit Feather M4 CAN](https://www.adafruit.com/product/4759)
* [Adafruit Feather nRF52840 Express](https://www.adafruit.com/product/4062) * [Adafruit Feather nRF52840 Express](https://www.adafruit.com/product/4062)
* [Adafruit Feather nRF52840 Sense](https://www.adafruit.com/product/4516)
* [Adafruit Feather RP2040](https://www.adafruit.com/product/4884)
* [Adafruit Feather STM32F405 Express](https://www.adafruit.com/product/4382) * [Adafruit Feather STM32F405 Express](https://www.adafruit.com/product/4382)
* [Adafruit Grand Central M4](https://www.adafruit.com/product/4064)
* [Adafruit ItsyBitsy M0](https://www.adafruit.com/product/3727) * [Adafruit ItsyBitsy M0](https://www.adafruit.com/product/3727)
* [Adafruit ItsyBitsy M4](https://www.adafruit.com/product/3800) * [Adafruit ItsyBitsy M4](https://www.adafruit.com/product/3800)
* [Adafruit ItsyBitsy nRF52840](https://www.adafruit.com/product/4481) * [Adafruit ItsyBitsy nRF52840](https://www.adafruit.com/product/4481)
@@ -66,15 +62,10 @@ The following 70 microcontroller boards are currently supported:
* [Adafruit PyPortal](https://www.adafruit.com/product/4116) * [Adafruit PyPortal](https://www.adafruit.com/product/4116)
* [Adafruit QT Py](https://www.adafruit.com/product/4600) * [Adafruit QT Py](https://www.adafruit.com/product/4600)
* [Adafruit Trinket M0](https://www.adafruit.com/product/3500) * [Adafruit Trinket M0](https://www.adafruit.com/product/3500)
* [Arduino Mega 1280](https://www.arduino.cc/en/Main/arduinoBoardMega/)
* [Arduino Mega 2560](https://store.arduino.cc/arduino-mega-2560-rev3) * [Arduino Mega 2560](https://store.arduino.cc/arduino-mega-2560-rev3)
* [Arduino MKR1000](https://store.arduino.cc/arduino-mkr1000-wifi) * [Arduino MKR1000](https://store.arduino.cc/arduino-mkr1000-wifi)
* [Arduino MKR WiFi 1010](https://store.arduino.cc/usa/mkr-wifi-1010)
* [Arduino Nano](https://store.arduino.cc/arduino-nano) * [Arduino Nano](https://store.arduino.cc/arduino-nano)
* [Arduino Nano 33 BLE](https://store.arduino.cc/nano-33-ble) * [Arduino Nano33 IoT](https://store.arduino.cc/nano-33-iot)
* [Arduino Nano 33 BLE Sense](https://store.arduino.cc/nano-33-ble-sense)
* [Arduino Nano 33 IoT](https://store.arduino.cc/nano-33-iot)
* [Arduino Nano RP2040 Connect](https://store.arduino.cc/nano-rp2040-connect)
* [Arduino Uno](https://store.arduino.cc/arduino-uno-rev3) * [Arduino Uno](https://store.arduino.cc/arduino-uno-rev3)
* [Arduino Zero](https://store.arduino.cc/usa/arduino-zero) * [Arduino Zero](https://store.arduino.cc/usa/arduino-zero)
* [BBC micro:bit](https://microbit.org/) * [BBC micro:bit](https://microbit.org/)
@@ -86,13 +77,11 @@ The following 70 microcontroller boards are currently supported:
* [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/) * [Makerdiary nRF52840-MDK USB Dongle](https://wiki.makerdiary.com/nrf52840-mdk-usb-dongle/)
* [Microchip SAM E54 Xplained Pro](https://www.microchip.com/developmenttools/productdetails/atsame54-xpro)
* [nice!nano](https://docs.nicekeyboards.com/#/nice!nano/) * [nice!nano](https://docs.nicekeyboards.com/#/nice!nano/)
* [Nintendo Switch](https://www.nintendo.com/switch/) * [Nintendo Switch](https://www.nintendo.com/switch/)
* [Nordic Semiconductor PCA10031](https://www.nordicsemi.com/eng/Products/nRF51-Dongle) * [Nordic Semiconductor PCA10031](https://www.nordicsemi.com/eng/Products/nRF51-Dongle)
* [Nordic Semiconductor PCA10040](https://www.nordicsemi.com/eng/Products/Bluetooth-low-energy/nRF52-DK) * [Nordic Semiconductor PCA10040](https://www.nordicsemi.com/eng/Products/Bluetooth-low-energy/nRF52-DK)
* [Nordic Semiconductor PCA10056](https://www.nordicsemi.com/Software-and-Tools/Development-Kits/nRF52840-DK) * [Nordic Semiconductor PCA10056](https://www.nordicsemi.com/Software-and-Tools/Development-Kits/nRF52840-DK)
* [Nordic Semiconductor pca10059](https://www.nordicsemi.com/Software-and-tools/Development-Kits/nRF52840-Dongle)
* [Particle Argon](https://docs.particle.io/datasheets/wi-fi/argon-datasheet/) * [Particle Argon](https://docs.particle.io/datasheets/wi-fi/argon-datasheet/)
* [Particle Boron](https://docs.particle.io/datasheets/cellular/boron-datasheet/) * [Particle Boron](https://docs.particle.io/datasheets/cellular/boron-datasheet/)
* [Particle Xenon](https://docs.particle.io/datasheets/discontinued/xenon-datasheet/) * [Particle Xenon](https://docs.particle.io/datasheets/discontinued/xenon-datasheet/)
@@ -101,16 +90,12 @@ The following 70 microcontroller boards are currently supported:
* [PJRC Teensy 3.6](https://www.pjrc.com/store/teensy36.html) * [PJRC Teensy 3.6](https://www.pjrc.com/store/teensy36.html)
* [PJRC Teensy 4.0](https://www.pjrc.com/store/teensy40.html) * [PJRC Teensy 4.0](https://www.pjrc.com/store/teensy40.html)
* [ProductivityOpen P1AM-100](https://facts-engineering.github.io/modules/P1AM-100/P1AM-100.html) * [ProductivityOpen P1AM-100](https://facts-engineering.github.io/modules/P1AM-100/P1AM-100.html)
* [Raspberry Pi Pico](https://www.raspberrypi.org/products/raspberry-pi-pico/)
* [Raytac MDBT50Q-RX Dongle (with TinyUF2 bootloader)](https://www.adafruit.com/product/5199)
* [Seeed Wio Terminal](https://www.seeedstudio.com/Wio-Terminal-p-4509.html) * [Seeed Wio Terminal](https://www.seeedstudio.com/Wio-Terminal-p-4509.html)
* [Seeed Seeeduino XIAO](https://www.seeedstudio.com/Seeeduino-XIAO-Arduino-Microcontroller-SAMD21-Cortex-M0+-p-4426.html) * [Seeed Seeeduino XIAO](https://www.seeedstudio.com/Seeeduino-XIAO-Arduino-Microcontroller-SAMD21-Cortex-M0+-p-4426.html)
* [Seeed Sipeed MAix BiT](https://www.seeedstudio.com/Sipeed-MAix-BiT-for-RISC-V-AI-IoT-p-2872.html) * [Seeed Sipeed MAix BiT](https://www.seeedstudio.com/Sipeed-MAix-BiT-for-RISC-V-AI-IoT-p-2872.html)
* [SiFIve HiFive1](https://www.sifive.com/boards/hifive1) * [SiFIve HiFive1](https://www.sifive.com/boards/hifive1)
* [ST Micro "Nucleo" F103RB](https://www.st.com/en/evaluation-tools/nucleo-f103rb.html) * [ST Micro "Nucleo" F103RB](https://www.st.com/en/evaluation-tools/nucleo-f103rb.html)
* [ST Micro "Nucleo" F722ZE](https://www.st.com/en/evaluation-tools/nucleo-f722ze.html) * [ST Micro "Nucleo" F722ZE](https://www.st.com/en/evaluation-tools/nucleo-f722ze.html)
* [ST Micro "Nucleo" L031K6](https://www.st.com/ja/evaluation-tools/nucleo-l031k6.html)
* [ST Micro "Nucleo" L432KC](https://www.st.com/ja/evaluation-tools/nucleo-l432kc.html)
* [ST Micro "Nucleo" L552ZE](https://www.st.com/en/evaluation-tools/nucleo-l552ze-q.html) * [ST Micro "Nucleo" L552ZE](https://www.st.com/en/evaluation-tools/nucleo-l552ze-q.html)
* [ST Micro STM32F103XX "Bluepill"](https://stm32-base.org/boards/STM32F103C8T6-Blue-Pill) * [ST Micro STM32F103XX "Bluepill"](https://stm32-base.org/boards/STM32F103C8T6-Blue-Pill)
* [ST Micro STM32F407 "Discovery"](https://www.st.com/en/evaluation-tools/stm32f4discovery.html) * [ST Micro STM32F407 "Discovery"](https://www.st.com/en/evaluation-tools/stm32f4discovery.html)
+2 -2
View File
@@ -12,7 +12,7 @@ jobs:
steps: steps:
- task: GoTool@0 - task: GoTool@0
inputs: inputs:
version: '1.17' version: '1.16'
- checkout: self - checkout: self
fetchDepth: 1 fetchDepth: 1
- task: Cache@2 - task: Cache@2
@@ -28,7 +28,7 @@ jobs:
- task: CacheBeta@0 - task: CacheBeta@0
displayName: Cache LLVM build displayName: Cache LLVM build
inputs: inputs:
key: llvm-build-11-windows-v5 key: llvm-build-11-windows-v4
path: llvm-build path: llvm-build
- task: Bash@3 - task: Bash@3
displayName: Build LLVM displayName: Build LLVM
+118 -310
View File
@@ -12,9 +12,7 @@ import (
"errors" "errors"
"fmt" "fmt"
"go/types" "go/types"
"hash/crc32"
"io/ioutil" "io/ioutil"
"math/bits"
"os" "os"
"path/filepath" "path/filepath"
"runtime" "runtime"
@@ -41,11 +39,6 @@ type BuildResult struct {
// The directory of the main package. This is useful for testing as the test // The directory of the main package. This is useful for testing as the test
// binary must be run in the directory of the tested package. // binary must be run in the directory of the tested package.
MainDir string MainDir string
// ImportPath is the import path of the main package. This is useful for
// correctly printing test results: the import path isn't always the same as
// the path listed on the command line.
ImportPath string
} }
// packageAction is the struct that is serialized to JSON and hashed, to work as // packageAction is the struct that is serialized to JSON and hashed, to work as
@@ -59,17 +52,14 @@ type BuildResult struct {
// key, avoiding the need for recompiling all dependencies when only the // key, avoiding the need for recompiling all dependencies when only the
// implementation of an imported package changes. // implementation of an imported package changes.
type packageAction struct { type packageAction struct {
ImportPath string ImportPath string
CompilerVersion int // compiler.Version CompilerVersion int // compiler.Version
InterpVersion int // interp.Version InterpVersion int // interp.Version
LLVMVersion string LLVMVersion string
Config *compiler.Config Config *compiler.Config
CFlags []string CFlags []string
FileHashes map[string]string // hash of every file that's part of the package FileHashes map[string]string // hash of every file that's part of the package
Imports map[string]string // map from imported package to action ID hash Imports map[string]string // map from imported package to action ID hash
OptLevel int // LLVM optimization level (0-3)
SizeLevel int // LLVM optimization for size level (0-2)
UndefinedGlobals []string // globals that are left as external globals (no initializer)
} }
// Build performs a single package to executable Go build. It takes in a package // Build performs a single package to executable Go build. It takes in a package
@@ -100,8 +90,7 @@ func Build(pkgName, outpath string, config *compileopts.Config, action func(Buil
AutomaticStackSize: config.AutomaticStackSize(), AutomaticStackSize: config.AutomaticStackSize(),
DefaultStackSize: config.Target.DefaultStackSize, DefaultStackSize: config.Target.DefaultStackSize,
NeedsStackObjects: config.NeedsStackObjects(), NeedsStackObjects: config.NeedsStackObjects(),
Debug: true, Debug: config.Debug(),
LLVMFeatures: config.LLVMFeatures(),
} }
// Load the target machine, which is the LLVM object that contains all // Load the target machine, which is the LLVM object that contains all
@@ -124,6 +113,11 @@ func Build(pkgName, outpath string, config *compileopts.Config, action func(Buil
return err return err
} }
// The slice of jobs that orchestrates most of the build.
// This is somewhat like an in-memory Makefile with each job being a
// Makefile target.
var jobs []*compileJob
// Create the *ssa.Program. This does not yet build the entire SSA of the // Create the *ssa.Program. This does not yet build the entire SSA of the
// program so it's pretty fast and doesn't need to be parallelized. // program so it's pretty fast and doesn't need to be parallelized.
program := lprogram.LoadSSA() program := lprogram.LoadSSA()
@@ -133,30 +127,20 @@ func Build(pkgName, outpath string, config *compileopts.Config, action func(Buil
var packageJobs []*compileJob var packageJobs []*compileJob
packageBitcodePaths := make(map[string]string) packageBitcodePaths := make(map[string]string)
packageActionIDs := make(map[string]string) packageActionIDs := make(map[string]string)
optLevel, sizeLevel, _ := config.OptLevels()
for _, pkg := range lprogram.Sorted() { for _, pkg := range lprogram.Sorted() {
pkg := pkg // necessary to avoid a race condition pkg := pkg // necessary to avoid a race condition
var undefinedGlobals []string
for name := range config.Options.GlobalValues[pkg.Pkg.Path()] {
undefinedGlobals = append(undefinedGlobals, name)
}
sort.Strings(undefinedGlobals)
// Create a cache key: a hash from the action ID below that contains all // Create a cache key: a hash from the action ID below that contains all
// the parameters for the build. // the parameters for the build.
actionID := packageAction{ actionID := packageAction{
ImportPath: pkg.ImportPath, ImportPath: pkg.ImportPath,
CompilerVersion: compiler.Version, CompilerVersion: compiler.Version,
InterpVersion: interp.Version, InterpVersion: interp.Version,
LLVMVersion: llvm.Version, LLVMVersion: llvm.Version,
Config: compilerConfig, Config: compilerConfig,
CFlags: pkg.CFlags, CFlags: pkg.CFlags,
FileHashes: make(map[string]string, len(pkg.FileHashes)), FileHashes: make(map[string]string, len(pkg.FileHashes)),
Imports: make(map[string]string, len(pkg.Pkg.Imports())), Imports: make(map[string]string, len(pkg.Pkg.Imports())),
OptLevel: optLevel,
SizeLevel: sizeLevel,
UndefinedGlobals: undefinedGlobals,
} }
for filePath, hash := range pkg.FileHashes { for filePath, hash := range pkg.FileHashes {
actionID.FileHashes[filePath] = hex.EncodeToString(hash) actionID.FileHashes[filePath] = hex.EncodeToString(hash)
@@ -207,25 +191,6 @@ func Build(pkgName, outpath string, config *compileopts.Config, action func(Buil
return errors.New("verification error after compiling package " + pkg.ImportPath) return errors.New("verification error after compiling package " + pkg.ImportPath)
} }
// Erase all globals that are part of the undefinedGlobals list.
// This list comes from the -ldflags="-X pkg.foo=val" option.
// Instead of setting the value directly in the AST (which would
// mean the value, which may be a secret, is stored in the build
// cache), the global itself is left external (undefined) and is
// only set at the end of the compilation.
for _, name := range undefinedGlobals {
globalName := pkg.Pkg.Path() + "." + name
global := mod.NamedGlobal(globalName)
if global.IsNil() {
return errors.New("global not found: " + globalName)
}
name := global.Name()
newGlobal := llvm.AddGlobal(mod, global.Type().ElementType(), name+".tmp")
global.ReplaceAllUsesWith(newGlobal)
global.EraseFromParentAsGlobal()
newGlobal.SetName(name)
}
// Try to interpret package initializers at compile time. // Try to interpret package initializers at compile time.
// It may only be possible to do this partially, in which case // It may only be possible to do this partially, in which case
// it is completed after all IR files are linked. // it is completed after all IR files are linked.
@@ -241,38 +206,6 @@ func Build(pkgName, outpath string, config *compileopts.Config, action func(Buil
return errors.New("verification error after interpreting " + pkgInit.Name()) return errors.New("verification error after interpreting " + pkgInit.Name())
} }
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. // Serialize the LLVM module as a bitcode file.
// Write to a temporary path that is renamed to the destination // Write to a temporary path that is renamed to the destination
// file to avoid race conditions with other TinyGo invocatiosn // file to avoid race conditions with other TinyGo invocatiosn
@@ -306,6 +239,7 @@ func Build(pkgName, outpath string, config *compileopts.Config, action func(Buil
return os.Rename(f.Name(), bitcodePath) return os.Rename(f.Name(), bitcodePath)
}, },
} }
jobs = append(jobs, job)
packageJobs = append(packageJobs, job) packageJobs = append(packageJobs, job)
} }
@@ -396,13 +330,14 @@ func Build(pkgName, outpath string, config *compileopts.Config, action func(Buil
return nil return nil
}, },
} }
jobs = append(jobs, programJob)
// Check whether we only need to create an object file. // Check whether we only need to create an object file.
// If so, we don't need to link anything and will be finished quickly. // If so, we don't need to link anything and will be finished quickly.
outext := filepath.Ext(outpath) outext := filepath.Ext(outpath)
if outext == ".o" || outext == ".bc" || outext == ".ll" { if outext == ".o" || outext == ".bc" || outext == ".ll" {
// Run jobs to produce the LLVM module. // Run jobs to produce the LLVM module.
err := runJobs(programJob) err := runJobs(jobs)
if err != nil { if err != nil {
return err return err
} }
@@ -443,6 +378,7 @@ func Build(pkgName, outpath string, config *compileopts.Config, action func(Buil
return ioutil.WriteFile(objfile, llvmBuf.Bytes(), 0666) return ioutil.WriteFile(objfile, llvmBuf.Bytes(), 0666)
}, },
} }
jobs = append(jobs, outputObjectFileJob)
// Prepare link command. // Prepare link command.
linkerDependencies := []*compileJob{outputObjectFileJob} linkerDependencies := []*compileJob{outputObjectFileJob}
@@ -457,23 +393,49 @@ func Build(pkgName, outpath string, config *compileopts.Config, action func(Buil
if err != nil { if err != nil {
return err return err
} }
jobs = append(jobs, job.dependencies...)
jobs = append(jobs, job)
linkerDependencies = append(linkerDependencies, job) linkerDependencies = append(linkerDependencies, job)
} }
// Add libc dependency if needed.
root := goenv.Get("TINYGOROOT")
switch config.Target.Libc {
case "picolibc":
job, err := Picolibc.load(config.Triple(), config.CPU(), dir)
if err != nil {
return err
}
// The library needs to be compiled (cache miss).
jobs = append(jobs, job.dependencies...)
jobs = append(jobs, job)
linkerDependencies = append(linkerDependencies, job)
case "wasi-libc":
path := filepath.Join(root, "lib/wasi-libc/sysroot/lib/wasm32-wasi/libc.a")
if _, err := os.Stat(path); os.IsNotExist(err) {
return errors.New("could not find wasi-libc, perhaps you need to run `make wasi-libc`?")
}
ldflags = append(ldflags, path)
case "":
// no library specified, so nothing to do
default:
return fmt.Errorf("unknown libc: %s", config.Target.Libc)
}
// Add jobs to compile extra files. These files are in C or assembly and // Add jobs to compile extra files. These files are in C or assembly and
// contain things like the interrupt vector table and low level operations // contain things like the interrupt vector table and low level operations
// such as stack switching. // such as stack switching.
root := goenv.Get("TINYGOROOT")
for _, path := range config.ExtraFiles() { for _, path := range config.ExtraFiles() {
abspath := filepath.Join(root, path) abspath := filepath.Join(root, path)
job := &compileJob{ job := &compileJob{
description: "compile extra file " + path, description: "compile extra file " + path,
run: func(job *compileJob) error { run: func(job *compileJob) error {
result, err := compileAndCacheCFile(abspath, dir, config.CFlags(), config.Options.PrintCommands) result, err := compileAndCacheCFile(abspath, dir, config.CFlags(), config)
job.result = result job.result = result
return err return err
}, },
} }
jobs = append(jobs, job)
linkerDependencies = append(linkerDependencies, job) linkerDependencies = append(linkerDependencies, job)
} }
@@ -487,11 +449,12 @@ func Build(pkgName, outpath string, config *compileopts.Config, action func(Buil
job := &compileJob{ job := &compileJob{
description: "compile CGo file " + abspath, description: "compile CGo file " + abspath,
run: func(job *compileJob) error { run: func(job *compileJob) error {
result, err := compileAndCacheCFile(abspath, dir, pkg.CFlags, config.Options.PrintCommands) result, err := compileAndCacheCFile(abspath, dir, pkg.CFlags, config)
job.result = result job.result = result
return err return err
}, },
} }
jobs = append(jobs, job)
linkerDependencies = append(linkerDependencies, job) linkerDependencies = append(linkerDependencies, job)
} }
} }
@@ -502,63 +465,9 @@ func Build(pkgName, outpath string, config *compileopts.Config, action func(Buil
ldflags = append(ldflags, lprogram.LDFlags...) ldflags = append(ldflags, lprogram.LDFlags...)
} }
// Add libc dependency if needed.
switch config.Target.Libc {
case "picolibc":
job, err := Picolibc.load(config.Triple(), config.CPU(), dir)
if err != nil {
return err
}
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`?")
}
job := dummyCompileJob(path)
linkerDependencies = append(linkerDependencies, job)
case "":
// no library specified, so nothing to do
default:
return fmt.Errorf("unknown libc: %s", config.Target.Libc)
}
// Strip debug information with -no-debug.
if !config.Debug() {
for _, tag := range config.BuildTags() {
if tag == "baremetal" {
// Don't use -no-debug on baremetal targets. It makes no sense:
// the debug information isn't flashed to the device anyway.
return fmt.Errorf("stripping debug information is unnecessary for baremetal targets")
}
}
if config.Target.Linker == "wasm-ld" {
// Don't just strip debug information, also compress relocations
// while we're at it. Relocations can only be compressed when debug
// information is stripped.
ldflags = append(ldflags, "--strip-debug", "--compress-relocations")
} else {
switch config.GOOS() {
case "linux":
// Either real linux or an embedded system (like AVR) that
// pretends to be Linux. It's a ELF linker wrapped by GCC in any
// case.
ldflags = append(ldflags, "-Wl,--strip-debug")
case "darwin":
// MacOS (darwin) doesn't have a linker flag to strip debug
// information. Apple expects you to use the strip command
// instead.
return errors.New("cannot remove debug information: MacOS doesn't suppor this linker flag")
default:
// Other OSes may have different flags.
return errors.New("cannot remove debug information: unknown OS: " + config.GOOS())
}
}
}
// Create a linker job, which links all object files together and does some // Create a linker job, which links all object files together and does some
// extra stuff that can only be done after linking. // extra stuff that can only be done after linking.
linkJob := &compileJob{ jobs = append(jobs, &compileJob{
description: "link", description: "link",
dependencies: linkerDependencies, dependencies: linkerDependencies,
run: func(job *compileJob) error { run: func(job *compileJob) error {
@@ -568,8 +477,8 @@ func Build(pkgName, outpath string, config *compileopts.Config, action func(Buil
} }
ldflags = append(ldflags, dependency.result) ldflags = append(ldflags, dependency.result)
} }
if config.Options.PrintCommands != nil { if config.Options.PrintCommands {
config.Options.PrintCommands(config.Target.Linker, ldflags...) fmt.Printf("%s %s\n", config.Target.Linker, strings.Join(ldflags, " "))
} }
err = link(config.Target.Linker, ldflags...) err = link(config.Target.Linker, ldflags...)
if err != nil { if err != nil {
@@ -587,8 +496,6 @@ func Build(pkgName, outpath string, config *compileopts.Config, action func(Buil
return err return err
} }
} }
// Apply ELF patches
if config.AutomaticStackSize() { if config.AutomaticStackSize() {
// Modify the .tinygo_stacksizes section that contains a stack size // Modify the .tinygo_stacksizes section that contains a stack size
// for each goroutine. // for each goroutine.
@@ -597,13 +504,6 @@ func Build(pkgName, outpath string, config *compileopts.Config, action func(Buil
return fmt.Errorf("could not modify stack sizes: %w", err) return fmt.Errorf("could not modify stack sizes: %w", err)
} }
} }
if config.RP2040BootPatch() {
// Patch the second stage bootloader CRC into the .boot2 section
err = patchRP2040BootCRC(executable)
if err != nil {
return fmt.Errorf("could not patch RP2040 second stage boot loader: %w", err)
}
}
if config.Options.PrintSizes == "short" || config.Options.PrintSizes == "full" { if config.Options.PrintSizes == "short" || config.Options.PrintSizes == "full" {
sizes, err := loadProgramSize(executable) sizes, err := loadProgramSize(executable)
@@ -631,12 +531,12 @@ func Build(pkgName, outpath string, config *compileopts.Config, action func(Buil
return nil return nil
}, },
} })
// Run all jobs to compile and link the program. // Run all jobs to compile and link the program.
// Do this now (instead of after elf-to-hex and similar conversions) as it // Do this now (instead of after elf-to-hex and similar conversions) as it
// is simpler and cannot be parallelized. // is simpler and cannot be parallelized.
err = runJobs(linkJob) err = runJobs(jobs)
if err != nil { if err != nil {
return err return err
} }
@@ -661,7 +561,7 @@ func Build(pkgName, outpath string, config *compileopts.Config, action func(Buil
if err != nil { if err != nil {
return err return err
} }
case "esp32", "esp32c3", "esp8266": case "esp32", "esp8266":
// Special format for the ESP family of chips (parsed by the ROM // Special format for the ESP family of chips (parsed by the ROM
// bootloader). // bootloader).
tmppath = filepath.Join(dir, "main"+outext) tmppath = filepath.Join(dir, "main"+outext)
@@ -669,25 +569,12 @@ func Build(pkgName, outpath string, config *compileopts.Config, action func(Buil
if err != nil { if err != nil {
return err return err
} }
case "nrf-dfu":
// special format for nrfutil for Nordic chips
tmphexpath := filepath.Join(dir, "main.hex")
err := objcopy(executable, tmphexpath, "hex")
if err != nil {
return err
}
tmppath = filepath.Join(dir, "main"+outext)
err = makeDFUFirmwareImage(config.Options, tmphexpath, tmppath)
if err != nil {
return err
}
default: default:
return fmt.Errorf("unknown output binary format: %s", outputBinaryFormat) return fmt.Errorf("unknown output binary format: %s", outputBinaryFormat)
} }
return action(BuildResult{ return action(BuildResult{
Binary: tmppath, Binary: tmppath,
MainDir: lprogram.MainPkg().Dir, MainDir: lprogram.MainPkg().Dir,
ImportPath: lprogram.MainPkg().ImportPath,
}) })
} }
@@ -716,12 +603,6 @@ func optimizeProgram(mod llvm.Module, config *compileopts.Config) error {
transform.ApplyFunctionSections(mod) // -ffunction-sections transform.ApplyFunctionSections(mod) // -ffunction-sections
} }
// Insert values from -ldflags="-X ..." into the IR.
err = setGlobalValues(mod, config.Options.GlobalValues)
if err != nil {
return err
}
// Browsers cannot handle external functions that have type i64 because it // Browsers cannot handle external functions that have type i64 because it
// cannot be represented exactly in JavaScript (JS only has doubles). To // cannot be represented exactly in JavaScript (JS only has doubles). To
// keep functions interoperable, pass int64 types as pointers to // keep functions interoperable, pass int64 types as pointers to
@@ -736,8 +617,21 @@ func optimizeProgram(mod llvm.Module, config *compileopts.Config) error {
// Optimization levels here are roughly the same as Clang, but probably not // Optimization levels here are roughly the same as Clang, but probably not
// exactly. // exactly.
optLevel, sizeLevel, inlinerThreshold := config.OptLevels() var errs []error
errs := transform.Optimize(mod, config, optLevel, sizeLevel, inlinerThreshold) switch config.Options.Opt {
case "none", "0":
errs = transform.Optimize(mod, config, 0, 0, 0) // -O0
case "1":
errs = transform.Optimize(mod, config, 1, 0, 0) // -O1
case "2":
errs = transform.Optimize(mod, config, 2, 0, 225) // -O2
case "s":
errs = transform.Optimize(mod, config, 2, 1, 225) // -Os
case "z":
errs = transform.Optimize(mod, config, 2, 2, 5) // -Oz, default
default:
return errors.New("unknown optimization level: -opt=" + config.Options.Opt)
}
if len(errs) > 0 { if len(errs) > 0 {
return newMultiError(errs) return newMultiError(errs)
} }
@@ -759,71 +653,6 @@ func optimizeProgram(mod llvm.Module, config *compileopts.Config) error {
return nil return nil
} }
// setGlobalValues sets the global values from the -ldflags="-X ..." compiler
// option in the given module. An error may be returned if the global is not of
// the expected type.
func setGlobalValues(mod llvm.Module, globals map[string]map[string]string) error {
var pkgPaths []string
for pkgPath := range globals {
pkgPaths = append(pkgPaths, pkgPath)
}
sort.Strings(pkgPaths)
for _, pkgPath := range pkgPaths {
pkg := globals[pkgPath]
var names []string
for name := range pkg {
names = append(names, name)
}
sort.Strings(names)
for _, name := range names {
value := pkg[name]
globalName := pkgPath + "." + name
global := mod.NamedGlobal(globalName)
if global.IsNil() || !global.Initializer().IsNil() {
// The global either does not exist (optimized away?) or has
// some value, in which case it has already been initialized at
// package init time.
continue
}
// A strin is a {ptr, len} pair. We need these types to build the
// initializer.
initializerType := global.Type().ElementType()
if initializerType.TypeKind() != llvm.StructTypeKind || initializerType.StructName() == "" {
return fmt.Errorf("%s: not a string", globalName)
}
elementTypes := initializerType.StructElementTypes()
if len(elementTypes) != 2 {
return fmt.Errorf("%s: not a string", globalName)
}
// Create a buffer for the string contents.
bufInitializer := mod.Context().ConstString(value, false)
buf := llvm.AddGlobal(mod, bufInitializer.Type(), ".string")
buf.SetInitializer(bufInitializer)
buf.SetAlignment(1)
buf.SetUnnamedAddr(true)
buf.SetLinkage(llvm.PrivateLinkage)
// Create the string value, which is a {ptr, len} pair.
zero := llvm.ConstInt(mod.Context().Int32Type(), 0, false)
ptr := llvm.ConstGEP(buf, []llvm.Value{zero, zero})
if ptr.Type() != elementTypes[0] {
return fmt.Errorf("%s: not a string", globalName)
}
length := llvm.ConstInt(elementTypes[1], uint64(len(value)), false)
initializer := llvm.ConstNamedStruct(initializerType, []llvm.Value{
ptr,
length,
})
// Set the initializer. No initializer should be set at this point.
global.SetInitializer(initializer)
}
}
return nil
}
// functionStackSizes keeps stack size information about a single function // functionStackSizes keeps stack size information about a single function
// (usually a goroutine). // (usually a goroutine).
type functionStackSize struct { type functionStackSize struct {
@@ -950,7 +779,30 @@ func determineStackSizes(mod llvm.Module, executable string) ([]string, map[stri
// stack size information. Before this modification, all stack sizes in the // stack size information. Before this modification, all stack sizes in the
// section assume the default stack size (which is relatively big). // section assume the default stack size (which is relatively big).
func modifyStackSizes(executable string, stackSizeLoads []string, stackSizes map[string]functionStackSize) error { func modifyStackSizes(executable string, stackSizeLoads []string, stackSizes map[string]functionStackSize) error {
data, fileHeader, err := getElfSectionData(executable, ".tinygo_stacksizes") fp, err := os.OpenFile(executable, os.O_RDWR, 0)
if err != nil {
return err
}
defer fp.Close()
elfFile, err := elf.NewFile(fp)
if err != nil {
return err
}
section := elfFile.Section(".tinygo_stacksizes")
if section == nil {
return errors.New("could not find .tinygo_stacksizes section")
}
if section.Size != section.FileSize {
// Sanity check.
return fmt.Errorf("expected .tinygo_stacksizes to have identical size and file size, got %d and %d", section.Size, section.FileSize)
}
// Read all goroutine stack sizes.
data := make([]byte, section.Size)
_, err = fp.ReadAt(data, int64(section.Offset))
if err != nil { if err != nil {
return err return err
} }
@@ -972,19 +824,15 @@ func modifyStackSizes(executable string, stackSizeLoads []string, stackSizes map
if fn.stackSizeType == stacksize.Bounded { if fn.stackSizeType == stacksize.Bounded {
stackSize := uint32(fn.stackSize) stackSize := uint32(fn.stackSize)
// Add stack size used by interrupts. // Adding 4 for the stack canary. Even though the size may be
switch fileHeader.Machine { // automatically determined, stack overflow checking is still
case elf.EM_ARM: // important as the stack size cannot be determined for all
if stackSize%8 != 0 { // goroutines.
// If the stack isn't a multiple of 8, it means the leaf stackSize += 4
// function with the biggest stack depth doesn't have an aligned
// stack. If the STKALIGN flag is set (which it is by default)
// the interrupt controller will forcibly align the stack before
// storing in-use registers. This will thus overwrite one word
// past the end of the stack (off-by-one).
stackSize += 4
}
// Add stack size used by interrupts.
switch elfFile.Machine {
case elf.EM_ARM:
// On Cortex-M (assumed here), this stack size is 8 words or 32 // On Cortex-M (assumed here), this stack size is 8 words or 32
// bytes. This is only to store the registers that the interrupt // bytes. This is only to store the registers that the interrupt
// may modify, the interrupt will switch to the interrupt stack // may modify, the interrupt will switch to the interrupt stack
@@ -992,14 +840,6 @@ func modifyStackSizes(executable string, stackSizeLoads []string, stackSizes map
// Some background: // Some background:
// https://interrupt.memfault.com/blog/cortex-m-rtos-context-switching // https://interrupt.memfault.com/blog/cortex-m-rtos-context-switching
stackSize += 32 stackSize += 32
// Adding 4 for the stack canary, and another 4 to keep the
// stack aligned. Even though the size may be automatically
// determined, stack overflow checking is still important as the
// stack size cannot be determined for all goroutines.
stackSize += 8
default:
return fmt.Errorf("unknown architecture: %s", fileHeader.Machine.String())
} }
// Finally write the stack size to the binary. // Finally write the stack size to the binary.
@@ -1007,7 +847,13 @@ func modifyStackSizes(executable string, stackSizeLoads []string, stackSizes map
} }
} }
return replaceElfSection(executable, ".tinygo_stacksizes", data) // Write back the modified stack sizes.
_, err = fp.WriteAt(data, int64(section.Offset))
if err != nil {
return err
}
return nil
} }
// printStacks prints the maximum stack depth for functions that are started as // printStacks prints the maximum stack depth for functions that are started as
@@ -1039,41 +885,3 @@ func printStacks(calculatedStacks []string, stackSizes map[string]functionStackS
} }
} }
} }
// RP2040 second stage bootloader CRC32 calculation
//
// Spec: https://datasheets.raspberrypi.org/rp2040/rp2040-datasheet.pdf
// Section: 2.8.1.3.1. Checksum
func patchRP2040BootCRC(executable string) error {
bytes, _, err := getElfSectionData(executable, ".boot2")
if err != nil {
return err
}
if len(bytes) != 256 {
return fmt.Errorf("rp2040 .boot2 section must be exactly 256 bytes")
}
// From the 'official' RP2040 checksum script:
//
// Our bootrom CRC32 is slightly bass-ackward but it's
// best to work around for now (FIXME)
// 100% worth it to save two Thumb instructions
revBytes := make([]byte, len(bytes))
for i := range bytes {
revBytes[i] = bits.Reverse8(bytes[i])
}
// crc32.Update does an initial negate and negates the
// result, so to meet RP2040 spec, pass 0x0 as initial
// hash and negate returned value.
//
// Note: checksum is over 252 bytes (256 - 4)
hash := bits.Reverse32(crc32.Update(0x0, crc32.IEEETable, revBytes[:252]) ^ 0xFFFFFFFF)
// Write the CRC to the end of the bootloader.
binary.LittleEndian.PutUint32(bytes[252:], hash)
// Update the .boot2 section to included the CRC
return replaceElfSection(executable, ".boot2", bytes)
}
+8 -16
View File
@@ -17,6 +17,7 @@ import (
"strings" "strings"
"unicode" "unicode"
"github.com/tinygo-org/tinygo/compileopts"
"github.com/tinygo-org/tinygo/goenv" "github.com/tinygo-org/tinygo/goenv"
"tinygo.org/x/go-llvm" "tinygo.org/x/go-llvm"
) )
@@ -56,7 +57,7 @@ import (
// depfile but without invalidating its name. For this reason, the depfile is // depfile but without invalidating its name. For this reason, the depfile is
// written on each new compilation (even when it seems unnecessary). However, it // written on each new compilation (even when it seems unnecessary). However, it
// could in rare cases lead to a stale file fetched from the cache. // could in rare cases lead to a stale file fetched from the cache.
func compileAndCacheCFile(abspath, tmpdir string, cflags []string, printCommands func(string, ...string)) (string, error) { func compileAndCacheCFile(abspath, tmpdir string, cflags []string, config *compileopts.Config) (string, error) {
// Hash input file. // Hash input file.
fileHash, err := hashFile(abspath) fileHash, err := hashFile(abspath)
if err != nil { if err != nil {
@@ -67,12 +68,14 @@ func compileAndCacheCFile(abspath, tmpdir string, cflags []string, printCommands
buf, err := json.Marshal(struct { buf, err := json.Marshal(struct {
Path string Path string
Hash string Hash string
Compiler string
Flags []string Flags []string
LLVMVersion string LLVMVersion string
}{ }{
Path: abspath, Path: abspath,
Hash: fileHash, Hash: fileHash,
Flags: cflags, Compiler: config.Target.Compiler,
Flags: config.CFlags(),
LLVMVersion: llvm.Version, LLVMVersion: llvm.Version,
}) })
if err != nil { if err != nil {
@@ -121,17 +124,10 @@ func compileAndCacheCFile(abspath, tmpdir string, cflags []string, printCommands
flags := append([]string{}, cflags...) // copy cflags flags := append([]string{}, cflags...) // copy cflags
flags = append(flags, "-MD", "-MV", "-MTdeps", "-MF", depTmpFile.Name()) // autogenerate dependencies flags = append(flags, "-MD", "-MV", "-MTdeps", "-MF", depTmpFile.Name()) // autogenerate dependencies
flags = append(flags, "-c", "-o", objTmpFile.Name(), abspath) flags = append(flags, "-c", "-o", objTmpFile.Name(), abspath)
if strings.ToLower(filepath.Ext(abspath)) == ".s" { if config.Options.PrintCommands {
// If this is an assembly file (.s or .S, lowercase or uppercase), then fmt.Printf("%s %s\n", config.Target.Compiler, strings.Join(flags, " "))
// we'll need to add -Qunused-arguments because many parameters are
// relevant to C, not assembly. And with -Werror, having meaningless
// flags (for the assembler) is a compiler error.
flags = append(flags, "-Qunused-arguments")
} }
if printCommands != nil { err = runCCompiler(config.Target.Compiler, flags...)
printCommands("clang", flags...)
}
err = runCCompiler(flags...)
if err != nil { if err != nil {
return "", &commandError{"failed to build", abspath, err} return "", &commandError{"failed to build", abspath, err}
} }
@@ -155,10 +151,6 @@ func compileAndCacheCFile(abspath, tmpdir string, cflags []string, printCommands
// Write dependencies file. // Write dependencies file.
f, err := ioutil.TempFile(filepath.Dir(depfileCachePath), depfileName) f, err := ioutil.TempFile(filepath.Dir(depfileCachePath), depfileName)
if err != nil {
return "", err
}
buf, err = json.MarshalIndent(dependencySlice, "", "\t") buf, err = json.MarshalIndent(dependencySlice, "", "\t")
if err != nil { if err != nil {
panic(err) // shouldn't happen panic(err) // shouldn't happen
+2 -12
View File
@@ -17,28 +17,18 @@ func NewConfig(options *compileopts.Options) (*compileopts.Config, error) {
if err != nil { if err != nil {
return nil, err return nil, err
} }
if options.OpenOCDCommands != nil {
// Override the OpenOCDCommands from the target spec if specified on
// the command-line
spec.OpenOCDCommands = options.OpenOCDCommands
}
goroot := goenv.Get("GOROOT") goroot := goenv.Get("GOROOT")
if goroot == "" { if goroot == "" {
return nil, errors.New("cannot locate $GOROOT, please set it manually") return nil, errors.New("cannot locate $GOROOT, please set it manually")
} }
major, minor, err := goenv.GetGorootVersion(goroot) major, minor, err := goenv.GetGorootVersion(goroot)
if err != nil { if err != nil {
return nil, fmt.Errorf("could not read version from GOROOT (%v): %v", goroot, err) return nil, fmt.Errorf("could not read version from GOROOT (%v): %v", goroot, err)
} }
if major != 1 || minor < 15 || minor > 17 { if major != 1 || minor < 13 || minor > 16 {
return nil, fmt.Errorf("requires go version 1.15 through 1.17, got go%d.%d", major, minor) return nil, fmt.Errorf("requires go version 1.13 through 1.16, got go%d.%d", major, minor)
} }
clangHeaderPath := getClangHeaderPath(goenv.Get("TINYGOROOT")) clangHeaderPath := getClangHeaderPath(goenv.Get("TINYGOROOT"))
return &compileopts.Config{ return &compileopts.Config{
Options: options, Options: options,
Target: spec, Target: spec,
-57
View File
@@ -1,57 +0,0 @@
package builder
import (
"debug/elf"
"fmt"
"os"
)
func getElfSectionData(executable string, sectionName string) ([]byte, elf.FileHeader, error) {
elfFile, err := elf.Open(executable)
if err != nil {
return nil, elf.FileHeader{}, err
}
defer elfFile.Close()
section := elfFile.Section(sectionName)
if section == nil {
return nil, elf.FileHeader{}, fmt.Errorf("could not find %s section", sectionName)
}
data, err := section.Data()
return data, elfFile.FileHeader, err
}
func replaceElfSection(executable string, sectionName string, data []byte) error {
fp, err := os.OpenFile(executable, os.O_RDWR, 0)
if err != nil {
return err
}
defer fp.Close()
elfFile, err := elf.Open(executable)
if err != nil {
return err
}
defer elfFile.Close()
section := elfFile.Section(sectionName)
if section == nil {
return fmt.Errorf("could not find %s section", sectionName)
}
// Implicitly check for compressed sections
if section.Size != section.FileSize {
return fmt.Errorf("expected section %s to have identical size and file size, got %d and %d", sectionName, section.Size, section.FileSize)
}
// Only permit complete replacement of section
if section.Size != uint64(len(data)) {
return fmt.Errorf("expected section %s to have size %d, was actually %d", sectionName, len(data), section.Size)
}
// Write the replacement section data
_, err = fp.WriteAt(data, int64(section.Offset))
return err
}
+6 -19
View File
@@ -78,21 +78,11 @@ func makeESPFirmareImage(infile, outfile, format string) error {
// An added benefit is that we don't need to check for errors all the time. // An added benefit is that we don't need to check for errors all the time.
outf := &bytes.Buffer{} outf := &bytes.Buffer{}
// Chip IDs. Source:
// https://github.com/espressif/esp-idf/blob/v4.3/components/bootloader_support/include/esp_app_format.h#L22
chip_id := map[string]uint16{
"esp32": 0x0000,
"esp32c3": 0x0005,
}[format]
// Image header. // Image header.
switch format { switch format {
case "esp32", "esp32c3": case "esp32":
// Header format: // Header format:
// https://github.com/espressif/esp-idf/blob/v4.3/components/bootloader_support/include/esp_app_format.h#L71 // https://github.com/espressif/esp-idf/blob/8fbb63c2/components/bootloader_support/include/esp_image_format.h#L58
// Note: not adding a SHA256 hash as the binary is modified by
// esptool.py while flashing and therefore the hash won't be valid
// anymore.
binary.Write(outf, binary.LittleEndian, struct { binary.Write(outf, binary.LittleEndian, struct {
magic uint8 magic uint8
segment_count uint8 segment_count uint8
@@ -101,18 +91,15 @@ func makeESPFirmareImage(infile, outfile, format string) error {
entry_addr uint32 entry_addr uint32
wp_pin uint8 wp_pin uint8
spi_pin_drv [3]uint8 spi_pin_drv [3]uint8
chip_id uint16 reserved [11]uint8
min_chip_rev uint8
reserved [8]uint8
hash_appended bool hash_appended bool
}{ }{
magic: 0xE9, magic: 0xE9,
segment_count: byte(len(segments)), segment_count: byte(len(segments)),
spi_mode: 2, // ESP_IMAGE_SPI_MODE_DIO spi_mode: 0, // irrelevant, replaced by esptool when flashing
spi_speed_size: 0x1f, // ESP_IMAGE_SPI_SPEED_80M, ESP_IMAGE_FLASH_SIZE_2MB spi_speed_size: 0, // spi_speed, spi_size: replaced by esptool when flashing
entry_addr: uint32(inf.Entry), entry_addr: uint32(inf.Entry),
wp_pin: 0xEE, // disable WP pin wp_pin: 0xEE, // disable WP pin
chip_id: chip_id,
hash_appended: true, // add a SHA256 hash hash_appended: true, // add a SHA256 hash
}) })
case "esp8266": case "esp8266":
@@ -155,7 +142,7 @@ func makeESPFirmareImage(infile, outfile, format string) error {
outf.Write(make([]byte, 15-outf.Len()%16)) outf.Write(make([]byte, 15-outf.Len()%16))
outf.WriteByte(checksum) outf.WriteByte(checksum)
if format != "esp8266" { if format == "esp32" {
// SHA256 hash (to protect against image corruption, not for security). // SHA256 hash (to protect against image corruption, not for security).
hash := sha256.Sum256(outf.Bytes()) hash := sha256.Sum256(outf.Bytes())
outf.Write(hash[:]) outf.Write(hash[:])
+6 -23
View File
@@ -65,29 +65,12 @@ func (job *compileJob) readyToRun() bool {
return true return true
} }
// runJobs runs the indicated job and all its dependencies. For every job, all // runJobs runs all the jobs indicated in the jobs slice and returns the error
// the dependencies are run first. It returns the error of the first job that // of the first job that fails to run.
// fails. // It runs all jobs in the order of the slice, as long as all dependencies have
// It runs all jobs in the order of the dependencies slice, depth-first. // already run. Therefore, if some jobs are preferred to run before others, they
// Therefore, if some jobs are preferred to run before others, they should be // should be ordered as such in this slice.
// ordered as such in the job dependencies. func runJobs(jobs []*compileJob) error {
func runJobs(job *compileJob) error {
// Create a slice of jobs to run, where all dependencies are run in order.
jobs := []*compileJob{}
addedJobs := map[*compileJob]struct{}{}
var addJobs func(*compileJob)
addJobs = func(job *compileJob) {
if _, ok := addedJobs[job]; ok {
return
}
for _, dep := range job.dependencies {
addJobs(dep)
}
jobs = append(jobs, job)
addedJobs[job] = struct{}{}
}
addJobs(job)
// Create channels to communicate with the workers. // Create channels to communicate with the workers.
doneChan := make(chan *compileJob) doneChan := make(chan *compileJob)
workerChan := make(chan *compileJob) workerChan := make(chan *compileJob)
+5 -4
View File
@@ -46,14 +46,15 @@ func (l *Library) Load(target, tmpdir string) (path string, err error) {
if err != nil { if err != nil {
return "", err return "", err
} }
err = runJobs(job) jobs := append([]*compileJob{job}, job.dependencies...)
err = runJobs(jobs)
return job.result, err return job.result, err
} }
// load returns a compile job to build this library file for the given target // load returns a compile job to build this library file for the given target
// and CPU. It may return a dummy compileJob if the library build is already // and CPU. It may return a dummy compileJob if the library build is already
// cached. The path is stored as job.result but is only valid after the job has // cached. The path is stored as job.result but is only valid if the job and
// been run. // job.dependencies have been run.
// The provided tmpdir will be used to store intermediary files and possibly the // The provided tmpdir will be used to store intermediary files and possibly the
// output archive file, it is expected to be removed after use. // output archive file, it is expected to be removed after use.
func (l *Library) load(target, cpu, tmpdir string) (job *compileJob, err error) { func (l *Library) load(target, cpu, tmpdir string) (job *compileJob, err error) {
@@ -135,7 +136,7 @@ func (l *Library) load(target, cpu, tmpdir string) (job *compileJob, err error)
var compileArgs []string var compileArgs []string
compileArgs = append(compileArgs, args...) compileArgs = append(compileArgs, args...)
compileArgs = append(compileArgs, "-o", objpath, srcpath) compileArgs = append(compileArgs, "-o", objpath, srcpath)
err := runCCompiler(compileArgs...) err := runCCompiler("clang", compileArgs...)
if err != nil { if err != nil {
return &commandError{"failed to build", srcpath, err} return &commandError{"failed to build", srcpath, err}
} }
-27
View File
@@ -1,27 +0,0 @@
package builder
import (
"fmt"
"io/ioutil"
"os/exec"
"github.com/tinygo-org/tinygo/compileopts"
)
// https://infocenter.nordicsemi.com/index.jsp?topic=%2Fug_nrfutil%2FUG%2Fnrfutil%2Fnrfutil_intro.html
func makeDFUFirmwareImage(options *compileopts.Options, infile, outfile string) error {
cmdLine := []string{"nrfutil", "pkg", "generate", "--hw-version", "52", "--sd-req", "0x0", "--debug-mode", "--application", infile, outfile}
if options.PrintCommands != nil {
options.PrintCommands(cmdLine[0], cmdLine[1:]...)
}
cmd := exec.Command(cmdLine[0], cmdLine[1:]...)
cmd.Stdout = ioutil.Discard
err := cmd.Run()
if err != nil {
return fmt.Errorf("could not run nrfutil pkg generate: %w", err)
}
return nil
}
+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.
+9
View File
@@ -12,6 +12,7 @@ import (
"go/types" "go/types"
"io/ioutil" "io/ioutil"
"path/filepath" "path/filepath"
"regexp"
"runtime" "runtime"
"strings" "strings"
"testing" "testing"
@@ -24,6 +25,14 @@ var flagUpdate = flag.Bool("update", false, "Update images based on test output.
// platforms and Go versions. // platforms and Go versions.
func normalizeResult(result string) string { func normalizeResult(result string) string {
actual := strings.ReplaceAll(result, "\r\n", "\n") actual := strings.ReplaceAll(result, "\r\n", "\n")
// Make sure all functions are wrapped, even those that would otherwise be
// single-line functions. This is necessary because Go 1.14 changed the way
// such functions are wrapped and it's important to have consistent test
// results.
re := regexp.MustCompile(`func \((.+)\)( .*?) +{ (.+) }`)
actual = re.ReplaceAllString(actual, "func ($1)$2 {\n\t$3\n}")
return actual return actual
} }
+74 -164
View File
@@ -11,184 +11,105 @@ import (
"strings" "strings"
) )
var (
prefixParseFns map[token.Token]func(*tokenizer) (ast.Expr, *scanner.Error)
precedences = map[token.Token]int{
token.ADD: precedenceAdd,
token.SUB: precedenceAdd,
token.MUL: precedenceMul,
token.QUO: precedenceMul,
token.REM: precedenceMul,
}
)
const (
precedenceLowest = iota + 1
precedenceAdd
precedenceMul
precedencePrefix
)
func init() {
// This must be done in an init function to avoid an initialization order
// failure.
prefixParseFns = map[token.Token]func(*tokenizer) (ast.Expr, *scanner.Error){
token.IDENT: parseIdent,
token.INT: parseBasicLit,
token.FLOAT: parseBasicLit,
token.STRING: parseBasicLit,
token.CHAR: parseBasicLit,
token.LPAREN: parseParenExpr,
token.SUB: parseUnaryExpr,
}
}
// parseConst parses the given string as a C constant. // parseConst parses the given string as a C constant.
func parseConst(pos token.Pos, fset *token.FileSet, value string) (ast.Expr, *scanner.Error) { func parseConst(pos token.Pos, fset *token.FileSet, value string) (ast.Expr, *scanner.Error) {
t := newTokenizer(pos, fset, value) t := newTokenizer(pos, fset, value)
expr, err := parseConstExpr(t, precedenceLowest) expr, err := parseConstExpr(t)
t.Next() if t.token != token.EOF {
if t.curToken != token.EOF {
return nil, &scanner.Error{ return nil, &scanner.Error{
Pos: t.fset.Position(t.curPos), Pos: t.fset.Position(t.pos),
Msg: "unexpected token " + t.curToken.String() + ", expected end of expression", Msg: "unexpected token " + t.token.String(),
} }
} }
return expr, err return expr, err
} }
// parseConstExpr parses a stream of C tokens to a Go expression. // parseConstExpr parses a stream of C tokens to a Go expression.
func parseConstExpr(t *tokenizer, precedence int) (ast.Expr, *scanner.Error) { func parseConstExpr(t *tokenizer) (ast.Expr, *scanner.Error) {
if t.curToken == token.EOF { switch t.token {
case token.LPAREN:
lparen := t.pos
t.Next()
x, err := parseConstExpr(t)
if err != nil {
return nil, err
}
if t.token != token.RPAREN {
return nil, unexpectedToken(t, token.RPAREN)
}
expr := &ast.ParenExpr{
Lparen: lparen,
X: x,
Rparen: t.pos,
}
t.Next()
return expr, nil
case token.INT, token.FLOAT, token.STRING, token.CHAR:
expr := &ast.BasicLit{
ValuePos: t.pos,
Kind: t.token,
Value: t.value,
}
t.Next()
return expr, nil
case token.IDENT:
expr := &ast.Ident{
NamePos: t.pos,
Name: "C." + t.value,
}
t.Next()
return expr, nil
case token.EOF:
return nil, &scanner.Error{ return nil, &scanner.Error{
Pos: t.fset.Position(t.curPos), Pos: t.fset.Position(t.pos),
Msg: "empty constant", Msg: "empty constant",
} }
} default:
prefix := prefixParseFns[t.curToken]
if prefix == nil {
return nil, &scanner.Error{ return nil, &scanner.Error{
Pos: t.fset.Position(t.curPos), Pos: t.fset.Position(t.pos),
Msg: fmt.Sprintf("unexpected token %s", t.curToken), Msg: fmt.Sprintf("unexpected token %s", t.token),
} }
} }
leftExpr, err := prefix(t)
for t.peekToken != token.EOF && precedence < precedences[t.peekToken] {
switch t.peekToken {
case token.ADD, token.SUB, token.MUL, token.QUO, token.REM:
t.Next()
leftExpr, err = parseBinaryExpr(t, leftExpr)
}
}
return leftExpr, err
}
func parseIdent(t *tokenizer) (ast.Expr, *scanner.Error) {
return &ast.Ident{
NamePos: t.curPos,
Name: "C." + t.curValue,
}, nil
}
func parseBasicLit(t *tokenizer) (ast.Expr, *scanner.Error) {
return &ast.BasicLit{
ValuePos: t.curPos,
Kind: t.curToken,
Value: t.curValue,
}, nil
}
func parseParenExpr(t *tokenizer) (ast.Expr, *scanner.Error) {
lparen := t.curPos
t.Next()
x, err := parseConstExpr(t, precedenceLowest)
if err != nil {
return nil, err
}
t.Next()
if t.curToken != token.RPAREN {
return nil, unexpectedToken(t, token.RPAREN)
}
expr := &ast.ParenExpr{
Lparen: lparen,
X: x,
Rparen: t.curPos,
}
return expr, nil
}
func parseBinaryExpr(t *tokenizer, left ast.Expr) (ast.Expr, *scanner.Error) {
expression := &ast.BinaryExpr{
X: left,
Op: t.curToken,
OpPos: t.curPos,
}
precedence := precedences[t.curToken]
t.Next()
right, err := parseConstExpr(t, precedence)
expression.Y = right
return expression, err
}
func parseUnaryExpr(t *tokenizer) (ast.Expr, *scanner.Error) {
expression := &ast.UnaryExpr{
OpPos: t.curPos,
Op: t.curToken,
}
t.Next()
x, err := parseConstExpr(t, precedencePrefix)
expression.X = x
return expression, err
} }
// unexpectedToken returns an error of the form "unexpected token FOO, expected // unexpectedToken returns an error of the form "unexpected token FOO, expected
// BAR". // BAR".
func unexpectedToken(t *tokenizer, expected token.Token) *scanner.Error { func unexpectedToken(t *tokenizer, expected token.Token) *scanner.Error {
return &scanner.Error{ return &scanner.Error{
Pos: t.fset.Position(t.curPos), Pos: t.fset.Position(t.pos),
Msg: fmt.Sprintf("unexpected token %s, expected %s", t.curToken, expected), Msg: fmt.Sprintf("unexpected token %s, expected %s", t.token, expected),
} }
} }
// tokenizer reads C source code and converts it to Go tokens. // tokenizer reads C source code and converts it to Go tokens.
type tokenizer struct { type tokenizer struct {
curPos, peekPos token.Pos pos token.Pos
fset *token.FileSet fset *token.FileSet
curToken, peekToken token.Token token token.Token
curValue, peekValue string value string
buf string buf string
} }
// newTokenizer initializes a new tokenizer, positioned at the first token in // newTokenizer initializes a new tokenizer, positioned at the first token in
// the string. // the string.
func newTokenizer(start token.Pos, fset *token.FileSet, buf string) *tokenizer { func newTokenizer(start token.Pos, fset *token.FileSet, buf string) *tokenizer {
t := &tokenizer{ t := &tokenizer{
peekPos: start, pos: start,
fset: fset, fset: fset,
buf: buf, buf: buf,
peekToken: token.ILLEGAL, token: token.ILLEGAL,
} }
// Parse the first two tokens (cur and peek). t.Next() // Parse the first token.
t.Next()
t.Next()
return t return t
} }
// Next consumes the next token in the stream. There is no return value, read // Next consumes the next token in the stream. There is no return value, read
// the next token from the pos, token and value properties. // the next token from the pos, token and value properties.
func (t *tokenizer) Next() { func (t *tokenizer) Next() {
// The previous peek is now the current token. t.pos += token.Pos(len(t.value))
t.curPos = t.peekPos
t.curToken = t.peekToken
t.curValue = t.peekValue
// Parse the next peek token.
t.peekPos += token.Pos(len(t.curValue))
for { for {
if len(t.buf) == 0 { if len(t.buf) == 0 {
t.peekToken = token.EOF t.token = token.EOF
return return
} }
c := t.buf[0] c := t.buf[0]
@@ -197,28 +118,17 @@ func (t *tokenizer) Next() {
// Skip whitespace. // Skip whitespace.
// Based on this source, not sure whether it represents C whitespace: // Based on this source, not sure whether it represents C whitespace:
// https://en.cppreference.com/w/cpp/string/byte/isspace // https://en.cppreference.com/w/cpp/string/byte/isspace
t.peekPos++ t.pos++
t.buf = t.buf[1:] t.buf = t.buf[1:]
case c == '(' || c == ')' || c == '+' || c == '-' || c == '*' || c == '/' || c == '%': case c == '(' || c == ')':
// Single-character tokens. // Single-character tokens.
// TODO: ++ (increment) and -- (decrement) operators.
switch c { switch c {
case '(': case '(':
t.peekToken = token.LPAREN t.token = token.LPAREN
case ')': case ')':
t.peekToken = token.RPAREN t.token = token.RPAREN
case '+':
t.peekToken = token.ADD
case '-':
t.peekToken = token.SUB
case '*':
t.peekToken = token.MUL
case '/':
t.peekToken = token.QUO
case '%':
t.peekToken = token.REM
} }
t.peekValue = t.buf[:1] t.value = t.buf[:1]
t.buf = t.buf[1:] t.buf = t.buf[1:]
return return
case c >= '0' && c <= '9': case c >= '0' && c <= '9':
@@ -236,17 +146,17 @@ func (t *tokenizer) Next() {
break break
} }
} }
t.peekValue = t.buf[:tokenLen] t.value = t.buf[:tokenLen]
t.buf = t.buf[tokenLen:] t.buf = t.buf[tokenLen:]
if hasDot { if hasDot {
// Integer constants are more complicated than this but this is // Integer constants are more complicated than this but this is
// a close approximation. // a close approximation.
// https://en.cppreference.com/w/cpp/language/integer_literal // https://en.cppreference.com/w/cpp/language/integer_literal
t.peekToken = token.FLOAT t.token = token.FLOAT
t.peekValue = strings.TrimRight(t.peekValue, "f") t.value = strings.TrimRight(t.value, "f")
} else { } else {
t.peekToken = token.INT t.token = token.INT
t.peekValue = strings.TrimRight(t.peekValue, "uUlL") t.value = strings.TrimRight(t.value, "uUlL")
} }
return return
case c >= 'A' && c <= 'Z' || c >= 'a' && c <= 'z' || c == '_': case c >= 'A' && c <= 'Z' || c >= 'a' && c <= 'z' || c == '_':
@@ -260,9 +170,9 @@ func (t *tokenizer) Next() {
break break
} }
} }
t.peekValue = t.buf[:tokenLen] t.value = t.buf[:tokenLen]
t.buf = t.buf[tokenLen:] t.buf = t.buf[tokenLen:]
t.peekToken = token.IDENT t.token = token.IDENT
return return
case c == '"': case c == '"':
// String constant. Find the first '"' character that is not // String constant. Find the first '"' character that is not
@@ -278,8 +188,8 @@ func (t *tokenizer) Next() {
escape = c == '\\' escape = c == '\\'
} }
} }
t.peekToken = token.STRING t.token = token.STRING
t.peekValue = t.buf[:tokenLen] t.value = t.buf[:tokenLen]
t.buf = t.buf[tokenLen:] t.buf = t.buf[tokenLen:]
return return
case c == '\'': case c == '\'':
@@ -296,12 +206,12 @@ func (t *tokenizer) Next() {
escape = c == '\\' escape = c == '\\'
} }
} }
t.peekToken = token.CHAR t.token = token.CHAR
t.peekValue = t.buf[:tokenLen] t.value = t.buf[:tokenLen]
t.buf = t.buf[tokenLen:] t.buf = t.buf[tokenLen:]
return return
default: default:
t.peekToken = token.ILLEGAL t.token = token.ILLEGAL
return return
} }
} }
+2 -19
View File
@@ -18,7 +18,7 @@ func TestParseConst(t *testing.T) {
{`(5)`, `(5)`}, {`(5)`, `(5)`},
{`(((5)))`, `(5)`}, {`(((5)))`, `(5)`},
{`)`, `error: 1:1: unexpected token )`}, {`)`, `error: 1:1: unexpected token )`},
{`5)`, `error: 1:2: unexpected token ), expected end of expression`}, {`5)`, `error: 1:2: unexpected token )`},
{" \t)", `error: 1:4: unexpected token )`}, {" \t)", `error: 1:4: unexpected token )`},
{`5.8f`, `5.8`}, {`5.8f`, `5.8`},
{`foo`, `C.foo`}, {`foo`, `C.foo`},
@@ -30,24 +30,7 @@ func TestParseConst(t *testing.T) {
{`'a'`, `'a'`}, {`'a'`, `'a'`},
{`0b10`, `0b10`}, {`0b10`, `0b10`},
{`0x1234_5678`, `0x1234_5678`}, {`0x1234_5678`, `0x1234_5678`},
{`5 5`, `error: 1:3: unexpected token INT, expected end of expression`}, // test for a bugfix {`5 5`, `error: 1:3: unexpected token INT`}, // test for a bugfix
// Binary operators.
{`5+5`, `5 + 5`},
{`5-5`, `5 - 5`},
{`5*5`, `5 * 5`},
{`5/5`, `5 / 5`},
{`5%5`, `5 % 5`},
{`(5/5)`, `(5 / 5)`},
{`1 - 2`, `1 - 2`},
{`1 - 2 + 3`, `1 - 2 + 3`},
{`1 - 2 * 3`, `1 - 2*3`},
{`(1 - 2) * 3`, `(1 - 2) * 3`},
{`1 * 2 - 3`, `1*2 - 3`},
{`1 * (2 - 3)`, `1 * (2 - 3)`},
// Unary operators.
{`-5`, `-5`},
{`-5-2`, `-5 - 2`},
{`5 - - 2`, `5 - -2`},
} { } {
fset := token.NewFileSet() fset := token.NewFileSet()
startPos := fset.AddFile("", -1, 1000).Pos(0) startPos := fset.AddFile("", -1, 1000).Pos(0)
+1 -1
View File
@@ -1,7 +1,7 @@
// CGo errors: // CGo errors:
// testdata/errors.go:4:2: warning: some warning // testdata/errors.go:4:2: warning: some warning
// testdata/errors.go:11:9: error: unknown type name 'someType' // testdata/errors.go:11:9: error: unknown type name 'someType'
// testdata/errors.go:13:23: unexpected token ), expected end of expression // 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: cannot use 2 << 10 (untyped int constant 2048) as uint8 value in variable declaration (overflows)
+15 -5
View File
@@ -67,7 +67,9 @@ type C.union3_t = C.union_1
type C.union_nested_t = C.union_3 type C.union_nested_t = C.union_3
type C.unionarray_t = struct{ arr [10]C.uchar } type C.unionarray_t = struct{ arr [10]C.uchar }
func (s *C.struct_4) bitfield_a() C.uchar { return s.__bitfield_1 & 0x1f } func (s *C.struct_4) bitfield_a() C.uchar {
return s.__bitfield_1 & 0x1f
}
func (s *C.struct_4) set_bitfield_a(value C.uchar) { func (s *C.struct_4) set_bitfield_a(value C.uchar) {
s.__bitfield_1 = s.__bitfield_1&^0x1f | value&0x1f<<0 s.__bitfield_1 = s.__bitfield_1&^0x1f | value&0x1f<<0
} }
@@ -103,9 +105,15 @@ type C.struct_type1 struct {
} }
type C.struct_type2 struct{ _type C.int } type C.struct_type2 struct{ _type C.int }
func (union *C.union_1) unionfield_i() *C.int { return (*C.int)(unsafe.Pointer(&union.$union)) } func (union *C.union_1) unionfield_i() *C.int {
func (union *C.union_1) unionfield_d() *float64 { return (*float64)(unsafe.Pointer(&union.$union)) } return (*C.int)(unsafe.Pointer(&union.$union))
func (union *C.union_1) unionfield_s() *C.short { return (*C.short)(unsafe.Pointer(&union.$union)) } }
func (union *C.union_1) unionfield_d() *float64 {
return (*float64)(unsafe.Pointer(&union.$union))
}
func (union *C.union_1) unionfield_s() *C.short {
return (*C.short)(unsafe.Pointer(&union.$union))
}
type C.union_1 struct{ $union uint64 } type C.union_1 struct{ $union uint64 }
@@ -130,7 +138,9 @@ func (union *C.union_3) unionfield_thing() *C.union3_t {
type C.union_3 struct{ $union [2]uint64 } type C.union_3 struct{ $union [2]uint64 }
func (union *C.union_union2d) unionfield_i() *C.int { return (*C.int)(unsafe.Pointer(&union.$union)) } func (union *C.union_union2d) unionfield_i() *C.int {
return (*C.int)(unsafe.Pointer(&union.$union))
}
func (union *C.union_union2d) unionfield_d() *[2]float64 { func (union *C.union_union2d) unionfield_d() *[2]float64 {
return (*[2]float64)(unsafe.Pointer(&union.$union)) return (*[2]float64)(unsafe.Pointer(&union.$union))
} }
+15 -64
View File
@@ -55,7 +55,7 @@ func (c *Config) GOARCH() string {
// BuildTags returns the complete list of build tags used during this build. // BuildTags returns the complete list of build tags used during this build.
func (c *Config) BuildTags() []string { func (c *Config) BuildTags() []string {
tags := append(c.Target.BuildTags, []string{"tinygo", "math_big_pure_go", "gc." + c.GC(), "scheduler." + c.Scheduler(), "serial." + c.Serial()}...) tags := append(c.Target.BuildTags, []string{"tinygo", "gc." + c.GC(), "scheduler." + c.Scheduler()}...)
for i := 1; i <= c.GoMinorVersion; i++ { for i := 1; i <= c.GoMinorVersion; i++ {
tags = append(tags, fmt.Sprintf("go1.%d", i)) tags = append(tags, fmt.Sprintf("go1.%d", i))
} }
@@ -80,7 +80,12 @@ func (c *Config) GC() string {
if c.Target.GC != "" { if c.Target.GC != "" {
return c.Target.GC return c.Target.GC
} }
return "conservative" for _, tag := range c.Target.BuildTags {
if tag == "baremetal" || tag == "wasm" {
return "conservative"
}
}
return "extalloc"
} }
// NeedsStackObjects returns true if the compiler should insert stack objects // NeedsStackObjects returns true if the compiler should insert stack objects
@@ -89,7 +94,7 @@ func (c *Config) NeedsStackObjects() bool {
switch c.GC() { switch c.GC() {
case "conservative", "extalloc": case "conservative", "extalloc":
for _, tag := range c.BuildTags() { for _, tag := range c.BuildTags() {
if tag == "tinygo.wasm" { if tag == "wasm" {
return true return true
} }
} }
@@ -113,39 +118,6 @@ func (c *Config) Scheduler() string {
return "coroutines" return "coroutines"
} }
// Serial returns the serial implementation for this build configuration: uart,
// usb (meaning USB-CDC), or none.
func (c *Config) Serial() string {
if c.Options.Serial != "" {
return c.Options.Serial
}
if c.Target.Serial != "" {
return c.Target.Serial
}
return "none"
}
// OptLevels returns the optimization level (0-2), size level (0-2), and inliner
// threshold as used in the LLVM optimization pipeline.
func (c *Config) OptLevels() (optLevel, sizeLevel int, inlinerThreshold uint) {
switch c.Options.Opt {
case "none", "0":
return 0, 0, 0 // -O0
case "1":
return 1, 0, 0 // -O1
case "2":
return 2, 0, 225 // -O2
case "s":
return 2, 1, 225 // -Os
case "z":
return 2, 2, 5 // -Oz, default
default:
// This is not shown to the user: valid choices are already checked as
// part of Options.Verify(). It is here as a sanity check.
panic("unknown optimization level: -opt=" + c.Options.Opt)
}
}
// FuncImplementation picks an appropriate func value implementation for the // FuncImplementation picks an appropriate func value implementation for the
// target. // target.
func (c *Config) FuncImplementation() string { func (c *Config) FuncImplementation() string {
@@ -188,19 +160,10 @@ func (c *Config) AutomaticStackSize() bool {
return false return false
} }
// RP2040BootPatch returns whether the RP2040 boot patch should be applied that
// calculates and patches in the checksum for the 2nd stage bootloader.
func (c *Config) RP2040BootPatch() bool {
if c.Target.RP2040BootPatch != nil {
return *c.Target.RP2040BootPatch
}
return false
}
// CFlags returns the flags to pass to the C compiler. This is necessary for CGo // CFlags returns the flags to pass to the C compiler. This is necessary for CGo
// preprocessing. // preprocessing.
func (c *Config) CFlags() []string { func (c *Config) CFlags() []string {
var cflags []string cflags := append([]string{}, c.Options.CFlags...)
for _, flag := range c.Target.CFlags { for _, flag := range c.Target.CFlags {
cflags = append(cflags, strings.ReplaceAll(flag, "{root}", goenv.Get("TINYGOROOT"))) cflags = append(cflags, strings.ReplaceAll(flag, "{root}", goenv.Get("TINYGOROOT")))
} }
@@ -209,8 +172,9 @@ func (c *Config) CFlags() []string {
cflags = append(cflags, "-nostdlibinc", "-Xclang", "-internal-isystem", "-Xclang", filepath.Join(root, "lib", "picolibc", "newlib", "libc", "include")) cflags = append(cflags, "-nostdlibinc", "-Xclang", "-internal-isystem", "-Xclang", filepath.Join(root, "lib", "picolibc", "newlib", "libc", "include"))
cflags = append(cflags, "-I"+filepath.Join(root, "lib/picolibc-include")) cflags = append(cflags, "-I"+filepath.Join(root, "lib/picolibc-include"))
} }
// Always emit debug information. It is optionally stripped at link time. if c.Debug() {
cflags = append(cflags, "-g") cflags = append(cflags, "-g")
}
return cflags return cflags
} }
@@ -220,7 +184,7 @@ func (c *Config) CFlags() []string {
func (c *Config) LDFlags() []string { func (c *Config) LDFlags() []string {
root := goenv.Get("TINYGOROOT") root := goenv.Get("TINYGOROOT")
// Merge and adjust LDFlags. // Merge and adjust LDFlags.
var ldflags []string ldflags := append([]string{}, c.Options.LDFlags...)
for _, flag := range c.Target.LDFlags { for _, flag := range c.Target.LDFlags {
ldflags = append(ldflags, strings.ReplaceAll(flag, "{root}", root)) ldflags = append(ldflags, strings.ReplaceAll(flag, "{root}", root))
} }
@@ -249,9 +213,8 @@ func (c *Config) VerifyIR() bool {
return c.Options.VerifyIR return c.Options.VerifyIR
} }
// Debug returns whether debug (DWARF) information should be retained by the // Debug returns whether to add debug symbols to the IR, for debugging with GDB
// linker. By default, debug information is retained but it can be removed with // and similar.
// the -no-debug flag.
func (c *Config) Debug() bool { func (c *Config) Debug() bool {
return c.Options.Debug return c.Options.Debug
} }
@@ -275,11 +238,6 @@ func (c *Config) BinaryFormat(ext string) string {
// More information: // More information:
// https://github.com/Microsoft/uf2 // https://github.com/Microsoft/uf2
return "uf2" return "uf2"
case ".zip":
if c.Target.BinaryFormat != "" {
return c.Target.BinaryFormat
}
return "zip"
default: default:
// Use the ELF format for unrecognized file formats. // Use the ELF format for unrecognized file formats.
return "elf" return "elf"
@@ -297,9 +255,6 @@ func (c *Config) Programmer() (method, openocdInterface string) {
case "openocd", "msd", "command": case "openocd", "msd", "command":
// The -programmer flag only specifies the flash method. // The -programmer flag only specifies the flash method.
return c.Options.Programmer, c.Target.OpenOCDInterface return c.Options.Programmer, c.Target.OpenOCDInterface
case "bmp":
// The -programmer flag only specifies the flash method.
return c.Options.Programmer, ""
default: default:
// The -programmer flag specifies something else, assume it specifies // The -programmer flag specifies something else, assume it specifies
// the OpenOCD interface name. // the OpenOCD interface name.
@@ -366,10 +321,6 @@ func (c *Config) WasmAbi() string {
return c.Target.WasmAbi return c.Target.WasmAbi
} }
func (c *Config) LLVMFeatures() string {
return c.Options.LLVMFeatures
}
type TestConfig struct { type TestConfig struct {
CompileTestBinary bool CompileTestBinary bool
// TODO: Filter the test functions to run, include verbose flag, etc // TODO: Filter the test functions to run, include verbose flag, etc
+18 -39
View File
@@ -2,43 +2,37 @@ package compileopts
import ( import (
"fmt" "fmt"
"regexp"
"strings" "strings"
) )
var ( var (
validGCOptions = []string{"none", "leaking", "extalloc", "conservative"} validGCOptions = []string{"none", "leaking", "extalloc", "conservative"}
validSchedulerOptions = []string{"none", "tasks", "coroutines"} validSchedulerOptions = []string{"none", "tasks", "coroutines"}
validSerialOptions = []string{"none", "uart", "usb"}
validPrintSizeOptions = []string{"none", "short", "full"} validPrintSizeOptions = []string{"none", "short", "full"}
validPanicStrategyOptions = []string{"print", "trap"} validPanicStrategyOptions = []string{"print", "trap"}
validOptOptions = []string{"none", "0", "1", "2", "s", "z"}
) )
// Options contains extra options to give to the compiler. These options are // Options contains extra options to give to the compiler. These options are
// usually passed from the command line. // usually passed from the command line.
type Options struct { type Options struct {
Target string Target string
Opt string Opt string
GC string GC string
PanicStrategy string PanicStrategy string
Scheduler string Scheduler string
Serial string PrintIR bool
PrintIR bool DumpSSA bool
DumpSSA bool VerifyIR bool
VerifyIR bool PrintCommands bool
PrintCommands func(cmd string, args ...string) Debug bool
Debug bool PrintSizes string
PrintSizes string PrintStacks bool
PrintAllocs *regexp.Regexp // regexp string CFlags []string
PrintStacks bool LDFlags []string
Tags string Tags string
WasmAbi string WasmAbi string
GlobalValues map[string]map[string]string // map[pkgpath]map[varname]value TestConfig TestConfig
TestConfig TestConfig Programmer string
Programmer string
OpenOCDCommands []string
LLVMFeatures string
} }
// Verify performs a validation on the given options, raising an error if options are not valid. // Verify performs a validation on the given options, raising an error if options are not valid.
@@ -61,15 +55,6 @@ func (o *Options) Verify() error {
} }
} }
if o.Serial != "" {
valid := isInArray(validSerialOptions, o.Serial)
if !valid {
return fmt.Errorf(`invalid serial option '%s': valid values are %s`,
o.Serial,
strings.Join(validSerialOptions, ", "))
}
}
if o.PrintSizes != "" { if o.PrintSizes != "" {
valid := isInArray(validPrintSizeOptions, o.PrintSizes) valid := isInArray(validPrintSizeOptions, o.PrintSizes)
if !valid { if !valid {
@@ -88,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
} }
+10 -19
View File
@@ -31,7 +31,7 @@ type TargetSpec struct {
BuildTags []string `json:"build-tags"` BuildTags []string `json:"build-tags"`
GC string `json:"gc"` GC string `json:"gc"`
Scheduler string `json:"scheduler"` Scheduler string `json:"scheduler"`
Serial string `json:"serial"` // which serial output to use (uart, usb, none) Compiler string `json:"compiler"`
Linker string `json:"linker"` Linker string `json:"linker"`
RTLib string `json:"rtlib"` // compiler runtime library (libgcc, compiler-rt) RTLib string `json:"rtlib"` // compiler runtime library (libgcc, compiler-rt)
Libc string `json:"libc"` Libc string `json:"libc"`
@@ -41,12 +41,10 @@ type TargetSpec struct {
LDFlags []string `json:"ldflags"` LDFlags []string `json:"ldflags"`
LinkerScript string `json:"linkerscript"` LinkerScript string `json:"linkerscript"`
ExtraFiles []string `json:"extra-files"` ExtraFiles []string `json:"extra-files"`
RP2040BootPatch *bool `json:"rp2040-boot-patch"` // Patch RP2040 2nd stage bootloader checksum
Emulator []string `json:"emulator" override:"copy"` // inherited Emulator must not be append Emulator []string `json:"emulator" override:"copy"` // inherited Emulator must not be append
FlashCommand string `json:"flash-command"` FlashCommand string `json:"flash-command"`
GDB []string `json:"gdb"` GDB []string `json:"gdb"`
PortReset string `json:"flash-1200-bps-reset"` PortReset string `json:"flash-1200-bps-reset"`
SerialPort []string `json:"serial-port"` // serial port IDs in the form "acm:vid:pid" or "usb:vid:pid"
FlashMethod string `json:"flash-method"` FlashMethod string `json:"flash-method"`
FlashVolume string `json:"msd-volume-name"` FlashVolume string `json:"msd-volume-name"`
FlashFilename string `json:"msd-firmware-name"` FlashFilename string `json:"msd-firmware-name"`
@@ -172,7 +170,6 @@ func LoadTarget(target string) (*TargetSpec, error) {
"386": "i386", "386": "i386",
"amd64": "x86_64", "amd64": "x86_64",
"arm64": "aarch64", "arm64": "aarch64",
"arm": "thumbv7",
}[goarch] }[goarch]
if llvmarch == "" { if llvmarch == "" {
llvmarch = goarch llvmarch = goarch
@@ -243,29 +240,23 @@ func defaultTarget(goos, goarch, triple string) (*TargetSpec, error) {
// No target spec available. Use the default one, useful on most systems // No target spec available. Use the default one, useful on most systems
// with a regular OS. // with a regular OS.
spec := TargetSpec{ spec := TargetSpec{
Triple: triple, Triple: triple,
GOOS: goos, GOOS: goos,
GOARCH: goarch, GOARCH: goarch,
BuildTags: []string{goos, goarch}, BuildTags: []string{goos, goarch},
Scheduler: "tasks", Compiler: "clang",
Linker: "cc", Linker: "cc",
DefaultStackSize: 1024 * 64, // 64kB CFlags: []string{"--target=" + triple},
CFlags: []string{"--target=" + triple}, GDB: []string{"gdb"},
GDB: []string{"gdb"}, PortReset: "false",
PortReset: "false",
}
if goarch == "386" {
spec.CPU = "pentium4"
} }
if goos == "darwin" { if goos == "darwin" {
spec.CFlags = append(spec.CFlags, "-isysroot", "/Library/Developer/CommandLineTools/SDKs/MacOSX.sdk")
spec.LDFlags = append(spec.LDFlags, "-Wl,-dead_strip") spec.LDFlags = append(spec.LDFlags, "-Wl,-dead_strip")
} else { } else {
spec.LDFlags = append(spec.LDFlags, "-no-pie", "-Wl,--gc-sections") // WARNING: clang < 5.0 requires -nopie spec.LDFlags = append(spec.LDFlags, "-no-pie", "-Wl,--gc-sections") // WARNING: clang < 5.0 requires -nopie
} }
if goarch != "wasm" { if goarch != "wasm" {
spec.ExtraFiles = append(spec.ExtraFiles, "src/runtime/gc_"+goarch+".S") spec.ExtraFiles = append(spec.ExtraFiles, "src/runtime/gc_"+goarch+".S")
spec.ExtraFiles = append(spec.ExtraFiles, "src/internal/task/task_stack_"+goarch+".S")
} }
if goarch != runtime.GOARCH { if goarch != runtime.GOARCH {
// Some educated guesses as to how to invoke helper programs. // Some educated guesses as to how to invoke helper programs.
-99
View File
@@ -1,99 +0,0 @@
package compiler
// This file defines alias functions for functions that are normally defined in
// Go assembly.
//
// The Go toolchain defines many performance critical functions in assembly
// instead of plain Go. This is a problem for TinyGo as it currently (as of
// august 2021) is not able to compile these assembly files and even if it
// could, it would not be able to make use of them for many targets that are
// supported by TinyGo (baremetal RISC-V, AVR, etc). Therefore, many of these
// functions are aliased to their generic Go implementation.
// This results in slower than possible implementations, but at least they are
// usable.
import "tinygo.org/x/go-llvm"
var stdlibAliases = map[string]string{
// crypto packages
"crypto/md5.block": "crypto/md5.blockGeneric",
"crypto/sha1.block": "crypto/sha1.blockGeneric",
"crypto/sha1.blockAMD64": "crypto/sha1.blockGeneric",
"crypto/sha256.block": "crypto/sha256.blockGeneric",
"crypto/sha512.blockAMD64": "crypto/sha512.blockGeneric",
// math package
"math.Asin": "math.asin",
"math.Asinh": "math.asinh",
"math.Acos": "math.acos",
"math.Acosh": "math.acosh",
"math.Atan": "math.atan",
"math.Atanh": "math.atanh",
"math.Atan2": "math.atan2",
"math.Cbrt": "math.cbrt",
"math.Ceil": "math.ceil",
"math.archCeil": "math.ceil",
"math.Cos": "math.cos",
"math.Cosh": "math.cosh",
"math.Erf": "math.erf",
"math.Erfc": "math.erfc",
"math.Exp": "math.exp",
"math.archExp": "math.exp",
"math.Expm1": "math.expm1",
"math.Exp2": "math.exp2",
"math.archExp2": "math.exp2",
"math.Floor": "math.floor",
"math.archFloor": "math.floor",
"math.Frexp": "math.frexp",
"math.Hypot": "math.hypot",
"math.archHypot": "math.hypot",
"math.Ldexp": "math.ldexp",
"math.Log": "math.log",
"math.archLog": "math.log",
"math.Log1p": "math.log1p",
"math.Log10": "math.log10",
"math.Log2": "math.log2",
"math.Max": "math.max",
"math.archMax": "math.max",
"math.Min": "math.min",
"math.archMin": "math.min",
"math.Mod": "math.mod",
"math.Modf": "math.modf",
"math.archModf": "math.modf",
"math.Pow": "math.pow",
"math.Remainder": "math.remainder",
"math.Sin": "math.sin",
"math.Sinh": "math.sinh",
"math.Sqrt": "math.sqrt",
"math.archSqrt": "math.sqrt",
"math.Tan": "math.tan",
"math.Tanh": "math.tanh",
"math.Trunc": "math.trunc",
"math.archTrunc": "math.trunc",
}
// createAlias implements the function (in the builder) as a call to the alias
// function.
func (b *builder) createAlias(alias llvm.Value) {
if b.Debug {
if b.fn.Syntax() != nil {
// Create debug info file if present.
b.difunc = b.attachDebugInfo(b.fn)
}
pos := b.program.Fset.Position(b.fn.Pos())
b.SetCurrentDebugLocation(uint(pos.Line), uint(pos.Column), b.difunc, llvm.Metadata{})
}
entryBlock := llvm.AddBasicBlock(b.llvmFn, "entry")
b.SetInsertPointAtEnd(entryBlock)
if b.llvmFn.Type() != alias.Type() {
b.addError(b.fn.Pos(), "alias function should have the same type as aliasee "+alias.Name())
b.CreateUnreachable()
return
}
result := b.CreateCall(alias, b.llvmFn.Params(), "")
if result.Type().TypeKind() == llvm.VoidTypeKind {
b.CreateRetVoid()
} else {
b.CreateRet(result)
}
}
-53
View File
@@ -101,55 +101,6 @@ func (b *builder) createSliceBoundsCheck(capacity, low, high, max llvm.Value, lo
b.createRuntimeAssert(outOfBounds, "slice", "slicePanic") b.createRuntimeAssert(outOfBounds, "slice", "slicePanic")
} }
// createSliceToArrayPointerCheck adds a check for slice-to-array pointer
// conversions. This conversion was added in Go 1.17. For details, see:
// https://tip.golang.org/ref/spec#Conversions_from_slice_to_array_pointer
func (b *builder) createSliceToArrayPointerCheck(sliceLen llvm.Value, arrayLen int64) {
// From the spec:
// > If the length of the slice is less than the length of the array, a
// > run-time panic occurs.
arrayLenValue := llvm.ConstInt(b.uintptrType, uint64(arrayLen), false)
isLess := b.CreateICmp(llvm.IntULT, sliceLen, arrayLenValue, "")
b.createRuntimeAssert(isLess, "slicetoarray", "sliceToArrayPointerPanic")
}
// createUnsafeSliceCheck inserts a runtime check used for unsafe.Slice. This
// function must panic if the ptr/len parameters are invalid.
func (b *builder) createUnsafeSliceCheck(ptr, len llvm.Value, lenType *types.Basic) {
// From the documentation of unsafe.Slice:
// > At run time, if len is negative, or if ptr is nil and len is not
// > zero, a run-time panic occurs.
// However, in practice, it is also necessary to check that the length is
// not too big that a GEP wouldn't be possible without wrapping the pointer.
// These two checks (non-negative and not too big) can be merged into one
// using an unsiged greater than.
// Make sure the len value is at least as big as a uintptr.
if len.Type().IntTypeWidth() < b.uintptrType.IntTypeWidth() {
if lenType.Info()&types.IsUnsigned != 0 {
len = b.CreateZExt(len, b.uintptrType, "")
} else {
len = b.CreateSExt(len, b.uintptrType, "")
}
}
// Determine the maximum slice size, and therefore the maximum value of the
// len parameter.
maxSize := b.maxSliceSize(ptr.Type().ElementType())
maxSizeValue := llvm.ConstInt(len.Type(), maxSize, false)
// Do the check. By using unsigned greater than for the length check, signed
// negative values are also checked (which are very large numbers when
// interpreted as signed values).
zero := llvm.ConstInt(len.Type(), 0, false)
lenOutOfBounds := b.CreateICmp(llvm.IntUGT, len, maxSizeValue, "")
ptrIsNil := b.CreateICmp(llvm.IntEQ, ptr, llvm.ConstNull(ptr.Type()), "")
lenIsNotZero := b.CreateICmp(llvm.IntNE, len, zero, "")
assert := b.CreateAnd(ptrIsNil, lenIsNotZero, "")
assert = b.CreateOr(assert, lenOutOfBounds, "")
b.createRuntimeAssert(assert, "unsafe.Slice", "unsafeSlicePanic")
}
// createChanBoundsCheck creates a bounds check before creating a new channel to // createChanBoundsCheck creates a bounds check before creating a new channel to
// check that the value is not too big for runtime.chanMake. // check that the value is not too big for runtime.chanMake.
func (b *builder) createChanBoundsCheck(elementSize uint64, bufSize llvm.Value, bufSizeType *types.Basic, pos token.Pos) { func (b *builder) createChanBoundsCheck(elementSize uint64, bufSize llvm.Value, bufSizeType *types.Basic, pos token.Pos) {
@@ -211,10 +162,6 @@ func (b *builder) createNilCheck(inst ssa.Value, ptr llvm.Value, blockPrefix str
case *ssa.Alloc: case *ssa.Alloc:
// An alloc is never nil. // An alloc is never nil.
return return
case *ssa.FreeVar:
// A free variable is allocated in a parent function and is thus never
// nil.
return
case *ssa.IndexAddr: case *ssa.IndexAddr:
// This pointer is the result of an index operation into a slice or // This pointer is the result of an index operation into a slice or
// array. Such slices/arrays are already bounds checked so the pointer // array. Such slices/arrays are already bounds checked so the pointer
+9 -28
View File
@@ -32,15 +32,8 @@ func (b *builder) createChanSend(instr *ssa.Send) {
// store value-to-send // store value-to-send
valueType := b.getLLVMType(instr.X.Type()) valueType := b.getLLVMType(instr.X.Type())
isZeroSize := b.targetData.TypeAllocSize(valueType) == 0 valueAlloca, valueAllocaCast, valueAllocaSize := b.createTemporaryAlloca(valueType, "chan.value")
var valueAlloca, valueAllocaCast, valueAllocaSize llvm.Value b.CreateStore(chanValue, valueAlloca)
if isZeroSize {
valueAlloca = llvm.ConstNull(llvm.PointerType(valueType, 0))
valueAllocaCast = llvm.ConstNull(b.i8ptrType)
} else {
valueAlloca, valueAllocaCast, valueAllocaSize = b.createTemporaryAlloca(valueType, "chan.value")
b.CreateStore(chanValue, valueAlloca)
}
// Allocate blockedlist buffer. // Allocate blockedlist buffer.
channelBlockedList := b.mod.GetTypeByName("runtime.channelBlockedList") channelBlockedList := b.mod.GetTypeByName("runtime.channelBlockedList")
@@ -53,9 +46,7 @@ func (b *builder) createChanSend(instr *ssa.Send) {
// This also works around a bug in CoroSplit, at least in LLVM 8: // This also works around a bug in CoroSplit, at least in LLVM 8:
// https://bugs.llvm.org/show_bug.cgi?id=41742 // https://bugs.llvm.org/show_bug.cgi?id=41742
b.emitLifetimeEnd(channelBlockedListAllocaCast, channelBlockedListAllocaSize) b.emitLifetimeEnd(channelBlockedListAllocaCast, channelBlockedListAllocaSize)
if !isZeroSize { b.emitLifetimeEnd(valueAllocaCast, valueAllocaSize)
b.emitLifetimeEnd(valueAllocaCast, valueAllocaSize)
}
} }
// createChanRecv emits a pseudo chan receive operation. It is lowered to the // createChanRecv emits a pseudo chan receive operation. It is lowered to the
@@ -65,14 +56,7 @@ func (b *builder) createChanRecv(unop *ssa.UnOp) llvm.Value {
ch := b.getValue(unop.X) ch := b.getValue(unop.X)
// Allocate memory to receive into. // Allocate memory to receive into.
isZeroSize := b.targetData.TypeAllocSize(valueType) == 0 valueAlloca, valueAllocaCast, valueAllocaSize := b.createTemporaryAlloca(valueType, "chan.value")
var valueAlloca, valueAllocaCast, valueAllocaSize llvm.Value
if isZeroSize {
valueAlloca = llvm.ConstNull(llvm.PointerType(valueType, 0))
valueAllocaCast = llvm.ConstNull(b.i8ptrType)
} else {
valueAlloca, valueAllocaCast, valueAllocaSize = b.createTemporaryAlloca(valueType, "chan.value")
}
// Allocate blockedlist buffer. // Allocate blockedlist buffer.
channelBlockedList := b.mod.GetTypeByName("runtime.channelBlockedList") channelBlockedList := b.mod.GetTypeByName("runtime.channelBlockedList")
@@ -80,14 +64,9 @@ func (b *builder) createChanRecv(unop *ssa.UnOp) llvm.Value {
// Do the receive. // Do the receive.
commaOk := b.createRuntimeCall("chanRecv", []llvm.Value{ch, valueAllocaCast, channelBlockedListAlloca}, "") commaOk := b.createRuntimeCall("chanRecv", []llvm.Value{ch, valueAllocaCast, channelBlockedListAlloca}, "")
var received llvm.Value received := b.CreateLoad(valueAlloca, "chan.received")
if isZeroSize {
received = llvm.ConstNull(valueType)
} else {
received = b.CreateLoad(valueAlloca, "chan.received")
b.emitLifetimeEnd(valueAllocaCast, valueAllocaSize)
}
b.emitLifetimeEnd(channelBlockedListAllocaCast, channelBlockedListAllocaSize) b.emitLifetimeEnd(channelBlockedListAllocaCast, channelBlockedListAllocaSize)
b.emitLifetimeEnd(valueAllocaCast, valueAllocaSize)
if unop.CommaOk { if unop.CommaOk {
tuple := llvm.Undef(b.ctx.StructType([]llvm.Type{valueType, b.ctx.Int1Type()}, false)) tuple := llvm.Undef(b.ctx.StructType([]llvm.Type{valueType, b.ctx.Int1Type()}, false))
@@ -137,6 +116,7 @@ func (b *builder) createSelect(expr *ssa.Select) llvm.Value {
// determine the receive buffer size and alignment. // determine the receive buffer size and alignment.
recvbufSize := uint64(0) recvbufSize := uint64(0)
recvbufAlign := 0 recvbufAlign := 0
hasReceives := false
var selectStates []llvm.Value var selectStates []llvm.Value
chanSelectStateType := b.getLLVMRuntimeType("chanSelectState") chanSelectStateType := b.getLLVMRuntimeType("chanSelectState")
for _, state := range expr.States { for _, state := range expr.States {
@@ -153,6 +133,7 @@ func (b *builder) createSelect(expr *ssa.Select) llvm.Value {
if align := b.targetData.ABITypeAlignment(llvmType); align > recvbufAlign { if align := b.targetData.ABITypeAlignment(llvmType); align > recvbufAlign {
recvbufAlign = align recvbufAlign = align
} }
hasReceives = true
case types.SendOnly: case types.SendOnly:
// Store this value in an alloca and put a pointer to this alloca // Store this value in an alloca and put a pointer to this alloca
// in the send state. // in the send state.
@@ -169,7 +150,7 @@ func (b *builder) createSelect(expr *ssa.Select) llvm.Value {
// Create a receive buffer, where the received value will be stored. // Create a receive buffer, where the received value will be stored.
recvbuf := llvm.Undef(b.i8ptrType) recvbuf := llvm.Undef(b.i8ptrType)
if recvbufSize != 0 { if hasReceives {
allocaType := llvm.ArrayType(b.ctx.Int8Type(), int(recvbufSize)) allocaType := llvm.ArrayType(b.ctx.Int8Type(), int(recvbufSize))
recvbufAlloca, _, _ := b.createTemporaryAlloca(allocaType, "select.recvbuf.alloca") recvbufAlloca, _, _ := b.createTemporaryAlloca(allocaType, "select.recvbuf.alloca")
recvbufAlloca.SetAlignment(recvbufAlign) recvbufAlloca.SetAlignment(recvbufAlign)
+74 -205
View File
@@ -23,7 +23,7 @@ import (
// Version of the compiler pacakge. Must be incremented each time the compiler // Version of the compiler pacakge. Must be incremented each time the compiler
// package changes in a way that affects the generated LLVM module. // package changes in a way that affects the generated LLVM module.
// This version is independent of the TinyGo version number. // This version is independent of the TinyGo version number.
const Version = 19 // last change: fix channel ops with zero values const Version = 6 // last change: fix issue 1304
func init() { func init() {
llvm.InitializeAllTargets() llvm.InitializeAllTargets()
@@ -59,7 +59,6 @@ type Config struct {
DefaultStackSize uint64 DefaultStackSize uint64
NeedsStackObjects bool NeedsStackObjects bool
Debug bool // Whether to emit debug information in the LLVM module. Debug bool // Whether to emit debug information in the LLVM module.
LLVMFeatures string
} }
// compilerContext contains function-independent data that should still be // compilerContext contains function-independent data that should still be
@@ -74,7 +73,6 @@ type compilerContext struct {
cu llvm.Metadata cu llvm.Metadata
difiles map[string]llvm.Metadata difiles map[string]llvm.Metadata
ditypes map[types.Type]llvm.Metadata ditypes map[types.Type]llvm.Metadata
llvmTypes map[types.Type]llvm.Type
machine llvm.TargetMachine machine llvm.TargetMachine
targetData llvm.TargetData targetData llvm.TargetData
intType llvm.Type intType llvm.Type
@@ -95,7 +93,6 @@ func newCompilerContext(moduleName string, machine llvm.TargetMachine, config *C
DumpSSA: dumpSSA, DumpSSA: dumpSSA,
difiles: make(map[string]llvm.Metadata), difiles: make(map[string]llvm.Metadata),
ditypes: make(map[types.Type]llvm.Metadata), ditypes: make(map[types.Type]llvm.Metadata),
llvmTypes: make(map[types.Type]llvm.Type),
machine: machine, machine: machine,
targetData: machine.CreateTargetData(), targetData: machine.CreateTargetData(),
astComments: map[string]*ast.CommentGroup{}, astComments: map[string]*ast.CommentGroup{},
@@ -188,12 +185,7 @@ func NewTargetMachine(config *Config) (llvm.TargetMachine, error) {
if err != nil { if err != nil {
return llvm.TargetMachine{}, err return llvm.TargetMachine{}, err
} }
features := strings.Join(config.Features, ",")
feat := config.Features
if len(config.LLVMFeatures) > 0 {
feat = append(feat, config.LLVMFeatures)
}
features := strings.Join(feat, ",")
var codeModel llvm.CodeModel var codeModel llvm.CodeModel
var relocationModel llvm.RelocMode var relocationModel llvm.RelocMode
@@ -306,7 +298,7 @@ func CompilePackage(moduleName string, pkg *loader.Package, ssaPkg *ssa.Package,
c.dibuilder.Finalize() c.dibuilder.Finalize()
} }
return c.mod, c.diagnostics return c.mod, nil
} }
// getLLVMRuntimeType obtains a named type from the runtime package and returns // getLLVMRuntimeType obtains a named type from the runtime package and returns
@@ -317,23 +309,10 @@ func (c *compilerContext) getLLVMRuntimeType(name string) llvm.Type {
return c.getLLVMType(typ) return c.getLLVMType(typ)
} }
// getLLVMType returns a LLVM type for a Go type. It doesn't recreate already // getLLVMType creates and returns a LLVM type for a Go type. In the case of
// created types. This is somewhat important for performance, but especially // named struct types (or Go types implemented as named LLVM structs such as
// important for named struct types (which should only be created once). // strings) it also creates it first if necessary.
func (c *compilerContext) getLLVMType(goType types.Type) llvm.Type { func (c *compilerContext) getLLVMType(goType types.Type) llvm.Type {
// Try to load the LLVM type from the cache.
if t, ok := c.llvmTypes[goType]; ok {
return t
}
// Not already created, so adding this type to the cache.
llvmType := c.makeLLVMType(goType)
c.llvmTypes[goType] = llvmType
return llvmType
}
// makeLLVMType creates a LLVM type for a Go type. Don't call this, use
// getLLVMType instead.
func (c *compilerContext) makeLLVMType(goType types.Type) llvm.Type {
switch typ := goType.(type) { switch typ := goType.(type) {
case *types.Array: case *types.Array:
elemType := c.getLLVMType(typ.Elem()) elemType := c.getLLVMType(typ.Elem())
@@ -382,10 +361,12 @@ func (c *compilerContext) makeLLVMType(goType types.Type) llvm.Type {
// LLVM. This is because it is otherwise impossible to create // LLVM. This is because it is otherwise impossible to create
// self-referencing types such as linked lists. // self-referencing types such as linked lists.
llvmName := typ.Obj().Pkg().Path() + "." + typ.Obj().Name() llvmName := typ.Obj().Pkg().Path() + "." + typ.Obj().Name()
llvmType := c.ctx.StructCreateNamed(llvmName) llvmType := c.mod.GetTypeByName(llvmName)
c.llvmTypes[goType] = llvmType // avoid infinite recursion if llvmType.IsNil() {
underlying := c.getLLVMType(st) llvmType = c.ctx.StructCreateNamed(llvmName)
llvmType.StructSetBody(underlying.StructElementTypes(), false) underlying := c.getLLVMType(st)
llvmType.StructSetBody(underlying.StructElementTypes(), false)
}
return llvmType return llvmType
} }
return c.getLLVMType(typ.Underlying()) return c.getLLVMType(typ.Underlying())
@@ -728,11 +709,11 @@ func (c *compilerContext) createPackage(irbuilder llvm.Builder, pkg *ssa.Package
member := pkg.Members[name] member := pkg.Members[name]
switch member := member.(type) { switch member := member.(type) {
case *ssa.Function: case *ssa.Function:
// Create the function definition.
b := newBuilder(c, irbuilder, member)
if member.Blocks == nil { if member.Blocks == nil {
continue // external function continue // external function
} }
// Create the function definition.
b := newBuilder(c, irbuilder, member)
b.createFunction() b.createFunction()
case *ssa.Type: case *ssa.Type:
if types.IsInterface(member.Type()) { if types.IsInterface(member.Type()) {
@@ -771,36 +752,10 @@ func (c *compilerContext) createPackage(irbuilder llvm.Builder, pkg *ssa.Package
case *ssa.Global: case *ssa.Global:
// Global variable. // Global variable.
info := c.getGlobalInfo(member) info := c.getGlobalInfo(member)
global := c.getGlobal(member)
if !info.extern { if !info.extern {
global := c.getGlobal(member)
global.SetInitializer(llvm.ConstNull(global.Type().ElementType())) global.SetInitializer(llvm.ConstNull(global.Type().ElementType()))
global.SetVisibility(llvm.HiddenVisibility) global.SetVisibility(llvm.HiddenVisibility)
if info.section != "" {
global.SetSection(info.section)
}
}
}
}
// Add forwarding functions for functions that would otherwise be
// implemented in assembly.
for _, name := range members {
member := pkg.Members[name]
switch member := member.(type) {
case *ssa.Function:
if member.Blocks != nil {
continue // external function
}
info := c.getFunctionInfo(member)
if aliasName, ok := stdlibAliases[info.linkName]; ok {
alias := c.mod.NamedFunction(aliasName)
if alias.IsNil() {
// Shouldn't happen, but perhaps best to just ignore.
// The error will be a link error, if there is an error.
continue
}
b := newBuilder(c, irbuilder, member)
b.createAlias(alias)
} }
} }
} }
@@ -826,9 +781,6 @@ func (b *builder) createFunction() {
b.llvmFn.SetVisibility(llvm.HiddenVisibility) b.llvmFn.SetVisibility(llvm.HiddenVisibility)
b.llvmFn.SetUnnamedAddr(true) b.llvmFn.SetUnnamedAddr(true)
} }
if b.info.section != "" {
b.llvmFn.SetSection(b.info.section)
}
if b.info.exported && strings.HasPrefix(b.Triple, "wasm") { if b.info.exported && strings.HasPrefix(b.Triple, "wasm") {
// Set the exported name. This is necessary for WebAssembly because // Set the exported name. This is necessary for WebAssembly because
// otherwise the function is not exported. // otherwise the function is not exported.
@@ -1012,61 +964,11 @@ func (b *builder) createFunction() {
} }
} }
// posser is an interface that's implemented by both ssa.Value and
// ssa.Instruction. It is implemented by everything that has a Pos() method,
// which is all that getPos() needs.
type posser interface {
Pos() token.Pos
}
// getPos returns position information for a ssa.Value or ssa.Instruction.
//
// Not all instructions have position information, especially when they're
// implicit (such as implicit casts or implicit returns at the end of a
// function). In these cases, it makes sense to try a bit harder to guess what
// the position really should be.
func getPos(val posser) token.Pos {
pos := val.Pos()
if pos != token.NoPos {
// Easy: position is known.
return pos
}
// No position information is known.
switch val := val.(type) {
case *ssa.MakeInterface:
return getPos(val.X)
case *ssa.MakeClosure:
return val.Fn.(*ssa.Function).Pos()
case *ssa.Return:
syntax := val.Parent().Syntax()
if syntax != nil {
// non-synthetic
return syntax.End()
}
return token.NoPos
case *ssa.FieldAddr:
return getPos(val.X)
case *ssa.IndexAddr:
return getPos(val.X)
case *ssa.Slice:
return getPos(val.X)
case *ssa.Store:
return getPos(val.Addr)
case *ssa.Extract:
return getPos(val.Tuple)
default:
// This is reachable, for example with *ssa.Const, *ssa.If, and
// *ssa.Jump. They might be implemented in some way in the future.
return token.NoPos
}
}
// createInstruction builds the LLVM IR equivalent instructions for the // createInstruction builds the LLVM IR equivalent instructions for the
// particular Go SSA instruction. // particular Go SSA instruction.
func (b *builder) createInstruction(instr ssa.Instruction) { func (b *builder) createInstruction(instr ssa.Instruction) {
if b.Debug { if b.Debug {
pos := b.program.Fset.Position(getPos(instr)) pos := b.program.Fset.Position(instr.Pos())
b.SetCurrentDebugLocation(uint(pos.Line), uint(pos.Column), b.difunc, llvm.Metadata{}) b.SetCurrentDebugLocation(uint(pos.Line), uint(pos.Column), b.difunc, llvm.Metadata{})
} }
@@ -1092,8 +994,52 @@ func (b *builder) createInstruction(instr ssa.Instruction) {
case *ssa.Defer: case *ssa.Defer:
b.createDefer(instr) b.createDefer(instr)
case *ssa.Go: case *ssa.Go:
// Get all function parameters to pass to the goroutine.
var params []llvm.Value
for _, param := range instr.Call.Args {
params = append(params, b.getValue(param))
}
// Start a new goroutine. // Start a new goroutine.
b.createGo(instr) if callee := instr.Call.StaticCallee(); callee != nil {
// Static callee is known. This makes it easier to start a new
// goroutine.
var context llvm.Value
switch value := instr.Call.Value.(type) {
case *ssa.Function:
// Goroutine call is regular function call. No context is necessary.
context = llvm.Undef(b.i8ptrType)
case *ssa.MakeClosure:
// A goroutine call on a func value, but the callee is trivial to find. For
// example: immediately applied functions.
funcValue := b.getValue(value)
context = b.extractFuncContext(funcValue)
default:
panic("StaticCallee returned an unexpected value")
}
params = append(params, context) // context parameter
b.createGoInstruction(b.getFunction(callee), params, "", callee.Pos())
} else if !instr.Call.IsInvoke() {
// This is a function pointer.
// At the moment, two extra params are passed to the newly started
// goroutine:
// * The function context, for closures.
// * The function pointer (for tasks).
funcPtr, context := b.decodeFuncValue(b.getValue(instr.Call.Value), instr.Call.Value.Type().Underlying().(*types.Signature))
params = append(params, context) // context parameter
switch b.Scheduler {
case "none", "coroutines":
// There are no additional parameters needed for the goroutine start operation.
case "tasks":
// Add the function pointer as a parameter to start the goroutine.
params = append(params, funcPtr)
default:
panic("unknown scheduler type")
}
b.createGoInstruction(funcPtr, params, b.fn.RelString(nil), instr.Pos())
} else {
b.addError(instr.Pos(), "todo: go on interface call")
}
case *ssa.If: case *ssa.If:
cond := b.getValue(instr.Cond) cond := b.getValue(instr.Cond)
block := instr.Block() block := instr.Block()
@@ -1306,38 +1252,6 @@ func (b *builder) createBuiltin(argTypes []types.Type, argValues []llvm.Value, c
case "ssa:wrapnilchk": case "ssa:wrapnilchk":
// TODO: do an actual nil check? // TODO: do an actual nil check?
return argValues[0], nil return argValues[0], nil
// Builtins from the unsafe package.
case "Add": // unsafe.Add
// This is basically just a GEP operation.
// Note: the pointer is always of type *i8.
ptr := argValues[0]
len := argValues[1]
return b.CreateGEP(ptr, []llvm.Value{len}, ""), nil
case "Slice": // unsafe.Slice
// This creates a slice from a pointer and a length.
// Note that the exception mentioned in the documentation (if the
// pointer and length are nil, the slice is also nil) is trivially
// already the case.
ptr := argValues[0]
len := argValues[1]
slice := llvm.Undef(b.ctx.StructType([]llvm.Type{
ptr.Type(),
b.uintptrType,
b.uintptrType,
}, false))
b.createUnsafeSliceCheck(ptr, len, argTypes[1].Underlying().(*types.Basic))
if len.Type().IntTypeWidth() < b.uintptrType.IntTypeWidth() {
// Too small, zero-extend len.
len = b.CreateZExt(len, b.uintptrType, "")
} else if len.Type().IntTypeWidth() > b.uintptrType.IntTypeWidth() {
// Too big, truncate len.
len = b.CreateTrunc(len, b.uintptrType, "")
}
slice = b.CreateInsertValue(slice, ptr, 0, "")
slice = b.CreateInsertValue(slice, len, 1, "")
slice = b.CreateInsertValue(slice, len, 2, "")
return slice, nil
default: default:
return llvm.Value{}, b.makeError(pos, "todo: builtin: "+callName) return llvm.Value{}, b.makeError(pos, "todo: builtin: "+callName)
} }
@@ -1366,11 +1280,6 @@ func (b *builder) createFunctionCall(instr *ssa.CallCommon) (llvm.Value, error)
return b.createMemoryCopyCall(fn, instr.Args) return b.createMemoryCopyCall(fn, instr.Args)
case name == "runtime.memzero": case name == "runtime.memzero":
return b.createMemoryZeroCall(instr.Args) return b.createMemoryZeroCall(instr.Args)
case name == "math.Ceil" || name == "math.Floor" || name == "math.Sqrt" || name == "math.Trunc":
result, ok := b.createMathOp(instr)
if ok {
return result, nil
}
case name == "device.Asm" || name == "device/arm.Asm" || name == "device/arm64.Asm" || name == "device/avr.Asm" || name == "device/riscv.Asm": case name == "device.Asm" || name == "device/arm.Asm" || name == "device/arm64.Asm" || name == "device/avr.Asm" || name == "device/riscv.Asm":
return b.createInlineAsm(instr.Args) return b.createInlineAsm(instr.Args)
case name == "device.AsmFull" || name == "device/arm.AsmFull" || name == "device/arm64.AsmFull" || name == "device/avr.AsmFull" || name == "device/riscv.AsmFull": case name == "device.AsmFull" || name == "device/arm.AsmFull" || name == "device/arm64.AsmFull" || name == "device/avr.AsmFull" || name == "device/riscv.AsmFull":
@@ -1383,8 +1292,6 @@ func (b *builder) createFunctionCall(instr *ssa.CallCommon) (llvm.Value, error)
return b.emitCSROperation(instr) return b.emitCSROperation(instr)
case strings.HasPrefix(name, "syscall.Syscall"): case strings.HasPrefix(name, "syscall.Syscall"):
return b.createSyscall(instr) return b.createSyscall(instr)
case strings.HasPrefix(name, "syscall.rawSyscallNoError"):
return b.createRawSyscallNoError(instr)
case strings.HasPrefix(name, "runtime/volatile.Load"): case strings.HasPrefix(name, "runtime/volatile.Load"):
return b.createVolatileLoad(instr) return b.createVolatileLoad(instr)
case strings.HasPrefix(name, "runtime/volatile.Store"): case strings.HasPrefix(name, "runtime/volatile.Store"):
@@ -1484,28 +1391,6 @@ func (b *builder) getValue(expr ssa.Value) llvm.Value {
} }
} }
// maxSliceSize determines the maximum size a slice of the given element type
// can be.
func (c *compilerContext) maxSliceSize(elementType llvm.Type) uint64 {
// Calculate ^uintptr(0), which is the max value that fits in uintptr.
maxPointerValue := llvm.ConstNot(llvm.ConstInt(c.uintptrType, 0, false)).ZExtValue()
// Calculate (^uint(0))/2, which is the max value that fits in an int.
maxIntegerValue := llvm.ConstNot(llvm.ConstInt(c.intType, 0, false)).ZExtValue() / 2
// Determine the maximum allowed size for a slice. The biggest possible
// pointer (starting from 0) would be maxPointerValue*sizeof(elementType) so
// divide by the element type to get the real maximum size.
maxSize := maxPointerValue / c.targetData.TypeAllocSize(elementType)
// len(slice) is an int. Make sure the length remains small enough to fit in
// an int.
if maxSize > maxIntegerValue {
maxSize = maxIntegerValue
}
return maxSize
}
// createExpr translates a Go SSA expression to LLVM IR. This can be zero, one, // createExpr translates a Go SSA expression to LLVM IR. This can be zero, one,
// or multiple LLVM IR instructions and/or runtime calls. // or multiple LLVM IR instructions and/or runtime calls.
func (b *builder) createExpr(expr ssa.Value) (llvm.Value, error) { func (b *builder) createExpr(expr ssa.Value) (llvm.Value, error) {
@@ -1719,8 +1604,10 @@ func (b *builder) createExpr(expr ssa.Value) (llvm.Value, error) {
elemSize := b.targetData.TypeAllocSize(llvmElemType) elemSize := b.targetData.TypeAllocSize(llvmElemType)
elemSizeValue := llvm.ConstInt(b.uintptrType, elemSize, false) elemSizeValue := llvm.ConstInt(b.uintptrType, elemSize, false)
maxSize := b.maxSliceSize(llvmElemType) // Calculate (^uintptr(0)) >> 1, which is the max value that fits in
if elemSize > maxSize { // uintptr if uintptr were signed.
maxSize := llvm.ConstLShr(llvm.ConstNot(llvm.ConstInt(b.uintptrType, 0, false)), llvm.ConstInt(b.uintptrType, 1, false))
if elemSize > maxSize.ZExtValue() {
// This seems to be checked by the typechecker already, but let's // This seems to be checked by the typechecker already, but let's
// check it again just to be sure. // check it again just to be sure.
return llvm.Value{}, b.makeError(expr.Pos(), fmt.Sprintf("slice element type is too big (%v bytes)", elemSize)) return llvm.Value{}, b.makeError(expr.Pos(), fmt.Sprintf("slice element type is too big (%v bytes)", elemSize))
@@ -1729,8 +1616,7 @@ func (b *builder) createExpr(expr ssa.Value) (llvm.Value, error) {
// Bounds checking. // Bounds checking.
lenType := expr.Len.Type().Underlying().(*types.Basic) lenType := expr.Len.Type().Underlying().(*types.Basic)
capType := expr.Cap.Type().Underlying().(*types.Basic) capType := expr.Cap.Type().Underlying().(*types.Basic)
maxSizeValue := llvm.ConstInt(b.uintptrType, maxSize, false) b.createSliceBoundsCheck(maxSize, sliceLen, sliceCap, sliceCap, lenType, capType, capType)
b.createSliceBoundsCheck(maxSizeValue, sliceLen, sliceCap, sliceCap, lenType, capType, capType)
// Allocate the backing array. // Allocate the backing array.
sliceCapCast, err := b.createConvert(expr.Cap.Type(), types.Typ[types.Uintptr], sliceCap, expr.Pos()) sliceCapCast, err := b.createConvert(expr.Cap.Type(), types.Typ[types.Uintptr], sliceCap, expr.Pos())
@@ -1973,17 +1859,6 @@ func (b *builder) createExpr(expr ssa.Value) (llvm.Value, error) {
default: default:
return llvm.Value{}, b.makeError(expr.Pos(), "unknown slice type: "+typ.String()) return llvm.Value{}, b.makeError(expr.Pos(), "unknown slice type: "+typ.String())
} }
case *ssa.SliceToArrayPointer:
// Conversion from a slice to an array pointer, as the name clearly
// says. This requires a runtime check to make sure the slice is at
// least as big as the array.
slice := b.getValue(expr.X)
sliceLen := b.CreateExtractValue(slice, 1, "")
arrayLen := expr.Type().Underlying().(*types.Pointer).Elem().Underlying().(*types.Array).Len()
b.createSliceToArrayPointerCheck(sliceLen, arrayLen)
ptr := b.CreateExtractValue(slice, 0, "")
ptr = b.CreateBitCast(ptr, b.getLLVMType(expr.Type()), "")
return ptr, nil
case *ssa.TypeAssert: case *ssa.TypeAssert:
return b.createTypeAssert(expr), nil return b.createTypeAssert(expr), nil
case *ssa.UnOp: case *ssa.UnOp:
@@ -2383,20 +2258,14 @@ func (b *builder) createConst(prefix string, expr *ssa.Const) llvm.Value {
} else if typ.Info()&types.IsString != 0 { } else if typ.Info()&types.IsString != 0 {
str := constant.StringVal(expr.Value) str := constant.StringVal(expr.Value)
strLen := llvm.ConstInt(b.uintptrType, uint64(len(str)), false) strLen := llvm.ConstInt(b.uintptrType, uint64(len(str)), false)
var strPtr llvm.Value objname := prefix + "$string"
if str != "" { global := llvm.AddGlobal(b.mod, llvm.ArrayType(b.ctx.Int8Type(), len(str)), objname)
objname := prefix + "$string" global.SetInitializer(b.ctx.ConstString(str, false))
global := llvm.AddGlobal(b.mod, llvm.ArrayType(b.ctx.Int8Type(), len(str)), objname) global.SetLinkage(llvm.InternalLinkage)
global.SetInitializer(b.ctx.ConstString(str, false)) global.SetGlobalConstant(true)
global.SetLinkage(llvm.InternalLinkage) global.SetUnnamedAddr(true)
global.SetGlobalConstant(true) zero := llvm.ConstInt(b.ctx.Int32Type(), 0, false)
global.SetUnnamedAddr(true) strPtr := b.CreateInBoundsGEP(global, []llvm.Value{zero, zero}, "")
global.SetAlignment(1)
zero := llvm.ConstInt(b.ctx.Int32Type(), 0, false)
strPtr = b.CreateInBoundsGEP(global, []llvm.Value{zero, zero}, "")
} else {
strPtr = llvm.ConstNull(b.i8ptrType)
}
strObj := llvm.ConstNamedStruct(b.getLLVMRuntimeType("_string"), []llvm.Value{strPtr, strLen}) strObj := llvm.ConstNamedStruct(b.getLLVMRuntimeType("_string"), []llvm.Value{strPtr, strLen})
return strObj return strObj
} else if typ.Kind() == types.UnsafePointer { } else if typ.Kind() == types.UnsafePointer {
+36 -75
View File
@@ -9,7 +9,6 @@ import (
"testing" "testing"
"github.com/tinygo-org/tinygo/compileopts" "github.com/tinygo-org/tinygo/compileopts"
"github.com/tinygo-org/tinygo/goenv"
"github.com/tinygo-org/tinygo/loader" "github.com/tinygo-org/tinygo/loader"
"tinygo.org/x/go-llvm" "tinygo.org/x/go-llvm"
) )
@@ -17,16 +16,9 @@ import (
// Pass -update to go test to update the output of the test files. // Pass -update to go test to update the output of the test files.
var flagUpdate = flag.Bool("update", false, "update tests based on test output") var flagUpdate = flag.Bool("update", false, "update tests based on test output")
type testCase struct {
file string
target string
}
// Basic tests for the compiler. Build some Go files and compare the output with // Basic tests for the compiler. Build some Go files and compare the output with
// the expected LLVM IR for regression testing. // the expected LLVM IR for regression testing.
func TestCompiler(t *testing.T) { func TestCompiler(t *testing.T) {
t.Parallel()
// Check LLVM version. // Check LLVM version.
llvmMajor, err := strconv.Atoi(strings.SplitN(llvm.Version, ".", 2)[0]) llvmMajor, err := strconv.Atoi(strings.SplitN(llvm.Version, ".", 2)[0])
if err != nil { if err != nil {
@@ -40,64 +32,42 @@ func TestCompiler(t *testing.T) {
t.Skip("compiler tests require LLVM 11 or above, got LLVM ", llvm.Version) t.Skip("compiler tests require LLVM 11 or above, got LLVM ", llvm.Version)
} }
tests := []testCase{ target, err := compileopts.LoadTarget("i686--linux")
{"basic.go", ""},
{"pointer.go", ""},
{"slice.go", ""},
{"string.go", ""},
{"float.go", ""},
{"interface.go", ""},
{"func.go", ""},
{"pragma.go", ""},
{"goroutine.go", "wasm"},
{"goroutine.go", "cortex-m-qemu"},
{"channel.go", ""},
{"intrinsics.go", "cortex-m-qemu"},
{"intrinsics.go", "wasm"},
}
_, minor, err := goenv.GetGorootVersion(goenv.Get("GOROOT"))
if err != nil { if err != nil {
t.Fatal("could not read Go version:", err) t.Fatal("failed to load target:", err)
} }
if minor >= 17 { config := &compileopts.Config{
tests = append(tests, testCase{"go1.17.go", ""}) Options: &compileopts.Options{},
Target: target,
}
compilerConfig := &Config{
Triple: config.Triple(),
GOOS: config.GOOS(),
GOARCH: config.GOARCH(),
CodeModel: config.CodeModel(),
RelocationModel: config.RelocationModel(),
Scheduler: config.Scheduler(),
FuncImplementation: config.FuncImplementation(),
AutomaticStackSize: config.AutomaticStackSize(),
}
machine, err := NewTargetMachine(compilerConfig)
if err != nil {
t.Fatal("failed to create target machine:", err)
} }
for _, tc := range tests { tests := []string{
name := tc.file "basic.go",
targetString := "wasm" "pointer.go",
if tc.target != "" { "slice.go",
targetString = tc.target "string.go",
name = tc.file + "-" + tc.target "float.go",
} "interface.go",
}
t.Run(name, func(t *testing.T) {
target, err := compileopts.LoadTarget(targetString)
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)
}
for _, testCase := range tests {
t.Run(testCase, func(t *testing.T) {
// Load entire program AST into memory. // Load entire program AST into memory.
lprogram, err := loader.Load(config, []string{"./testdata/" + tc.file}, config.ClangHeaders, types.Config{ lprogram, err := loader.Load(config, []string{"./testdata/" + testCase}, config.ClangHeaders, types.Config{
Sizes: Sizes(machine), Sizes: Sizes(machine),
}) })
if err != nil { if err != nil {
@@ -105,25 +75,20 @@ func TestCompiler(t *testing.T) {
} }
err = lprogram.Parse() err = lprogram.Parse()
if err != nil { if err != nil {
t.Fatalf("could not parse test case %s: %s", tc.file, err) t.Fatalf("could not parse test case %s: %s", testCase, err)
} }
// Compile AST to IR. // Compile AST to IR.
program := lprogram.LoadSSA() program := lprogram.LoadSSA()
pkg := lprogram.MainPkg() pkg := lprogram.MainPkg()
mod, errs := CompilePackage(tc.file, pkg, program.Package(pkg.Pkg), machine, compilerConfig, false) mod, errs := CompilePackage(testCase, pkg, program.Package(pkg.Pkg), machine, compilerConfig, false)
if errs != nil { if errs != nil {
for _, err := range errs { for _, err := range errs {
t.Error(err) t.Log("error:", err)
} }
return return
} }
err = llvm.VerifyModule(mod, llvm.PrintMessageAction)
if err != nil {
t.Error(err)
}
// Optimize IR a little. // Optimize IR a little.
funcPasses := llvm.NewFunctionPassManagerForModule(mod) funcPasses := llvm.NewFunctionPassManagerForModule(mod)
defer funcPasses.Dispose() defer funcPasses.Dispose()
@@ -134,22 +99,18 @@ func TestCompiler(t *testing.T) {
} }
funcPasses.FinalizeFunc() funcPasses.FinalizeFunc()
outFilePrefix := tc.file[:len(tc.file)-3] outfile := "./testdata/" + testCase[:len(testCase)-3] + ".ll"
if tc.target != "" {
outFilePrefix += "-" + tc.target
}
outPath := "./testdata/" + outFilePrefix + ".ll"
// Update test if needed. Do not check the result. // Update test if needed. Do not check the result.
if *flagUpdate { if *flagUpdate {
err := ioutil.WriteFile(outPath, []byte(mod.String()), 0666) err := ioutil.WriteFile(outfile, []byte(mod.String()), 0666)
if err != nil { if err != nil {
t.Error("failed to write updated output file:", err) t.Error("failed to write updated output file:", err)
} }
return return
} }
expected, err := ioutil.ReadFile(outPath) expected, err := ioutil.ReadFile(outfile)
if err != nil { if err != nil {
t.Fatal("failed to read golden file:", err) t.Fatal("failed to read golden file:", err)
} }
+3 -15
View File
@@ -25,13 +25,14 @@ func (c *compilerContext) createFuncValue(builder llvm.Builder, funcPtr, context
// Closure is: {context, function pointer} // Closure is: {context, function pointer}
funcValueScalar = funcPtr funcValueScalar = funcPtr
case "switch": case "switch":
sigGlobal := c.getTypeCode(sig)
funcValueWithSignatureGlobalName := funcPtr.Name() + "$withSignature" funcValueWithSignatureGlobalName := funcPtr.Name() + "$withSignature"
funcValueWithSignatureGlobal := c.mod.NamedGlobal(funcValueWithSignatureGlobalName) funcValueWithSignatureGlobal := c.mod.NamedGlobal(funcValueWithSignatureGlobalName)
if funcValueWithSignatureGlobal.IsNil() { if funcValueWithSignatureGlobal.IsNil() {
funcValueWithSignatureType := c.getLLVMRuntimeType("funcValueWithSignature") funcValueWithSignatureType := c.getLLVMRuntimeType("funcValueWithSignature")
funcValueWithSignature := llvm.ConstNamedStruct(funcValueWithSignatureType, []llvm.Value{ funcValueWithSignature := llvm.ConstNamedStruct(funcValueWithSignatureType, []llvm.Value{
llvm.ConstPtrToInt(funcPtr, c.uintptrType), llvm.ConstPtrToInt(funcPtr, c.uintptrType),
c.getFuncSignatureID(sig), sigGlobal,
}) })
funcValueWithSignatureGlobal = llvm.AddGlobal(c.mod, funcValueWithSignatureType, funcValueWithSignatureGlobalName) funcValueWithSignatureGlobal = llvm.AddGlobal(c.mod, funcValueWithSignatureType, funcValueWithSignatureGlobalName)
funcValueWithSignatureGlobal.SetInitializer(funcValueWithSignature) funcValueWithSignatureGlobal.SetInitializer(funcValueWithSignature)
@@ -49,19 +50,6 @@ func (c *compilerContext) createFuncValue(builder llvm.Builder, funcPtr, context
return funcValue return funcValue
} }
// getFuncSignatureID returns a new external global for a given signature. This
// global reference is not real, it is only used during func lowering to assign
// signature types to functions and will then be removed.
func (c *compilerContext) getFuncSignatureID(sig *types.Signature) llvm.Value {
sigGlobalName := "reflect/types.funcid:" + getTypeCodeName(sig)
sigGlobal := c.mod.NamedGlobal(sigGlobalName)
if sigGlobal.IsNil() {
sigGlobal = llvm.AddGlobal(c.mod, c.ctx.Int8Type(), sigGlobalName)
sigGlobal.SetGlobalConstant(true)
}
return sigGlobal
}
// extractFuncScalar returns some scalar that can be used in comparisons. It is // extractFuncScalar returns some scalar that can be used in comparisons. It is
// a cheap operation. // a cheap operation.
func (b *builder) extractFuncScalar(funcValue llvm.Value) llvm.Value { func (b *builder) extractFuncScalar(funcValue llvm.Value) llvm.Value {
@@ -83,7 +71,7 @@ func (b *builder) decodeFuncValue(funcValue llvm.Value, sig *types.Signature) (f
funcPtr = b.CreateExtractValue(funcValue, 1, "") funcPtr = b.CreateExtractValue(funcValue, 1, "")
case "switch": case "switch":
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:
+15 -117
View File
@@ -5,110 +5,25 @@ package compiler
import ( import (
"go/token" "go/token"
"go/types"
"github.com/tinygo-org/tinygo/compiler/llvmutil" "github.com/tinygo-org/tinygo/compiler/llvmutil"
"golang.org/x/tools/go/ssa" "golang.org/x/tools/go/ssa"
"tinygo.org/x/go-llvm" "tinygo.org/x/go-llvm"
) )
// createGo emits code to start a new goroutine. // createGoInstruction starts a new goroutine with the provided function pointer
func (b *builder) createGo(instr *ssa.Go) { // and parameters.
// Get all function parameters to pass to the goroutine. // In general, you should pass all regular parameters plus the context parameter.
var params []llvm.Value // There is one exception: the task-based scheduler needs to have the function
for _, param := range instr.Call.Args { // pointer passed in as a parameter too in addition to the context.
params = append(params, b.getValue(param)) //
} // Because a go statement doesn't return anything, return undef.
func (b *builder) createGoInstruction(funcPtr llvm.Value, params []llvm.Value, prefix string, pos token.Pos) llvm.Value {
var prefix string
var funcPtr llvm.Value
hasContext := false
if callee := instr.Call.StaticCallee(); callee != nil {
// Static callee is known. This makes it easier to start a new
// goroutine.
var context llvm.Value
switch value := instr.Call.Value.(type) {
case *ssa.Function:
// Goroutine call is regular function call. No context is necessary.
if b.Scheduler == "coroutines" {
// The context parameter is assumed to be always present in the
// coroutines scheduler.
context = llvm.Undef(b.i8ptrType)
}
case *ssa.MakeClosure:
// A goroutine call on a func value, but the callee is trivial to find. For
// example: immediately applied functions.
funcValue := b.getValue(value)
context = b.extractFuncContext(funcValue)
default:
panic("StaticCallee returned an unexpected value")
}
if !context.IsNil() {
params = append(params, context) // context parameter
hasContext = true
}
funcPtr = b.getFunction(callee)
} else if builtin, ok := instr.Call.Value.(*ssa.Builtin); ok {
// We cheat. None of the builtins do any long or blocking operation, so
// we might as well run these builtins right away without the program
// noticing the difference.
// Possible exceptions:
// - copy: this is a possibly long operation, but not a blocking
// operation. Semantically it makes no difference to run it right
// away (not in a goroutine). However, in practice it makes no sense
// to run copy in a goroutine as there is no way to (safely) know
// when it is finished.
// - panic: the error message would appear in the parent goroutine.
// But because `go panic("err")` would halt the program anyway
// (there is no recover), panicking right away would give the same
// behavior as creating a goroutine, switching the scheduler to that
// goroutine, and panicking there. So this optimization seems
// correct.
// - recover: because it runs in a new goroutine, it is never a
// deferred function. Thus this is a no-op.
if builtin.Name() == "recover" {
// This is a no-op, even in a deferred function:
// go recover()
return
}
var argTypes []types.Type
var argValues []llvm.Value
for _, arg := range instr.Call.Args {
argTypes = append(argTypes, arg.Type())
argValues = append(argValues, b.getValue(arg))
}
b.createBuiltin(argTypes, argValues, builtin.Name(), instr.Pos())
return
} else if !instr.Call.IsInvoke() {
// This is a function pointer.
// At the moment, two extra params are passed to the newly started
// goroutine:
// * The function context, for closures.
// * The function pointer (for tasks).
var context llvm.Value
funcPtr, context = b.decodeFuncValue(b.getValue(instr.Call.Value), instr.Call.Value.Type().Underlying().(*types.Signature))
params = append(params, context) // context parameter
hasContext = true
switch b.Scheduler {
case "none", "coroutines":
// There are no additional parameters needed for the goroutine start operation.
case "tasks":
// Add the function pointer as a parameter to start the goroutine.
params = append(params, funcPtr)
default:
panic("unknown scheduler type")
}
prefix = b.fn.RelString(nil)
} else {
b.addError(instr.Pos(), "todo: go on interface call")
return
}
paramBundle := b.emitPointerPack(params) paramBundle := b.emitPointerPack(params)
var callee, stackSize llvm.Value var callee, stackSize llvm.Value
switch b.Scheduler { switch b.Scheduler {
case "none", "tasks": case "none", "tasks":
callee = b.createGoroutineStartWrapper(funcPtr, prefix, hasContext, instr.Pos()) callee = b.createGoroutineStartWrapper(funcPtr, prefix, pos)
if b.AutomaticStackSize { if b.AutomaticStackSize {
// The stack size is not known until after linking. Call a dummy // The stack size is not known until after linking. Call a dummy
// function that will be replaced with a load from a special ELF // function that will be replaced with a load from a special ELF
@@ -119,9 +34,6 @@ func (b *builder) createGo(instr *ssa.Go) {
} else { } else {
// The stack size is fixed at compile time. By emitting it here as a // The stack size is fixed at compile time. By emitting it here as a
// constant, it can be optimized. // constant, it can be optimized.
if b.Scheduler == "tasks" && b.DefaultStackSize == 0 {
b.addError(instr.Pos(), "default stack size for goroutines is not set")
}
stackSize = llvm.ConstInt(b.uintptrType, b.DefaultStackSize, false) stackSize = llvm.ConstInt(b.uintptrType, b.DefaultStackSize, false)
} }
case "coroutines": case "coroutines":
@@ -134,6 +46,7 @@ func (b *builder) createGo(instr *ssa.Go) {
} }
start := b.getFunction(b.program.ImportedPackage("internal/task").Members["start"].(*ssa.Function)) start := b.getFunction(b.program.ImportedPackage("internal/task").Members["start"].(*ssa.Function))
b.createCall(start, []llvm.Value{callee, paramBundle, stackSize, llvm.Undef(b.i8ptrType), llvm.ConstPointerNull(b.i8ptrType)}, "") b.createCall(start, []llvm.Value{callee, paramBundle, stackSize, llvm.Undef(b.i8ptrType), llvm.ConstPointerNull(b.i8ptrType)}, "")
return llvm.Undef(funcPtr.Type().ElementType().ReturnType())
} }
// createGoroutineStartWrapper creates a wrapper for the task-based // createGoroutineStartWrapper creates a wrapper for the task-based
@@ -154,12 +67,7 @@ func (b *builder) createGo(instr *ssa.Go) {
// allows a single (pointer) argument to the newly started goroutine. Also, it // allows a single (pointer) argument to the newly started goroutine. Also, it
// ignores the return value because newly started goroutines do not have a // ignores the return value because newly started goroutines do not have a
// return value. // return value.
// func (c *compilerContext) createGoroutineStartWrapper(fn llvm.Value, prefix string, pos token.Pos) llvm.Value {
// The hasContext parameter indicates whether the context parameter (the second
// to last parameter of the function) is used for this wrapper. If hasContext is
// false, the parameter bundle is assumed to have no context parameter and undef
// is passed instead.
func (c *compilerContext) createGoroutineStartWrapper(fn llvm.Value, prefix string, hasContext bool, pos token.Pos) llvm.Value {
var wrapper llvm.Value var wrapper llvm.Value
builder := c.ctx.NewBuilder() builder := c.ctx.NewBuilder()
@@ -206,15 +114,8 @@ func (c *compilerContext) createGoroutineStartWrapper(fn llvm.Value, prefix stri
// Create the list of params for the call. // Create the list of params for the call.
paramTypes := fn.Type().ElementType().ParamTypes() paramTypes := fn.Type().ElementType().ParamTypes()
paramTypes = paramTypes[:len(paramTypes)-1] // strip parentHandle parameter params := llvmutil.EmitPointerUnpack(builder, c.mod, wrapper.Param(0), paramTypes[:len(paramTypes)-1])
if !hasContext { params = append(params, llvm.Undef(c.i8ptrType))
paramTypes = paramTypes[:len(paramTypes)-1] // strip context parameter
}
params := llvmutil.EmitPointerUnpack(builder, c.mod, wrapper.Param(0), paramTypes)
if !hasContext {
params = append(params, llvm.Undef(c.i8ptrType)) // add dummy context parameter
}
params = append(params, llvm.Undef(c.i8ptrType)) // add dummy parentHandle parameter
// Create the call. // Create the call.
builder.CreateCall(fn, params, "") builder.CreateCall(fn, params, "")
@@ -276,11 +177,8 @@ func (c *compilerContext) createGoroutineStartWrapper(fn llvm.Value, prefix stri
// Get the function pointer. // Get the function pointer.
fnPtr := params[len(params)-1] fnPtr := params[len(params)-1]
// The last parameter in the packed object has somewhat of a dual role. // Ignore the last param, which isn't used anymore.
// Inside the parameter bundle it's the function pointer, stored right // TODO: avoid this extra "parent handle" parameter in most functions.
// after the context pointer. But in the IR call instruction, it's the
// parentHandle function that's always undef outside of the coroutines
// scheduler. Thus, make the parameter undef here.
params[len(params)-1] = llvm.Undef(c.i8ptrType) params[len(params)-1] = llvm.Undef(c.i8ptrType)
// Create the call. // Create the call.
+18 -33
View File
@@ -46,7 +46,6 @@ func (c *compilerContext) getTypeCode(typ types.Type) llvm.Value {
var references llvm.Value var references llvm.Value
var length int64 var length int64
var methodSet llvm.Value var methodSet llvm.Value
var ptrTo llvm.Value
switch typ := typ.(type) { switch typ := typ.(type) {
case *types.Named: case *types.Named:
references = c.getTypeCode(typ.Underlying()) references = c.getTypeCode(typ.Underlying())
@@ -70,25 +69,22 @@ func (c *compilerContext) getTypeCode(typ types.Type) llvm.Value {
if _, ok := typ.Underlying().(*types.Interface); !ok { if _, ok := typ.Underlying().(*types.Interface); !ok {
methodSet = c.getTypeMethodSet(typ) methodSet = c.getTypeMethodSet(typ)
} }
if _, ok := typ.Underlying().(*types.Pointer); !ok { if !references.IsNil() || length != 0 || !methodSet.IsNil() {
ptrTo = c.getTypeCode(types.NewPointer(typ)) // Set the 'references' field of the runtime.typecodeID struct.
globalValue := llvm.ConstNull(global.Type().ElementType())
if !references.IsNil() {
globalValue = llvm.ConstInsertValue(globalValue, references, []uint32{0})
}
if length != 0 {
lengthValue := llvm.ConstInt(c.uintptrType, uint64(length), false)
globalValue = llvm.ConstInsertValue(globalValue, lengthValue, []uint32{1})
}
if !methodSet.IsNil() {
globalValue = llvm.ConstInsertValue(globalValue, methodSet, []uint32{2})
}
global.SetInitializer(globalValue)
global.SetLinkage(llvm.LinkOnceODRLinkage)
} }
globalValue := llvm.ConstNull(global.Type().ElementType())
if !references.IsNil() {
globalValue = llvm.ConstInsertValue(globalValue, references, []uint32{0})
}
if length != 0 {
lengthValue := llvm.ConstInt(c.uintptrType, uint64(length), false)
globalValue = llvm.ConstInsertValue(globalValue, lengthValue, []uint32{1})
}
if !methodSet.IsNil() {
globalValue = llvm.ConstInsertValue(globalValue, methodSet, []uint32{2})
}
if !ptrTo.IsNil() {
globalValue = llvm.ConstInsertValue(globalValue, ptrTo, []uint32{3})
}
global.SetInitializer(globalValue)
global.SetLinkage(llvm.LinkOnceODRLinkage)
global.SetGlobalConstant(true) global.SetGlobalConstant(true)
} }
return global return global
@@ -311,21 +307,10 @@ func (c *compilerContext) getInterfaceMethodSet(typ types.Type) llvm.Value {
// 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 := methodSignature(method)
var globalName string signatureGlobal := c.mod.NamedGlobal("func " + signature)
if token.IsExported(method.Name()) {
globalName = "reflect/methods." + signature
} else {
globalName = method.Type().(*types.Signature).Recv().Pkg().Path() + ".$methods." + signature
}
signatureGlobal := c.mod.NamedGlobal(globalName)
if signatureGlobal.IsNil() { if signatureGlobal.IsNil() {
// TODO: put something useful in these globals, such as the method signatureGlobal = llvm.AddGlobal(c.mod, c.ctx.Int8Type(), "func "+signature)
// signature. Useful to one day implement reflect.Value.Method(n).
signatureGlobal = llvm.AddGlobal(c.mod, c.ctx.Int8Type(), globalName)
signatureGlobal.SetInitializer(llvm.ConstInt(c.ctx.Int8Type(), 0, false))
signatureGlobal.SetLinkage(llvm.LinkOnceODRLinkage)
signatureGlobal.SetGlobalConstant(true) signatureGlobal.SetGlobalConstant(true)
signatureGlobal.SetAlignment(1)
} }
return signatureGlobal return signatureGlobal
} }
@@ -356,7 +341,7 @@ 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) globalName := "reflect/types.type:" + getTypeCodeName(expr.AssertedType) + "$id"
assertedTypeCodeGlobal := b.mod.NamedGlobal(globalName) assertedTypeCodeGlobal := b.mod.NamedGlobal(globalName)
if assertedTypeCodeGlobal.IsNil() { if assertedTypeCodeGlobal.IsNil() {
// Create a new typecode global. // Create a new typecode global.
+4 -1
View File
@@ -41,7 +41,10 @@ func (b *builder) createInterruptGlobal(instr *ssa.CallCommon) (llvm.Value, erro
// type are lowered in the interrupt lowering pass. // type are lowered in the interrupt lowering pass.
globalType := b.program.ImportedPackage("runtime/interrupt").Type("handle").Type() globalType := b.program.ImportedPackage("runtime/interrupt").Type("handle").Type()
globalLLVMType := b.getLLVMType(globalType) globalLLVMType := b.getLLVMType(globalType)
globalName := b.fn.Package().Pkg.Path() + "$interrupt" + strconv.FormatInt(id.Int64(), 10) globalName := "runtime/interrupt.$interrupt" + strconv.FormatInt(id.Int64(), 10)
if global := b.mod.NamedGlobal(globalName); !global.IsNil() {
return llvm.Value{}, b.makeError(instr.Pos(), "interrupt redeclared in this program")
}
global := llvm.AddGlobal(b.mod, globalLLVMType, globalName) global := llvm.AddGlobal(b.mod, globalLLVMType, globalName)
global.SetVisibility(llvm.HiddenVisibility) global.SetVisibility(llvm.HiddenVisibility)
global.SetGlobalConstant(true) global.SetGlobalConstant(true)
-54
View File
@@ -48,57 +48,3 @@ func (b *builder) createMemoryZeroCall(args []ssa.Value) (llvm.Value, error) {
b.CreateCall(llvmFn, params, "") b.CreateCall(llvmFn, params, "")
return llvm.Value{}, nil return llvm.Value{}, nil
} }
var mathToLLVMMapping = map[string]string{
"math.Sqrt": "llvm.sqrt.f64",
"math.Floor": "llvm.floor.f64",
"math.Ceil": "llvm.ceil.f64",
"math.Trunc": "llvm.trunc.f64",
}
// createMathOp tries to lower the given call as a LLVM math intrinsic, if
// possible. It returns the call result if possible, and a boolean whether it
// succeeded. If it doesn't succeed, the architecture doesn't support the given
// intrinsic.
func (b *builder) createMathOp(call *ssa.CallCommon) (llvm.Value, bool) {
// Check whether this intrinsic is supported on the given GOARCH.
// If it is unsupported, this can have two reasons:
//
// 1. LLVM can expand the intrinsic inline (using float instructions), but
// the result doesn't pass the tests of the math package.
// 2. LLVM cannot expand the intrinsic inline, will therefore lower it as a
// libm function call, but the libm function call also fails the math
// package tests.
//
// Whatever the implementation, it must pass the tests in the math package
// so unfortunately only the below intrinsic+architecture combinations are
// supported.
name := call.StaticCallee().RelString(nil)
switch name {
case "math.Ceil", "math.Floor", "math.Trunc":
if b.GOARCH != "wasm" && b.GOARCH != "arm64" {
return llvm.Value{}, false
}
case "math.Sqrt":
if b.GOARCH != "wasm" && b.GOARCH != "amd64" && b.GOARCH != "386" {
return llvm.Value{}, false
}
default:
return llvm.Value{}, false // only the above functions are supported.
}
llvmFn := b.mod.NamedFunction(mathToLLVMMapping[name])
if llvmFn.IsNil() {
// The intrinsic doesn't exist yet, so declare it.
// At the moment, all supported intrinsics have the form "double
// foo(double %x)" so we can hardcode the signature here.
llvmType := llvm.FunctionType(b.ctx.DoubleType(), []llvm.Type{b.ctx.DoubleType()}, false)
llvmFn = llvm.AddFunction(b.mod, mathToLLVMMapping[name], llvmType)
}
// Create a call to the intrinsic.
args := make([]llvm.Value, len(call.Args))
for i, arg := range call.Args {
args[i] = b.getValue(arg)
}
return b.CreateCall(llvmFn, args, ""), true
}
+5 -28
View File
@@ -24,7 +24,6 @@ type functionInfo struct {
module string // go:wasm-module module string // go:wasm-module
importName string // go:linkname, go:export - The name the developer assigns importName string // go:linkname, go:export - The name the developer assigns
linkName string // go:linkname, go:export - The name that we map for the particular module -> importName linkName string // go:linkname, go:export - The name that we map for the particular module -> importName
section string // go:section - object file section name
exported bool // go:export, CGo exported bool // go:export, CGo
nobounds bool // go:nobounds nobounds bool // go:nobounds
variadic bool // go:variadic (CGo only) variadic bool // go:variadic (CGo only)
@@ -140,17 +139,6 @@ func (c *compilerContext) getFunction(fn *ssa.Function) llvm.Value {
for _, attrName := range []string{"noalias", "nonnull"} { for _, attrName := range []string{"noalias", "nonnull"} {
llvmFn.AddAttributeAtIndex(0, c.ctx.CreateEnumAttribute(llvm.AttributeKindID(attrName), 0)) llvmFn.AddAttributeAtIndex(0, c.ctx.CreateEnumAttribute(llvm.AttributeKindID(attrName), 0))
} }
case "runtime.sliceAppend":
// Appending a slice will only read the to-be-appended slice, it won't
// be modified.
llvmFn.AddAttributeAtIndex(2, c.ctx.CreateEnumAttribute(llvm.AttributeKindID("nocapture"), 0))
llvmFn.AddAttributeAtIndex(2, c.ctx.CreateEnumAttribute(llvm.AttributeKindID("readonly"), 0))
case "runtime.sliceCopy":
// Copying a slice won't capture any of the parameters.
llvmFn.AddAttributeAtIndex(1, c.ctx.CreateEnumAttribute(llvm.AttributeKindID("writeonly"), 0))
llvmFn.AddAttributeAtIndex(1, c.ctx.CreateEnumAttribute(llvm.AttributeKindID("nocapture"), 0))
llvmFn.AddAttributeAtIndex(2, c.ctx.CreateEnumAttribute(llvm.AttributeKindID("readonly"), 0))
llvmFn.AddAttributeAtIndex(2, c.ctx.CreateEnumAttribute(llvm.AttributeKindID("nocapture"), 0))
case "runtime.trackPointer": case "runtime.trackPointer":
// This function is necessary for tracking pointers on the stack in a // This function is necessary for tracking pointers on the stack in a
// portable way (see gc_stack_portable.go). Indicate to the optimizer // portable way (see gc_stack_portable.go). Indicate to the optimizer
@@ -271,10 +259,6 @@ func (info *functionInfo) parsePragmas(f *ssa.Function) {
if hasUnsafeImport(f.Pkg.Pkg) { if hasUnsafeImport(f.Pkg.Pkg) {
info.linkName = parts[2] info.linkName = parts[2]
} }
case "//go:section":
if len(parts) == 2 && hasUnsafeImport(f.Pkg.Pkg) {
info.section = parts[1]
}
case "//go:nobounds": case "//go:nobounds":
// Skip bounds checking in this function. Useful for some // Skip bounds checking in this function. Useful for some
// runtime functions. // runtime functions.
@@ -330,7 +314,6 @@ type globalInfo struct {
linkName string // go:extern linkName string // go:extern
extern bool // go:extern extern bool // go:extern
align int // go:align align int // go:align
section string // go:section
} }
// loadASTComments loads comments on globals from the AST, for use later in the // loadASTComments loads comments on globals from the AST, for use later in the
@@ -372,18 +355,16 @@ func (c *compilerContext) getGlobal(g *ssa.Global) llvm.Value {
// Set alignment from the //go:align comment. // Set alignment from the //go:align comment.
var alignInBits uint32 var alignInBits uint32
alignment := c.targetData.ABITypeAlignment(llvmType) if info.align < 0 || info.align&(info.align-1) != 0 {
if info.align > alignment {
alignment = info.align
}
if alignment <= 0 || alignment&(alignment-1) != 0 {
// Check for power-of-two (or 0). // Check for power-of-two (or 0).
// See: https://stackoverflow.com/a/108360 // See: https://stackoverflow.com/a/108360
c.addError(g.Pos(), "global variable alignment must be a positive power of two") c.addError(g.Pos(), "global variable alignment must be a positive power of two")
} else { } else {
// Set the alignment only when it is a power of two. // Set the alignment only when it is a power of two.
alignInBits = uint32(alignment) ^ uint32(alignment-1) alignInBits = uint32(info.align) ^ uint32(info.align-1)
llvmGlobal.SetAlignment(alignment) if info.align > c.targetData.ABITypeAlignment(llvmType) {
llvmGlobal.SetAlignment(info.align)
}
} }
if c.Debug && !info.extern { if c.Debug && !info.extern {
@@ -444,10 +425,6 @@ func (info *globalInfo) parsePragmas(doc *ast.CommentGroup) {
if err == nil { if err == nil {
info.align = align info.align = align
} }
case "//go:section":
if len(parts) == 2 {
info.section = parts[1]
}
} }
} }
} }
+8 -30
View File
@@ -10,11 +10,11 @@ import (
"tinygo.org/x/go-llvm" "tinygo.org/x/go-llvm"
) )
// createRawSyscall creates a system call with the provided system call number // createSyscall emits an inline system call instruction, depending on the
// and returns the result as a single integer (the system call result). The // target OS/arch.
// result is not further interpreted. func (b *builder) createSyscall(call *ssa.CallCommon) (llvm.Value, error) {
func (b *builder) createRawSyscall(call *ssa.CallCommon) (llvm.Value, error) {
num := b.getValue(call.Args[0]) num := b.getValue(call.Args[0])
var syscallResult llvm.Value
switch { switch {
case b.GOARCH == "amd64": case b.GOARCH == "amd64":
if b.GOOS == "darwin" { if b.GOOS == "darwin" {
@@ -57,7 +57,7 @@ func (b *builder) createRawSyscall(call *ssa.CallCommon) (llvm.Value, error) {
constraints += ",~{rcx},~{r11}" constraints += ",~{rcx},~{r11}"
fnType := llvm.FunctionType(b.uintptrType, argTypes, false) fnType := llvm.FunctionType(b.uintptrType, argTypes, false)
target := llvm.InlineAsm(fnType, "syscall", constraints, true, false, llvm.InlineAsmDialectIntel) target := llvm.InlineAsm(fnType, "syscall", constraints, true, false, llvm.InlineAsmDialectIntel)
return b.CreateCall(target, args, ""), nil syscallResult = b.CreateCall(target, args, "")
case b.GOARCH == "386" && b.GOOS == "linux": case b.GOARCH == "386" && b.GOOS == "linux":
// Sources: // Sources:
// syscall(2) man page // syscall(2) man page
@@ -83,7 +83,7 @@ func (b *builder) createRawSyscall(call *ssa.CallCommon) (llvm.Value, error) {
} }
fnType := llvm.FunctionType(b.uintptrType, argTypes, false) fnType := llvm.FunctionType(b.uintptrType, argTypes, false)
target := llvm.InlineAsm(fnType, "int 0x80", constraints, true, false, llvm.InlineAsmDialectIntel) target := llvm.InlineAsm(fnType, "int 0x80", constraints, true, false, llvm.InlineAsmDialectIntel)
return b.CreateCall(target, args, ""), nil syscallResult = b.CreateCall(target, args, "")
case b.GOARCH == "arm" && b.GOOS == "linux": case b.GOARCH == "arm" && b.GOOS == "linux":
// Implement the EABI system call convention for Linux. // Implement the EABI system call convention for Linux.
// Source: syscall(2) man page. // Source: syscall(2) man page.
@@ -115,7 +115,7 @@ func (b *builder) createRawSyscall(call *ssa.CallCommon) (llvm.Value, error) {
} }
fnType := llvm.FunctionType(b.uintptrType, argTypes, false) fnType := llvm.FunctionType(b.uintptrType, argTypes, false)
target := llvm.InlineAsm(fnType, "svc #0", constraints, true, false, 0) target := llvm.InlineAsm(fnType, "svc #0", constraints, true, false, 0)
return b.CreateCall(target, args, ""), nil syscallResult = b.CreateCall(target, args, "")
case b.GOARCH == "arm64" && b.GOOS == "linux": case b.GOARCH == "arm64" && b.GOOS == "linux":
// Source: syscall(2) man page. // Source: syscall(2) man page.
args := []llvm.Value{} args := []llvm.Value{}
@@ -147,19 +147,10 @@ func (b *builder) createRawSyscall(call *ssa.CallCommon) (llvm.Value, error) {
constraints += ",~{x16},~{x17}" // scratch registers constraints += ",~{x16},~{x17}" // scratch registers
fnType := llvm.FunctionType(b.uintptrType, argTypes, false) fnType := llvm.FunctionType(b.uintptrType, argTypes, false)
target := llvm.InlineAsm(fnType, "svc #0", constraints, true, false, 0) target := llvm.InlineAsm(fnType, "svc #0", constraints, true, false, 0)
return b.CreateCall(target, args, ""), nil syscallResult = b.CreateCall(target, args, "")
default: default:
return llvm.Value{}, b.makeError(call.Pos(), "unknown GOOS/GOARCH for syscall: "+b.GOOS+"/"+b.GOARCH) return llvm.Value{}, b.makeError(call.Pos(), "unknown GOOS/GOARCH for syscall: "+b.GOOS+"/"+b.GOARCH)
} }
}
// createSyscall emits instructions for the syscall.Syscall* family of
// functions, depending on the target OS/arch.
func (b *builder) createSyscall(call *ssa.CallCommon) (llvm.Value, error) {
syscallResult, err := b.createRawSyscall(call)
if err != nil {
return syscallResult, err
}
switch b.GOOS { switch b.GOOS {
case "linux", "freebsd": case "linux", "freebsd":
// Return values: r0, r1 uintptr, err Errno // Return values: r0, r1 uintptr, err Errno
@@ -199,16 +190,3 @@ func (b *builder) createSyscall(call *ssa.CallCommon) (llvm.Value, error) {
return llvm.Value{}, b.makeError(call.Pos(), "unknown GOOS/GOARCH for syscall: "+b.GOOS+"/"+b.GOARCH) return llvm.Value{}, b.makeError(call.Pos(), "unknown GOOS/GOARCH for syscall: "+b.GOOS+"/"+b.GOARCH)
} }
} }
// createRawSyscallNoError emits instructions for the Linux-specific
// syscall.rawSyscallNoError function.
func (b *builder) createRawSyscallNoError(call *ssa.CallCommon) (llvm.Value, error) {
syscallResult, err := b.createRawSyscall(call)
if err != nil {
return syscallResult, err
}
retval := llvm.ConstNull(b.ctx.StructType([]llvm.Type{b.uintptrType, b.uintptrType}, false))
retval = b.CreateInsertValue(retval, syscallResult, 0, "")
retval = b.CreateInsertValue(retval, llvm.ConstInt(b.uintptrType, 0, false), 1, "")
return retval, nil
}
-15
View File
@@ -55,18 +55,3 @@ func complexMul(x, y complex64) complex64 {
} }
// TODO: complexDiv (requires runtime call) // TODO: complexDiv (requires runtime call)
// A type 'kv' also exists in function foo. Test that these two types don't
// conflict with each other.
type kv struct {
v float32
}
func foo(a *kv) {
// Define a new 'kv' type.
type kv struct {
v byte
}
// Use this type.
func(b *kv) {}(nil)
}
+2 -16
View File
@@ -1,10 +1,7 @@
; ModuleID = 'basic.go' ; ModuleID = 'basic.go'
source_filename = "basic.go" source_filename = "basic.go"
target datalayout = "e-m:e-p:32:32-i64:64-n32:64-S128" target datalayout = "e-m:e-p:32:32-p270:32:32-p271:32:32-p272:64:64-f64:32:64-f80:32-n8:16:32-S128"
target triple = "wasm32--wasi" target triple = "i686--linux"
%main.kv = type { float }
%main.kv.0 = type { i8 }
declare noalias nonnull i8* @runtime.alloc(i32, i8*, i8*) declare noalias nonnull i8* @runtime.alloc(i32, i8*, i8*)
@@ -101,14 +98,3 @@ entry:
%7 = insertvalue { float, float } %6, float %5, 1 %7 = insertvalue { float, float } %6, float %5, 1
ret { float, float } %7 ret { float, float } %7
} }
define hidden void @main.foo(%main.kv* dereferenceable_or_null(4) %a, i8* %context, i8* %parentHandle) unnamed_addr {
entry:
call void @"main.foo$1"(%main.kv.0* null, i8* undef, i8* undef)
ret void
}
define hidden void @"main.foo$1"(%main.kv.0* dereferenceable_or_null(1) %b, i8* %context, i8* %parentHandle) unnamed_addr {
entry:
ret void
}
-25
View File
@@ -1,25 +0,0 @@
package main
func chanIntSend(ch chan int) {
ch <- 3
}
func chanIntRecv(ch chan int) {
<-ch
}
func chanZeroSend(ch chan struct{}) {
ch <- struct{}{}
}
func chanZeroRecv(ch chan struct{}) {
<-ch
}
func selectZeroRecv(ch1 chan int, ch2 chan struct{}) {
select {
case ch1 <- 1:
case <-ch2:
default:
}
}
-114
View File
@@ -1,114 +0,0 @@
; ModuleID = 'channel.go'
source_filename = "channel.go"
target datalayout = "e-m:e-p:32:32-i64:64-n32:64-S128"
target triple = "wasm32--wasi"
%runtime.channel = type { i32, i32, i8, %runtime.channelBlockedList*, i32, i32, i32, i8* }
%runtime.channelBlockedList = type { %runtime.channelBlockedList*, %"internal/task.Task"*, %runtime.chanSelectState*, { %runtime.channelBlockedList*, i32, i32 } }
%"internal/task.Task" = type { %"internal/task.Task"*, i8*, i64, %"internal/task.state" }
%"internal/task.state" = type { i8* }
%runtime.chanSelectState = type { %runtime.channel*, i8* }
declare noalias nonnull i8* @runtime.alloc(i32, i8*, i8*)
define hidden void @main.init(i8* %context, i8* %parentHandle) unnamed_addr {
entry:
ret void
}
define hidden void @main.chanIntSend(%runtime.channel* dereferenceable_or_null(32) %ch, i8* %context, i8* %parentHandle) unnamed_addr {
entry:
%chan.blockedList = alloca %runtime.channelBlockedList, align 8
%chan.value = alloca i32, align 4
%chan.value.bitcast = bitcast i32* %chan.value to i8*
call void @llvm.lifetime.start.p0i8(i64 4, i8* nonnull %chan.value.bitcast)
store i32 3, i32* %chan.value, align 4
%chan.blockedList.bitcast = bitcast %runtime.channelBlockedList* %chan.blockedList to i8*
call void @llvm.lifetime.start.p0i8(i64 24, i8* nonnull %chan.blockedList.bitcast)
call void @runtime.chanSend(%runtime.channel* %ch, i8* nonnull %chan.value.bitcast, %runtime.channelBlockedList* nonnull %chan.blockedList, i8* undef, i8* null)
call void @llvm.lifetime.end.p0i8(i64 24, i8* nonnull %chan.blockedList.bitcast)
call void @llvm.lifetime.end.p0i8(i64 4, i8* nonnull %chan.value.bitcast)
ret void
}
; Function Attrs: argmemonly nounwind willreturn
declare void @llvm.lifetime.start.p0i8(i64 immarg, i8* nocapture) #0
declare void @runtime.chanSend(%runtime.channel* dereferenceable_or_null(32), i8*, %runtime.channelBlockedList* dereferenceable_or_null(24), i8*, i8*)
; Function Attrs: argmemonly nounwind willreturn
declare void @llvm.lifetime.end.p0i8(i64 immarg, i8* nocapture) #0
define hidden void @main.chanIntRecv(%runtime.channel* dereferenceable_or_null(32) %ch, i8* %context, i8* %parentHandle) unnamed_addr {
entry:
%chan.blockedList = alloca %runtime.channelBlockedList, align 8
%chan.value = alloca i32, align 4
%chan.value.bitcast = bitcast i32* %chan.value to i8*
call void @llvm.lifetime.start.p0i8(i64 4, i8* nonnull %chan.value.bitcast)
%chan.blockedList.bitcast = bitcast %runtime.channelBlockedList* %chan.blockedList to i8*
call void @llvm.lifetime.start.p0i8(i64 24, i8* nonnull %chan.blockedList.bitcast)
%0 = call i1 @runtime.chanRecv(%runtime.channel* %ch, i8* nonnull %chan.value.bitcast, %runtime.channelBlockedList* nonnull %chan.blockedList, i8* undef, i8* null)
call void @llvm.lifetime.end.p0i8(i64 4, i8* nonnull %chan.value.bitcast)
call void @llvm.lifetime.end.p0i8(i64 24, i8* nonnull %chan.blockedList.bitcast)
ret void
}
declare i1 @runtime.chanRecv(%runtime.channel* dereferenceable_or_null(32), i8*, %runtime.channelBlockedList* dereferenceable_or_null(24), i8*, i8*)
define hidden void @main.chanZeroSend(%runtime.channel* dereferenceable_or_null(32) %ch, i8* %context, i8* %parentHandle) unnamed_addr {
entry:
%chan.blockedList = alloca %runtime.channelBlockedList, align 8
%chan.blockedList.bitcast = bitcast %runtime.channelBlockedList* %chan.blockedList to i8*
call void @llvm.lifetime.start.p0i8(i64 24, i8* nonnull %chan.blockedList.bitcast)
call void @runtime.chanSend(%runtime.channel* %ch, i8* null, %runtime.channelBlockedList* nonnull %chan.blockedList, i8* undef, i8* null)
call void @llvm.lifetime.end.p0i8(i64 24, i8* nonnull %chan.blockedList.bitcast)
ret void
}
define hidden void @main.chanZeroRecv(%runtime.channel* dereferenceable_or_null(32) %ch, i8* %context, i8* %parentHandle) unnamed_addr {
entry:
%chan.blockedList = alloca %runtime.channelBlockedList, align 8
%chan.blockedList.bitcast = bitcast %runtime.channelBlockedList* %chan.blockedList to i8*
call void @llvm.lifetime.start.p0i8(i64 24, i8* nonnull %chan.blockedList.bitcast)
%0 = call i1 @runtime.chanRecv(%runtime.channel* %ch, i8* null, %runtime.channelBlockedList* nonnull %chan.blockedList, i8* undef, i8* null)
call void @llvm.lifetime.end.p0i8(i64 24, i8* nonnull %chan.blockedList.bitcast)
ret void
}
define hidden void @main.selectZeroRecv(%runtime.channel* dereferenceable_or_null(32) %ch1, %runtime.channel* dereferenceable_or_null(32) %ch2, i8* %context, i8* %parentHandle) unnamed_addr {
entry:
%select.states.alloca = alloca [2 x %runtime.chanSelectState], align 8
%select.send.value = alloca i32, align 4
store i32 1, i32* %select.send.value, align 4
%select.states.alloca.bitcast = bitcast [2 x %runtime.chanSelectState]* %select.states.alloca to i8*
call void @llvm.lifetime.start.p0i8(i64 16, i8* nonnull %select.states.alloca.bitcast)
%.repack = getelementptr inbounds [2 x %runtime.chanSelectState], [2 x %runtime.chanSelectState]* %select.states.alloca, i32 0, i32 0, i32 0
store %runtime.channel* %ch1, %runtime.channel** %.repack, align 8
%.repack1 = getelementptr inbounds [2 x %runtime.chanSelectState], [2 x %runtime.chanSelectState]* %select.states.alloca, i32 0, i32 0, i32 1
%0 = bitcast i8** %.repack1 to i32**
store i32* %select.send.value, i32** %0, align 4
%.repack3 = getelementptr inbounds [2 x %runtime.chanSelectState], [2 x %runtime.chanSelectState]* %select.states.alloca, i32 0, i32 1, i32 0
store %runtime.channel* %ch2, %runtime.channel** %.repack3, align 8
%.repack4 = getelementptr inbounds [2 x %runtime.chanSelectState], [2 x %runtime.chanSelectState]* %select.states.alloca, i32 0, i32 1, i32 1
store i8* null, i8** %.repack4, align 4
%select.states = getelementptr inbounds [2 x %runtime.chanSelectState], [2 x %runtime.chanSelectState]* %select.states.alloca, i32 0, i32 0
%select.result = call { i32, i1 } @runtime.tryChanSelect(i8* undef, %runtime.chanSelectState* nonnull %select.states, i32 2, i32 2, i8* undef, i8* null)
call void @llvm.lifetime.end.p0i8(i64 16, i8* nonnull %select.states.alloca.bitcast)
%1 = extractvalue { i32, i1 } %select.result, 0
%2 = icmp eq i32 %1, 0
br i1 %2, label %select.done, label %select.next
select.done: ; preds = %select.body, %select.next, %entry
ret void
select.next: ; preds = %entry
%3 = icmp eq i32 %1, 1
br i1 %3, label %select.body, label %select.done
select.body: ; preds = %select.next
br label %select.done
}
declare { i32, i1 } @runtime.tryChanSelect(i8*, %runtime.chanSelectState*, i32, i32, i8*, i8*)
attributes #0 = { argmemonly nounwind willreturn }
+2 -2
View File
@@ -1,7 +1,7 @@
; ModuleID = 'float.go' ; ModuleID = 'float.go'
source_filename = "float.go" source_filename = "float.go"
target datalayout = "e-m:e-p:32:32-i64:64-n32:64-S128" target datalayout = "e-m:e-p:32:32-p270:32:32-p271:32:32-p272:64:64-f64:32:64-f80:32-n8:16:32-S128"
target triple = "wasm32--wasi" target triple = "i686--linux"
declare noalias nonnull i8* @runtime.alloc(i32, i8*, i8*) declare noalias nonnull i8* @runtime.alloc(i32, i8*, i8*)
-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-i64:64-n32:64-S128"
target triple = "wasm32--wasi"
%runtime.funcValueWithSignature = type { i32, i8* }
@"reflect/types.funcid:func:{basic:int}{}" = external constant i8
@"main.someFunc$withSignature" = linkonce_odr constant %runtime.funcValueWithSignature { i32 ptrtoint (void (i32, i8*, i8*)* @main.someFunc to i32), i8* @"reflect/types.funcid:func:{basic:int}{}" }
declare noalias nonnull i8* @runtime.alloc(i32, i8*, i8*)
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
}
-41
View File
@@ -1,41 +0,0 @@
package main
// Test changes to the language introduced in Go 1.17.
// For details, see: https://tip.golang.org/doc/go1.17#language
// These tests should be merged into the regular slice tests once Go 1.17 is the
// minimun Go version for TinyGo.
import "unsafe"
func Add32(p unsafe.Pointer, len int) unsafe.Pointer {
return unsafe.Add(p, len)
}
func Add64(p unsafe.Pointer, len int64) unsafe.Pointer {
return unsafe.Add(p, len)
}
func SliceToArray(s []int) *[4]int {
return (*[4]int)(s)
}
func SliceToArrayConst() *[4]int {
s := make([]int, 6)
return (*[4]int)(s)
}
func SliceInt(ptr *int, len int) []int {
return unsafe.Slice(ptr, len)
}
func SliceUint16(ptr *byte, len uint16) []byte {
return unsafe.Slice(ptr, len)
}
func SliceUint64(ptr *int, len uint64) []int {
return unsafe.Slice(ptr, len)
}
func SliceInt64(ptr *int, len int64) []int {
return unsafe.Slice(ptr, len)
}
-136
View File
@@ -1,136 +0,0 @@
; ModuleID = 'go1.17.go'
source_filename = "go1.17.go"
target datalayout = "e-m:e-p:32:32-i64:64-n32:64-S128"
target triple = "wasm32--wasi"
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 i8* @main.Add32(i8* %p, i32 %len, i8* %context, i8* %parentHandle) unnamed_addr {
entry:
%0 = getelementptr i8, i8* %p, i32 %len
ret i8* %0
}
define hidden i8* @main.Add64(i8* %p, i64 %len, i8* %context, i8* %parentHandle) unnamed_addr {
entry:
%0 = trunc i64 %len to i32
%1 = getelementptr i8, i8* %p, i32 %0
ret i8* %1
}
define hidden [4 x i32]* @main.SliceToArray(i32* %s.data, i32 %s.len, i32 %s.cap, i8* %context, i8* %parentHandle) unnamed_addr {
entry:
%0 = icmp ult i32 %s.len, 4
br i1 %0, label %slicetoarray.throw, label %slicetoarray.next
slicetoarray.throw: ; preds = %entry
call void @runtime.sliceToArrayPointerPanic(i8* undef, i8* null)
unreachable
slicetoarray.next: ; preds = %entry
%1 = bitcast i32* %s.data to [4 x i32]*
ret [4 x i32]* %1
}
declare void @runtime.sliceToArrayPointerPanic(i8*, i8*)
define hidden [4 x i32]* @main.SliceToArrayConst(i8* %context, i8* %parentHandle) unnamed_addr {
entry:
%makeslice = call i8* @runtime.alloc(i32 24, i8* undef, i8* null)
br i1 false, label %slicetoarray.throw, label %slicetoarray.next
slicetoarray.throw: ; preds = %entry
unreachable
slicetoarray.next: ; preds = %entry
%0 = bitcast i8* %makeslice to [4 x i32]*
ret [4 x i32]* %0
}
define hidden { i32*, i32, i32 } @main.SliceInt(i32* dereferenceable_or_null(4) %ptr, i32 %len, i8* %context, i8* %parentHandle) unnamed_addr {
entry:
%0 = icmp ugt i32 %len, 1073741823
%1 = icmp eq i32* %ptr, null
%2 = icmp ne i32 %len, 0
%3 = and i1 %1, %2
%4 = or i1 %3, %0
br i1 %4, label %unsafe.Slice.throw, label %unsafe.Slice.next
unsafe.Slice.throw: ; preds = %entry
call void @runtime.unsafeSlicePanic(i8* undef, i8* null)
unreachable
unsafe.Slice.next: ; preds = %entry
%5 = insertvalue { i32*, i32, i32 } undef, i32* %ptr, 0
%6 = insertvalue { i32*, i32, i32 } %5, i32 %len, 1
%7 = insertvalue { i32*, i32, i32 } %6, i32 %len, 2
ret { i32*, i32, i32 } %7
}
declare void @runtime.unsafeSlicePanic(i8*, i8*)
define hidden { i8*, i32, i32 } @main.SliceUint16(i8* dereferenceable_or_null(1) %ptr, i16 %len, i8* %context, i8* %parentHandle) unnamed_addr {
entry:
%0 = icmp eq i8* %ptr, null
%1 = icmp ne i16 %len, 0
%2 = and i1 %0, %1
br i1 %2, label %unsafe.Slice.throw, label %unsafe.Slice.next
unsafe.Slice.throw: ; preds = %entry
call void @runtime.unsafeSlicePanic(i8* undef, i8* null)
unreachable
unsafe.Slice.next: ; preds = %entry
%3 = zext i16 %len to i32
%4 = insertvalue { i8*, i32, i32 } undef, i8* %ptr, 0
%5 = insertvalue { i8*, i32, i32 } %4, i32 %3, 1
%6 = insertvalue { i8*, i32, i32 } %5, i32 %3, 2
ret { i8*, i32, i32 } %6
}
define hidden { i32*, i32, i32 } @main.SliceUint64(i32* dereferenceable_or_null(4) %ptr, i64 %len, i8* %context, i8* %parentHandle) unnamed_addr {
entry:
%0 = icmp ugt i64 %len, 1073741823
%1 = icmp eq i32* %ptr, null
%2 = icmp ne i64 %len, 0
%3 = and i1 %1, %2
%4 = or i1 %3, %0
br i1 %4, label %unsafe.Slice.throw, label %unsafe.Slice.next
unsafe.Slice.throw: ; preds = %entry
call void @runtime.unsafeSlicePanic(i8* undef, i8* null)
unreachable
unsafe.Slice.next: ; preds = %entry
%5 = trunc i64 %len to i32
%6 = insertvalue { i32*, i32, i32 } undef, i32* %ptr, 0
%7 = insertvalue { i32*, i32, i32 } %6, i32 %5, 1
%8 = insertvalue { i32*, i32, i32 } %7, i32 %5, 2
ret { i32*, i32, i32 } %8
}
define hidden { i32*, i32, i32 } @main.SliceInt64(i32* dereferenceable_or_null(4) %ptr, i64 %len, i8* %context, i8* %parentHandle) unnamed_addr {
entry:
%0 = icmp ugt i64 %len, 1073741823
%1 = icmp eq i32* %ptr, null
%2 = icmp ne i64 %len, 0
%3 = and i1 %1, %2
%4 = or i1 %3, %0
br i1 %4, label %unsafe.Slice.throw, label %unsafe.Slice.next
unsafe.Slice.throw: ; preds = %entry
call void @runtime.unsafeSlicePanic(i8* undef, i8* null)
unreachable
unsafe.Slice.next: ; preds = %entry
%5 = trunc i64 %len to i32
%6 = insertvalue { i32*, i32, i32 } undef, i32* %ptr, 0
%7 = insertvalue { i32*, i32, i32 } %6, i32 %5, 1
%8 = insertvalue { i32*, i32, i32 } %7, i32 %5, 2
ret { i32*, i32, i32 } %8
}
-150
View File
@@ -1,150 +0,0 @@
; ModuleID = 'goroutine.go'
source_filename = "goroutine.go"
target datalayout = "e-m:e-p:32:32-Fi8-i64:64-v128:64:128-a:0:32-n32-S64"
target triple = "armv7m-none-eabi"
%runtime.channel = type { i32, i32, i8, %runtime.channelBlockedList*, i32, i32, i32, i8* }
%runtime.channelBlockedList = type { %runtime.channelBlockedList*, %"internal/task.Task"*, %runtime.chanSelectState*, { %runtime.channelBlockedList*, i32, i32 } }
%"internal/task.Task" = type { %"internal/task.Task"*, i8*, i64, %"internal/task.state" }
%"internal/task.state" = type { i32, i32* }
%runtime.chanSelectState = type { %runtime.channel*, i8* }
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.regularFunctionGoroutine(i8* %context, i8* %parentHandle) unnamed_addr {
entry:
%stacksize = call i32 @"internal/task.getGoroutineStackSize"(i32 ptrtoint (void (i8*)* @"main.regularFunction$gowrapper" to i32), i8* undef, i8* undef)
call void @"internal/task.start"(i32 ptrtoint (void (i8*)* @"main.regularFunction$gowrapper" to i32), i8* nonnull inttoptr (i32 5 to i8*), i32 %stacksize, i8* undef, i8* null)
ret void
}
declare void @main.regularFunction(i32, i8*, i8*)
define linkonce_odr void @"main.regularFunction$gowrapper"(i8* %0) unnamed_addr #0 {
entry:
%unpack.int = ptrtoint i8* %0 to i32
call void @main.regularFunction(i32 %unpack.int, i8* undef, i8* undef)
ret void
}
declare i32 @"internal/task.getGoroutineStackSize"(i32, i8*, i8*)
declare void @"internal/task.start"(i32, i8*, i32, i8*, i8*)
define hidden void @main.inlineFunctionGoroutine(i8* %context, i8* %parentHandle) unnamed_addr {
entry:
%stacksize = call i32 @"internal/task.getGoroutineStackSize"(i32 ptrtoint (void (i8*)* @"main.inlineFunctionGoroutine$1$gowrapper" to i32), i8* undef, i8* undef)
call void @"internal/task.start"(i32 ptrtoint (void (i8*)* @"main.inlineFunctionGoroutine$1$gowrapper" to i32), i8* nonnull inttoptr (i32 5 to i8*), i32 %stacksize, i8* undef, i8* null)
ret void
}
define hidden void @"main.inlineFunctionGoroutine$1"(i32 %x, i8* %context, i8* %parentHandle) unnamed_addr {
entry:
ret void
}
define linkonce_odr void @"main.inlineFunctionGoroutine$1$gowrapper"(i8* %0) unnamed_addr #1 {
entry:
%unpack.int = ptrtoint i8* %0 to i32
call void @"main.inlineFunctionGoroutine$1"(i32 %unpack.int, i8* undef, i8* undef)
ret void
}
define hidden void @main.closureFunctionGoroutine(i8* %context, i8* %parentHandle) unnamed_addr {
entry:
%n = call i8* @runtime.alloc(i32 4, i8* undef, i8* null)
%0 = bitcast i8* %n to i32*
store i32 3, i32* %0, align 4
%1 = call i8* @runtime.alloc(i32 8, i8* undef, i8* null)
%2 = bitcast i8* %1 to i32*
store i32 5, i32* %2, align 4
%3 = getelementptr inbounds i8, i8* %1, i32 4
%4 = bitcast i8* %3 to i8**
store i8* %n, i8** %4, align 4
%stacksize = call i32 @"internal/task.getGoroutineStackSize"(i32 ptrtoint (void (i8*)* @"main.closureFunctionGoroutine$1$gowrapper" to i32), i8* undef, i8* undef)
call void @"internal/task.start"(i32 ptrtoint (void (i8*)* @"main.closureFunctionGoroutine$1$gowrapper" to i32), i8* nonnull %1, i32 %stacksize, i8* undef, i8* null)
%5 = load i32, i32* %0, align 4
call void @runtime.printint32(i32 %5, i8* undef, i8* null)
ret void
}
define hidden void @"main.closureFunctionGoroutine$1"(i32 %x, i8* %context, i8* %parentHandle) unnamed_addr {
entry:
%unpack.ptr = bitcast i8* %context to i32*
store i32 7, i32* %unpack.ptr, align 4
ret void
}
define linkonce_odr void @"main.closureFunctionGoroutine$1$gowrapper"(i8* %0) unnamed_addr #2 {
entry:
%1 = bitcast i8* %0 to i32*
%2 = load i32, i32* %1, align 4
%3 = getelementptr inbounds i8, i8* %0, i32 4
%4 = bitcast i8* %3 to i8**
%5 = load i8*, i8** %4, align 4
call void @"main.closureFunctionGoroutine$1"(i32 %2, i8* %5, i8* undef)
ret void
}
declare void @runtime.printint32(i32, i8*, i8*)
define hidden void @main.funcGoroutine(i8* %fn.context, void (i32, i8*, i8*)* %fn.funcptr, i8* %context, i8* %parentHandle) unnamed_addr {
entry:
%0 = call i8* @runtime.alloc(i32 12, i8* undef, i8* null)
%1 = bitcast i8* %0 to i32*
store i32 5, i32* %1, align 4
%2 = getelementptr inbounds i8, i8* %0, i32 4
%3 = bitcast i8* %2 to i8**
store i8* %fn.context, i8** %3, align 4
%4 = getelementptr inbounds i8, i8* %0, i32 8
%5 = bitcast i8* %4 to void (i32, i8*, i8*)**
store void (i32, i8*, i8*)* %fn.funcptr, void (i32, i8*, i8*)** %5, align 4
%stacksize = call i32 @"internal/task.getGoroutineStackSize"(i32 ptrtoint (void (i8*)* @main.funcGoroutine.gowrapper to i32), i8* undef, i8* undef)
call void @"internal/task.start"(i32 ptrtoint (void (i8*)* @main.funcGoroutine.gowrapper to i32), i8* nonnull %0, i32 %stacksize, i8* undef, i8* null)
ret void
}
define linkonce_odr void @main.funcGoroutine.gowrapper(i8* %0) unnamed_addr #3 {
entry:
%1 = bitcast i8* %0 to i32*
%2 = load i32, i32* %1, align 4
%3 = getelementptr inbounds i8, i8* %0, i32 4
%4 = bitcast i8* %3 to i8**
%5 = load i8*, i8** %4, align 4
%6 = getelementptr inbounds i8, i8* %0, i32 8
%7 = bitcast i8* %6 to void (i32, i8*, i8*)**
%8 = load void (i32, i8*, i8*)*, void (i32, i8*, i8*)** %7, align 4
call void %8(i32 %2, i8* %5, i8* undef)
ret void
}
define hidden void @main.recoverBuiltinGoroutine(i8* %context, i8* %parentHandle) unnamed_addr {
entry:
ret void
}
define hidden void @main.copyBuiltinGoroutine(i8* %dst.data, i32 %dst.len, i32 %dst.cap, i8* %src.data, i32 %src.len, i32 %src.cap, i8* %context, i8* %parentHandle) unnamed_addr {
entry:
%copy.n = call i32 @runtime.sliceCopy(i8* %dst.data, i8* %src.data, i32 %dst.len, i32 %src.len, i32 1, i8* undef, i8* null)
ret void
}
declare i32 @runtime.sliceCopy(i8* nocapture writeonly, i8* nocapture readonly, i32, i32, i32, i8*, i8*)
define hidden void @main.closeBuiltinGoroutine(%runtime.channel* dereferenceable_or_null(32) %ch, i8* %context, i8* %parentHandle) unnamed_addr {
entry:
call void @runtime.chanClose(%runtime.channel* %ch, i8* undef, i8* null)
ret void
}
declare void @runtime.chanClose(%runtime.channel* dereferenceable_or_null(32), i8*, i8*)
attributes #0 = { "tinygo-gowrapper"="main.regularFunction" }
attributes #1 = { "tinygo-gowrapper"="main.inlineFunctionGoroutine$1" }
attributes #2 = { "tinygo-gowrapper"="main.closureFunctionGoroutine$1" }
attributes #3 = { "tinygo-gowrapper" }
-106
View File
@@ -1,106 +0,0 @@
; ModuleID = 'goroutine.go'
source_filename = "goroutine.go"
target datalayout = "e-m:e-p:32:32-i64:64-n32:64-S128"
target triple = "wasm32--wasi"
%runtime.funcValueWithSignature = type { i32, i8* }
%runtime.channel = type { i32, i32, i8, %runtime.channelBlockedList*, i32, i32, i32, i8* }
%runtime.channelBlockedList = type { %runtime.channelBlockedList*, %"internal/task.Task"*, %runtime.chanSelectState*, { %runtime.channelBlockedList*, i32, i32 } }
%"internal/task.Task" = type { %"internal/task.Task"*, i8*, i64, %"internal/task.state" }
%"internal/task.state" = type { i8* }
%runtime.chanSelectState = type { %runtime.channel*, i8* }
@"main.regularFunctionGoroutine$pack" = private unnamed_addr constant { i32, i8* } { i32 5, i8* undef }
@"main.inlineFunctionGoroutine$pack" = private unnamed_addr constant { i32, i8* } { i32 5, i8* undef }
@"reflect/types.funcid:func:{basic:int}{}" = external constant i8
@"main.closureFunctionGoroutine$1$withSignature" = linkonce_odr constant %runtime.funcValueWithSignature { i32 ptrtoint (void (i32, i8*, i8*)* @"main.closureFunctionGoroutine$1" to i32), i8* @"reflect/types.funcid:func:{basic:int}{}" }
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.regularFunctionGoroutine(i8* %context, i8* %parentHandle) unnamed_addr {
entry:
call void @"internal/task.start"(i32 ptrtoint (void (i32, i8*, i8*)* @main.regularFunction to i32), i8* bitcast ({ i32, i8* }* @"main.regularFunctionGoroutine$pack" to i8*), i32 undef, i8* undef, i8* null)
ret void
}
declare void @main.regularFunction(i32, i8*, i8*)
declare void @"internal/task.start"(i32, i8*, i32, i8*, i8*)
define hidden void @main.inlineFunctionGoroutine(i8* %context, i8* %parentHandle) unnamed_addr {
entry:
call void @"internal/task.start"(i32 ptrtoint (void (i32, i8*, i8*)* @"main.inlineFunctionGoroutine$1" to i32), i8* bitcast ({ i32, i8* }* @"main.inlineFunctionGoroutine$pack" to i8*), i32 undef, i8* undef, i8* null)
ret void
}
define hidden void @"main.inlineFunctionGoroutine$1"(i32 %x, i8* %context, i8* %parentHandle) unnamed_addr {
entry:
ret void
}
define hidden void @main.closureFunctionGoroutine(i8* %context, i8* %parentHandle) unnamed_addr {
entry:
%n = call i8* @runtime.alloc(i32 4, i8* undef, i8* null)
%0 = bitcast i8* %n to i32*
store i32 3, i32* %0, align 4
%1 = call i8* @runtime.alloc(i32 8, i8* undef, i8* null)
%2 = bitcast i8* %1 to i32*
store i32 5, i32* %2, align 4
%3 = getelementptr inbounds i8, i8* %1, i32 4
%4 = bitcast i8* %3 to i8**
store i8* %n, i8** %4, align 4
call void @"internal/task.start"(i32 ptrtoint (void (i32, i8*, i8*)* @"main.closureFunctionGoroutine$1" to i32), i8* nonnull %1, i32 undef, i8* undef, i8* null)
%5 = load i32, i32* %0, align 4
call void @runtime.printint32(i32 %5, i8* undef, i8* null)
ret void
}
define hidden void @"main.closureFunctionGoroutine$1"(i32 %x, i8* %context, i8* %parentHandle) unnamed_addr {
entry:
%unpack.ptr = bitcast i8* %context to i32*
store i32 7, i32* %unpack.ptr, align 4
ret void
}
declare void @runtime.printint32(i32, i8*, i8*)
define hidden void @main.funcGoroutine(i8* %fn.context, i32 %fn.funcptr, i8* %context, i8* %parentHandle) unnamed_addr {
entry:
%0 = call i32 @runtime.getFuncPtr(i8* %fn.context, i32 %fn.funcptr, i8* nonnull @"reflect/types.funcid:func:{basic:int}{}", i8* undef, i8* null)
%1 = call i8* @runtime.alloc(i32 8, i8* undef, i8* null)
%2 = bitcast i8* %1 to i32*
store i32 5, i32* %2, align 4
%3 = getelementptr inbounds i8, i8* %1, i32 4
%4 = bitcast i8* %3 to i8**
store i8* %fn.context, i8** %4, align 4
call void @"internal/task.start"(i32 %0, i8* nonnull %1, i32 undef, i8* undef, i8* null)
ret void
}
declare i32 @runtime.getFuncPtr(i8*, i32, i8* dereferenceable_or_null(1), i8*, i8*)
define hidden void @main.recoverBuiltinGoroutine(i8* %context, i8* %parentHandle) unnamed_addr {
entry:
ret void
}
define hidden void @main.copyBuiltinGoroutine(i8* %dst.data, i32 %dst.len, i32 %dst.cap, i8* %src.data, i32 %src.len, i32 %src.cap, i8* %context, i8* %parentHandle) unnamed_addr {
entry:
%copy.n = call i32 @runtime.sliceCopy(i8* %dst.data, i8* %src.data, i32 %dst.len, i32 %src.len, i32 1, i8* undef, i8* null)
ret void
}
declare i32 @runtime.sliceCopy(i8* nocapture writeonly, i8* nocapture readonly, i32, i32, i32, i8*, i8*)
define hidden void @main.closeBuiltinGoroutine(%runtime.channel* dereferenceable_or_null(32) %ch, i8* %context, i8* %parentHandle) unnamed_addr {
entry:
call void @runtime.chanClose(%runtime.channel* %ch, i8* undef, i8* null)
ret void
}
declare void @runtime.chanClose(%runtime.channel* dereferenceable_or_null(32), i8*, i8*)
-43
View File
@@ -1,43 +0,0 @@
package main
func regularFunctionGoroutine() {
go regularFunction(5)
}
func inlineFunctionGoroutine() {
go func(x int) {
}(5)
}
func closureFunctionGoroutine() {
n := 3
go func(x int) {
n = 7
}(5)
print(n) // note: this is racy (but good enough for this test)
}
func funcGoroutine(fn func(x int)) {
go fn(5)
}
func recoverBuiltinGoroutine() {
// This is a no-op.
go recover()
}
func copyBuiltinGoroutine(dst, src []byte) {
// This is not run in a goroutine. While this copy operation can indeed take
// some time (if there is a lot of data to copy), there is no race-free way
// to make use of the result so it's unlikely applications will make use of
// it. And doing it this way should be just within the Go specification.
go copy(dst, src)
}
func closeBuiltinGoroutine(ch chan int) {
// This builtin is executed directly, not in a goroutine.
// The observed behavior is the same.
go close(ch)
}
func regularFunction(x int)
+18 -19
View File
@@ -1,27 +1,26 @@
; ModuleID = 'interface.go' ; ModuleID = 'interface.go'
source_filename = "interface.go" source_filename = "interface.go"
target datalayout = "e-m:e-p:32:32-i64:64-n32:64-S128" target datalayout = "e-m:e-p:32:32-p270:32:32-p271:32:32-p272:64:64-f64:32:64-f80:32-n8:16:32-S128"
target triple = "wasm32--wasi" target triple = "i686--linux"
%runtime.typecodeID = type { %runtime.typecodeID*, i32, %runtime.interfaceMethodInfo*, %runtime.typecodeID* } %runtime.typecodeID = type { %runtime.typecodeID*, i32, %runtime.interfaceMethodInfo* }
%runtime.interfaceMethodInfo = type { i8*, i32 } %runtime.interfaceMethodInfo = type { i8*, i32 }
%runtime._interface = type { i32, i8* } %runtime._interface = type { i32, i8* }
%runtime._string = type { i8*, i32 } %runtime._string = type { i8*, i32 }
@"reflect/types.type:basic:int" = linkonce_odr constant %runtime.typecodeID { %runtime.typecodeID* null, i32 0, %runtime.interfaceMethodInfo* null, %runtime.typecodeID* @"reflect/types.type:pointer:basic:int" } @"reflect/types.type:basic:int" = linkonce_odr constant %runtime.typecodeID zeroinitializer
@"reflect/types.type:pointer:basic:int" = linkonce_odr constant %runtime.typecodeID { %runtime.typecodeID* @"reflect/types.type:basic:int", i32 0, %runtime.interfaceMethodInfo* null, %runtime.typecodeID* null } @"reflect/types.type:pointer:basic:int" = linkonce_odr constant %runtime.typecodeID { %runtime.typecodeID* @"reflect/types.type:basic:int", i32 0, %runtime.interfaceMethodInfo* null }
@"reflect/types.type:pointer:named:error" = linkonce_odr constant %runtime.typecodeID { %runtime.typecodeID* @"reflect/types.type:named:error", i32 0, %runtime.interfaceMethodInfo* null, %runtime.typecodeID* null } @"reflect/types.type:pointer:named:error" = linkonce_odr constant %runtime.typecodeID { %runtime.typecodeID* @"reflect/types.type:named:error", i32 0, %runtime.interfaceMethodInfo* null }
@"reflect/types.type:named:error" = linkonce_odr constant %runtime.typecodeID { %runtime.typecodeID* @"reflect/types.type:interface:{Error:func:{}{basic:string}}", i32 0, %runtime.interfaceMethodInfo* null, %runtime.typecodeID* @"reflect/types.type:pointer:named:error" } @"reflect/types.type:named:error" = linkonce_odr constant %runtime.typecodeID { %runtime.typecodeID* @"reflect/types.type:interface:{Error:func:{}{basic:string}}", i32 0, %runtime.interfaceMethodInfo* null }
@"reflect/types.type:interface:{Error:func:{}{basic:string}}" = linkonce_odr constant %runtime.typecodeID { %runtime.typecodeID* bitcast ([1 x i8*]* @"reflect/types.interface:interface{Error() string}$interface" to %runtime.typecodeID*), i32 0, %runtime.interfaceMethodInfo* null, %runtime.typecodeID* @"reflect/types.type:pointer:interface:{Error:func:{}{basic:string}}" } @"reflect/types.type:interface:{Error:func:{}{basic:string}}" = linkonce_odr constant %runtime.typecodeID { %runtime.typecodeID* bitcast ([1 x i8*]* @"reflect/types.interface:interface{Error() string}$interface" to %runtime.typecodeID*), i32 0, %runtime.interfaceMethodInfo* null }
@"reflect/methods.Error() string" = linkonce_odr constant i8 0, align 1 @"func Error() string" = external constant i8
@"reflect/types.interface:interface{Error() string}$interface" = linkonce_odr constant [1 x i8*] [i8* @"reflect/methods.Error() string"] @"reflect/types.interface:interface{Error() string}$interface" = linkonce_odr constant [1 x i8*] [i8* @"func Error() string"]
@"reflect/types.type:pointer:interface:{Error:func:{}{basic:string}}" = linkonce_odr constant %runtime.typecodeID { %runtime.typecodeID* @"reflect/types.type:interface:{Error:func:{}{basic:string}}", i32 0, %runtime.interfaceMethodInfo* null, %runtime.typecodeID* null } @"reflect/types.type:pointer:interface:{String:func:{}{basic:string}}" = linkonce_odr constant %runtime.typecodeID { %runtime.typecodeID* @"reflect/types.type:interface:{String:func:{}{basic:string}}", i32 0, %runtime.interfaceMethodInfo* null }
@"reflect/types.type:pointer:interface:{String:func:{}{basic:string}}" = linkonce_odr constant %runtime.typecodeID { %runtime.typecodeID* @"reflect/types.type:interface:{String:func:{}{basic:string}}", i32 0, %runtime.interfaceMethodInfo* null, %runtime.typecodeID* null } @"reflect/types.type:interface:{String:func:{}{basic:string}}" = linkonce_odr constant %runtime.typecodeID { %runtime.typecodeID* bitcast ([1 x i8*]* @"reflect/types.interface:interface{String() string}$interface" to %runtime.typecodeID*), i32 0, %runtime.interfaceMethodInfo* null }
@"reflect/types.type:interface:{String:func:{}{basic:string}}" = linkonce_odr constant %runtime.typecodeID { %runtime.typecodeID* bitcast ([1 x i8*]* @"reflect/types.interface:interface{String() string}$interface" to %runtime.typecodeID*), i32 0, %runtime.interfaceMethodInfo* null, %runtime.typecodeID* @"reflect/types.type:pointer:interface:{String:func:{}{basic:string}}" } @"func String() string" = external constant i8
@"reflect/methods.String() string" = linkonce_odr constant i8 0, align 1 @"reflect/types.interface:interface{String() string}$interface" = linkonce_odr constant [1 x i8*] [i8* @"func String() string"]
@"reflect/types.interface:interface{String() string}$interface" = linkonce_odr constant [1 x i8*] [i8* @"reflect/methods.String() string"] @"reflect/types.type:basic:int$id" = external constant i8
@"reflect/types.typeid:basic:int" = external constant i8 @"error$interface" = linkonce_odr constant [1 x i8*] [i8* @"func Error() string"]
@"error$interface" = linkonce_odr constant [1 x i8*] [i8* @"reflect/methods.Error() string"]
declare noalias nonnull i8* @runtime.alloc(i32, i8*, i8*) declare noalias nonnull i8* @runtime.alloc(i32, i8*, i8*)
@@ -52,7 +51,7 @@ entry:
define hidden i1 @main.isInt(i32 %itf.typecode, i8* %itf.value, i8* %context, i8* %parentHandle) unnamed_addr { define hidden i1 @main.isInt(i32 %itf.typecode, i8* %itf.value, i8* %context, i8* %parentHandle) unnamed_addr {
entry: entry:
%typecode = call i1 @runtime.typeAssert(i32 %itf.typecode, i8* nonnull @"reflect/types.typeid:basic:int", i8* undef, i8* null) %typecode = call i1 @runtime.typeAssert(i32 %itf.typecode, i8* nonnull @"reflect/types.type:basic:int$id", i8* undef, i8* null)
br i1 %typecode, label %typeassert.ok, label %typeassert.next br i1 %typecode, label %typeassert.ok, label %typeassert.next
typeassert.ok: ; preds = %entry typeassert.ok: ; preds = %entry
@@ -92,7 +91,7 @@ typeassert.next: ; preds = %typeassert.ok, %ent
define hidden %runtime._string @main.callErrorMethod(i32 %itf.typecode, i8* %itf.value, i8* %context, i8* %parentHandle) unnamed_addr { define hidden %runtime._string @main.callErrorMethod(i32 %itf.typecode, i8* %itf.value, i8* %context, i8* %parentHandle) unnamed_addr {
entry: 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 @"reflect/methods.Error() string", i8* undef, i8* null) %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*)* %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) %0 = call %runtime._string %invoke.func.cast(i8* %itf.value, i8* undef, i8* undef)
ret %runtime._string %0 ret %runtime._string %0
-27
View File
@@ -1,27 +0,0 @@
; ModuleID = 'intrinsics.go'
source_filename = "intrinsics.go"
target datalayout = "e-m:e-p:32:32-Fi8-i64:64-v128:64:128-a:0:32-n32-S64"
target triple = "armv7m-none-eabi"
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 double @main.mySqrt(double %x, i8* %context, i8* %parentHandle) unnamed_addr {
entry:
%0 = call double @math.Sqrt(double %x, i8* undef, i8* undef)
ret double %0
}
declare double @math.Sqrt(double, i8*, i8*)
define hidden double @main.myTrunc(double %x, i8* %context, i8* %parentHandle) unnamed_addr {
entry:
%0 = call double @math.Trunc(double %x, i8* undef, i8* undef)
ret double %0
}
declare double @math.Trunc(double, i8*, i8*)
-31
View File
@@ -1,31 +0,0 @@
; ModuleID = 'intrinsics.go'
source_filename = "intrinsics.go"
target datalayout = "e-m:e-p:32:32-i64:64-n32:64-S128"
target triple = "wasm32--wasi"
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 double @main.mySqrt(double %x, i8* %context, i8* %parentHandle) unnamed_addr {
entry:
%0 = call double @llvm.sqrt.f64(double %x)
ret double %0
}
; Function Attrs: nounwind readnone speculatable willreturn
declare double @llvm.sqrt.f64(double) #0
define hidden double @main.myTrunc(double %x, i8* %context, i8* %parentHandle) unnamed_addr {
entry:
%0 = call double @llvm.trunc.f64(double %x)
ret double %0
}
; Function Attrs: nounwind readnone speculatable willreturn
declare double @llvm.trunc.f64(double) #0
attributes #0 = { nounwind readnone speculatable willreturn }
-14
View File
@@ -1,14 +0,0 @@
package main
// Test how intrinsics are lowered: either as regular calls to the math
// functions or as LLVM builtins (such as llvm.sqrt.f64).
import "math"
func mySqrt(x float64) float64 {
return math.Sqrt(x)
}
func myTrunc(x float64) float64 {
return math.Trunc(x)
}
+2 -2
View File
@@ -1,7 +1,7 @@
; ModuleID = 'pointer.go' ; ModuleID = 'pointer.go'
source_filename = "pointer.go" source_filename = "pointer.go"
target datalayout = "e-m:e-p:32:32-i64:64-n32:64-S128" target datalayout = "e-m:e-p:32:32-p270:32:32-p271:32:32-p272:64:64-f64:32:64-f80:32-n8:16:32-S128"
target triple = "wasm32--wasi" target triple = "i686--linux"
declare noalias nonnull i8* @runtime.alloc(i32, i8*, i8*) declare noalias nonnull i8* @runtime.alloc(i32, i8*, i8*)
-66
View File
@@ -1,66 +0,0 @@
package main
import _ "unsafe"
// Creates an external global with name extern_global.
//go:extern extern_global
var externGlobal [0]byte
// Creates a
//go:align 32
var alignedGlobal [4]uint32
// Test conflicting pragmas (the last one counts).
//go:align 64
//go:align 16
var alignedGlobal16 [4]uint32
// Test exported functions.
//export extern_func
func externFunc() {
}
// Define a function in a different package using go:linkname.
//go:linkname withLinkageName1 somepkg.someFunction1
func withLinkageName1() {
}
// Import a function from a different package using go:linkname.
//go:linkname withLinkageName2 somepkg.someFunction2
func withLinkageName2()
// Function has an 'inline hint', similar to the inline keyword in C.
//go:inline
func inlineFunc() {
}
// Function should never be inlined, equivalent to GCC
// __attribute__((noinline)).
//go:noinline
func noinlineFunc() {
}
// This function should have the specified section.
//go:section .special_function_section
func functionInSection() {
}
//export exportedFunctionInSection
//go:section .special_function_section
func exportedFunctionInSection() {
}
// This function should not: it's only a declaration and not a definition.
//go:section .special_function_section
func undefinedFunctionNotInSection()
//go:section .special_global_section
var globalInSection uint32
//go:section .special_global_section
//go:extern undefinedGlobalNotInSection
var undefinedGlobalNotInSection uint32
//go:align 1024
//go:section .global_section
var multipleGlobalPragmas uint32
-59
View File
@@ -1,59 +0,0 @@
; ModuleID = 'pragma.go'
source_filename = "pragma.go"
target datalayout = "e-m:e-p:32:32-i64:64-n32:64-S128"
target triple = "wasm32--wasi"
@extern_global = external global [0 x i8], align 1
@main.alignedGlobal = hidden global [4 x i32] zeroinitializer, align 32
@main.alignedGlobal16 = hidden global [4 x i32] zeroinitializer, align 16
@main.globalInSection = hidden global i32 0, section ".special_global_section", align 4
@undefinedGlobalNotInSection = external global i32, align 4
@main.multipleGlobalPragmas = hidden global i32 0, section ".global_section", align 1024
declare noalias nonnull i8* @runtime.alloc(i32, i8*, i8*)
define hidden void @main.init(i8* %context, i8* %parentHandle) unnamed_addr {
entry:
ret void
}
define void @extern_func() #0 {
entry:
ret void
}
define hidden void @somepkg.someFunction1(i8* %context, i8* %parentHandle) unnamed_addr {
entry:
ret void
}
declare void @somepkg.someFunction2(i8*, i8*)
; Function Attrs: inlinehint
define hidden void @main.inlineFunc(i8* %context, i8* %parentHandle) unnamed_addr #1 {
entry:
ret void
}
; Function Attrs: noinline
define hidden void @main.noinlineFunc(i8* %context, i8* %parentHandle) unnamed_addr #2 {
entry:
ret void
}
define hidden void @main.functionInSection(i8* %context, i8* %parentHandle) unnamed_addr section ".special_function_section" {
entry:
ret void
}
define void @exportedFunctionInSection() #3 section ".special_function_section" {
entry:
ret void
}
declare void @main.undefinedFunctionNotInSection(i8*, i8*)
attributes #0 = { "wasm-export-name"="extern_func" }
attributes #1 = { inlinehint }
attributes #2 = { noinline }
attributes #3 = { "wasm-export-name"="exportedFunctionInSection" }
-18
View File
@@ -23,21 +23,3 @@ func sliceAppendSlice(ints, added []int) []int {
func sliceCopy(dst, src []int) int { func sliceCopy(dst, src []int) int {
return copy(dst, src) return copy(dst, src)
} }
// Test bounds checking in *ssa.MakeSlice instruction.
func makeByteSlice(len int) []byte {
return make([]byte, len)
}
func makeInt16Slice(len int) []int16 {
return make([]int16, len)
}
func makeArraySlice(len int) [][3]byte {
return make([][3]byte, len) // slice with element size of 3
}
func makeInt32Slice(len int) []int32 {
return make([]int32, len)
}
+4 -80
View File
@@ -1,7 +1,7 @@
; ModuleID = 'slice.go' ; ModuleID = 'slice.go'
source_filename = "slice.go" source_filename = "slice.go"
target datalayout = "e-m:e-p:32:32-i64:64-n32:64-S128" target datalayout = "e-m:e-p:32:32-p270:32:32-p271:32:32-p272:64:64-f64:32:64-f80:32-n8:16:32-S128"
target triple = "wasm32--wasi" target triple = "i686--linux"
declare noalias nonnull i8* @runtime.alloc(i32, i8*, i8*) declare noalias nonnull i8* @runtime.alloc(i32, i8*, i8*)
@@ -60,7 +60,7 @@ entry:
ret { i32*, i32, i32 } %7 ret { i32*, i32, i32 } %7
} }
declare { i8*, i32, i32 } @runtime.sliceAppend(i8*, i8* nocapture readonly, i32, i32, i32, i32, i8*, i8*) declare { i8*, i32, i32 } @runtime.sliceAppend(i8*, i8*, i32, i32, i32, i32, i8*, i8*)
define hidden { i32*, i32, i32 } @main.sliceAppendSlice(i32* %ints.data, i32 %ints.len, i32 %ints.cap, i32* %added.data, i32 %added.len, i32 %added.cap, i8* %context, i8* %parentHandle) unnamed_addr { define hidden { i32*, i32, i32 } @main.sliceAppendSlice(i32* %ints.data, i32 %ints.len, i32 %ints.cap, i32* %added.data, i32 %added.len, i32 %added.cap, i8* %context, i8* %parentHandle) unnamed_addr {
entry: entry:
@@ -85,80 +85,4 @@ entry:
ret i32 %copy.n ret i32 %copy.n
} }
declare i32 @runtime.sliceCopy(i8* nocapture writeonly, i8* nocapture readonly, i32, i32, i32, i8*, i8*) declare i32 @runtime.sliceCopy(i8*, i8*, i32, i32, i32, i8*, i8*)
define hidden { i8*, i32, i32 } @main.makeByteSlice(i32 %len, i8* %context, i8* %parentHandle) unnamed_addr {
entry:
%slice.maxcap = icmp slt i32 %len, 0
br i1 %slice.maxcap, label %slice.throw, label %slice.next
slice.throw: ; preds = %entry
call void @runtime.slicePanic(i8* undef, i8* null)
unreachable
slice.next: ; preds = %entry
%makeslice.buf = call i8* @runtime.alloc(i32 %len, i8* undef, i8* null)
%0 = insertvalue { i8*, i32, i32 } undef, i8* %makeslice.buf, 0
%1 = insertvalue { i8*, i32, i32 } %0, i32 %len, 1
%2 = insertvalue { i8*, i32, i32 } %1, i32 %len, 2
ret { i8*, i32, i32 } %2
}
declare void @runtime.slicePanic(i8*, i8*)
define hidden { i16*, i32, i32 } @main.makeInt16Slice(i32 %len, i8* %context, i8* %parentHandle) unnamed_addr {
entry:
%slice.maxcap = icmp slt i32 %len, 0
br i1 %slice.maxcap, label %slice.throw, label %slice.next
slice.throw: ; preds = %entry
call void @runtime.slicePanic(i8* undef, i8* null)
unreachable
slice.next: ; preds = %entry
%makeslice.cap = shl i32 %len, 1
%makeslice.buf = call i8* @runtime.alloc(i32 %makeslice.cap, i8* undef, i8* null)
%makeslice.array = bitcast i8* %makeslice.buf to i16*
%0 = insertvalue { i16*, i32, i32 } undef, i16* %makeslice.array, 0
%1 = insertvalue { i16*, i32, i32 } %0, i32 %len, 1
%2 = insertvalue { i16*, i32, i32 } %1, i32 %len, 2
ret { i16*, i32, i32 } %2
}
define hidden { [3 x i8]*, i32, i32 } @main.makeArraySlice(i32 %len, i8* %context, i8* %parentHandle) unnamed_addr {
entry:
%slice.maxcap = icmp ugt i32 %len, 1431655765
br i1 %slice.maxcap, label %slice.throw, label %slice.next
slice.throw: ; preds = %entry
call void @runtime.slicePanic(i8* undef, i8* null)
unreachable
slice.next: ; preds = %entry
%makeslice.cap = mul i32 %len, 3
%makeslice.buf = call i8* @runtime.alloc(i32 %makeslice.cap, i8* undef, i8* null)
%makeslice.array = bitcast i8* %makeslice.buf to [3 x i8]*
%0 = insertvalue { [3 x i8]*, i32, i32 } undef, [3 x i8]* %makeslice.array, 0
%1 = insertvalue { [3 x i8]*, i32, i32 } %0, i32 %len, 1
%2 = insertvalue { [3 x i8]*, i32, i32 } %1, i32 %len, 2
ret { [3 x i8]*, i32, i32 } %2
}
define hidden { i32*, i32, i32 } @main.makeInt32Slice(i32 %len, i8* %context, i8* %parentHandle) unnamed_addr {
entry:
%slice.maxcap = icmp ugt i32 %len, 1073741823
br i1 %slice.maxcap, label %slice.throw, label %slice.next
slice.throw: ; preds = %entry
call void @runtime.slicePanic(i8* undef, i8* null)
unreachable
slice.next: ; preds = %entry
%makeslice.cap = shl i32 %len, 2
%makeslice.buf = call i8* @runtime.alloc(i32 %makeslice.cap, i8* undef, i8* null)
%makeslice.array = bitcast i8* %makeslice.buf to i32*
%0 = insertvalue { i32*, i32, i32 } undef, i32* %makeslice.array, 0
%1 = insertvalue { i32*, i32, i32 } %0, i32 %len, 1
%2 = insertvalue { i32*, i32, i32 } %1, i32 %len, 2
ret { i32*, i32, i32 } %2
}
-8
View File
@@ -1,13 +1,5 @@
package main package main
func someString() string {
return "foo"
}
func zeroLengthString() string {
return ""
}
func stringLen(s string) int { func stringLen(s string) int {
return len(s) return len(s)
} }
+2 -16
View File
@@ -1,11 +1,7 @@
; ModuleID = 'string.go' ; ModuleID = 'string.go'
source_filename = "string.go" source_filename = "string.go"
target datalayout = "e-m:e-p:32:32-i64:64-n32:64-S128" target datalayout = "e-m:e-p:32:32-p270:32:32-p271:32:32-p272:64:64-f64:32:64-f80:32-n8:16:32-S128"
target triple = "wasm32--wasi" 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*) declare noalias nonnull i8* @runtime.alloc(i32, i8*, i8*)
@@ -14,16 +10,6 @@ entry:
ret void 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 { define hidden i32 @main.stringLen(i8* %s.data, i32 %s.len, i8* %context, i8* %parentHandle) unnamed_addr {
entry: entry:
ret i32 %s.len ret i32 %s.len
+4 -4
View File
@@ -1,6 +1,6 @@
module github.com/tinygo-org/tinygo module github.com/tinygo-org/tinygo
go 1.15 go 1.13
require ( require (
github.com/blakesmith/ar v0.0.0-20150311145944-8bd4349a67f2 github.com/blakesmith/ar v0.0.0-20150311145944-8bd4349a67f2
@@ -10,7 +10,7 @@ require (
github.com/marcinbor85/gohex v0.0.0-20200531091804-343a4b548892 github.com/marcinbor85/gohex v0.0.0-20200531091804-343a4b548892
github.com/mattn/go-colorable v0.1.8 github.com/mattn/go-colorable v0.1.8
go.bug.st/serial v1.1.2 go.bug.st/serial v1.1.2
golang.org/x/sys v0.0.0-20210510120138-977fb7262007 golang.org/x/sys v0.0.0-20210113181707-4bcb84eeeb78
golang.org/x/tools v0.1.6-0.20210813165731-45389f592fe9 golang.org/x/tools v0.0.0-20200216192241-b320d3a0f5a2
tinygo.org/x/go-llvm v0.0.0-20210325115028-e7b85195e81c tinygo.org/x/go-llvm v0.0.0-20210308112806-9ef958b6bed4
) )
+6 -19
View File
@@ -30,8 +30,6 @@ github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Ky
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/yuin/goldmark v1.3.5 h1:dPmz1Snjq0kmkz159iL7S6WzdahUTHnHB5M56WFVifs=
github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
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 h1:6xDpbta8KJ+VLRTeM8ghhxXRMLE/Lr8h9iDKwydarAY=
@@ -40,35 +38,24 @@ golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACk
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee h1:WG0RUwxtNT4qqaXX3DPA8zHFNm/D9xaBpxzHt1WcA/E= golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee h1:WG0RUwxtNT4qqaXX3DPA8zHFNm/D9xaBpxzHt1WcA/E=
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191128015809-6d18c012aee9 h1:ZBzSG/7F4eNKz2L3GE9o300RX0Az1Bw5HF7PDraD+qU=
golang.org/x/sys v0.0.0-20191128015809-6d18c012aee9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200909081042-eff7692f9009/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200909081042-eff7692f9009/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210113181707-4bcb84eeeb78 h1:nVuTkr9L6Bq62qpUqKo/RnZCFfzDBL0bYo6w9OJUqZY=
golang.org/x/sys v0.0.0-20210113181707-4bcb84eeeb78/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210113181707-4bcb84eeeb78/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210510120138-977fb7262007 h1:gG67DSER+11cZvqIMb8S8bt0vZtiN6xWYARwirrOSfE=
golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/tools v0.0.0-20200216192241-b320d3a0f5a2 h1:0sfSpGSa544Fwnbot3Oxq/U6SXqjty6Jy/3wRhVS7ig=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20200216192241-b320d3a0f5a2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898 h1:/atklqdjdhuosWIl6AIbOeHJjicWYPqR9bpxqxYG2pA=
golang.org/x/tools v0.1.6-0.20210813165731-45389f592fe9 h1:nvvuMxmx1q0gfRki3T0hjG8EwAcVCs91oWAXvyt4zhI=
golang.org/x/tools v0.1.6-0.20210813165731-45389f592fe9/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
tinygo.org/x/go-llvm v0.0.0-20210308112806-9ef958b6bed4 h1:CMUHxVTb+UuUePuMf8vkWjZ3gTp9BBK91KrgOCwoNHs= tinygo.org/x/go-llvm v0.0.0-20210308112806-9ef958b6bed4 h1:CMUHxVTb+UuUePuMf8vkWjZ3gTp9BBK91KrgOCwoNHs=
tinygo.org/x/go-llvm v0.0.0-20210308112806-9ef958b6bed4/go.mod h1:fv1F0BSNpxMfCL0zF3M4OPFbgYHnhtB6ST0HvUtu/LE= tinygo.org/x/go-llvm v0.0.0-20210308112806-9ef958b6bed4/go.mod h1:fv1F0BSNpxMfCL0zF3M4OPFbgYHnhtB6ST0HvUtu/LE=
tinygo.org/x/go-llvm v0.0.0-20210325115028-e7b85195e81c h1:vn9IPshzYmzZis10UEVrsIBRv9FpykADw6M3/tHHROg=
tinygo.org/x/go-llvm v0.0.0-20210325115028-e7b85195e81c/go.mod h1:fv1F0BSNpxMfCL0zF3M4OPFbgYHnhtB6ST0HvUtu/LE=
+2 -3
View File
@@ -171,9 +171,8 @@ func getGoroot() string {
switch runtime.GOOS { switch runtime.GOOS {
case "linux": case "linux":
candidates = []string{ candidates = []string{
"/usr/local/go", // manually installed "/usr/local/go", // manually installed
"/usr/lib/go", // from the distribution "/usr/lib/go", // from the distribution
"/snap/go/current/", // installed using snap
} }
case "darwin": case "darwin":
candidates = []string{ candidates = []string{
+5 -5
View File
@@ -12,7 +12,7 @@ import (
// Version of TinyGo. // Version of TinyGo.
// Update this value before release of new version of software. // Update this value before release of new version of software.
const Version = "0.20.0" const Version = "0.18.0-dev"
// GetGorootVersion returns the major and minor version for a given GOROOT path. // GetGorootVersion returns the major and minor version for a given GOROOT path.
// If the goroot cannot be determined, (0, 0) is returned. // If the goroot cannot be determined, (0, 0) is returned.
@@ -48,10 +48,7 @@ func GetGorootVersion(goroot string) (major, minor int, err error) {
// toolchain for the given GOROOT path. It is usually of the form `go1.x.y` but // toolchain for the given GOROOT path. It is usually of the form `go1.x.y` but
// can have some variations (for beta releases, for example). // can have some variations (for beta releases, for example).
func GorootVersionString(goroot string) (string, error) { func GorootVersionString(goroot string) (string, error) {
if data, err := ioutil.ReadFile(filepath.Join(goroot, "VERSION")); err == nil { if data, err := ioutil.ReadFile(filepath.Join(
return string(data), nil
} else if data, err := ioutil.ReadFile(filepath.Join(
goroot, "src", "runtime", "internal", "sys", "zversion.go")); err == nil { goroot, "src", "runtime", "internal", "sys", "zversion.go")); err == nil {
r := regexp.MustCompile("const TheVersion = `(.*)`") r := regexp.MustCompile("const TheVersion = `(.*)`")
@@ -62,6 +59,9 @@ func GorootVersionString(goroot string) (string, error) {
return string(matches[1]), nil return string(matches[1]), nil
} else if data, err := ioutil.ReadFile(filepath.Join(goroot, "VERSION")); err == nil {
return string(data), nil
} else { } else {
return "", err return "", err
} }
+1 -17
View File
@@ -25,7 +25,6 @@ type function struct {
// basicBlock represents a LLVM basic block and contains a slice of // basicBlock represents a LLVM basic block and contains a slice of
// instructions. The last instruction must be a terminator instruction. // instructions. The last instruction must be a terminator instruction.
type basicBlock struct { type basicBlock struct {
phiNodes []instruction
instructions []instruction instructions []instruction
} }
@@ -136,15 +135,6 @@ func (r *runner) compileFunction(llvmFn llvm.Value) *function {
default: default:
panic("unknown number of operands") panic("unknown number of operands")
} }
case llvm.Switch:
// A switch is an array of (value, label) pairs, of which the
// first one indicates the to-switch value and the default
// label.
numOperands := llvmInst.OperandsCount()
for i := 0; i < numOperands; i += 2 {
inst.operands = append(inst.operands, r.getValue(llvmInst.Operand(i)))
inst.operands = append(inst.operands, literalValue{uint32(blockIndices[llvmInst.Operand(i+1)])})
}
case llvm.PHI: case llvm.PHI:
inst.name = llvmInst.Name() inst.name = llvmInst.Name()
incomingCount := inst.llvmInst.IncomingCount() incomingCount := inst.llvmInst.IncomingCount()
@@ -347,13 +337,7 @@ func (r *runner) compileFunction(llvmFn llvm.Value) *function {
// This error is handled when actually trying to interpret this // This error is handled when actually trying to interpret this
// instruction (to not trigger on code that won't be executed). // instruction (to not trigger on code that won't be executed).
} }
if inst.opcode == llvm.PHI { bb.instructions = append(bb.instructions, inst)
// PHI nodes need to be treated specially, see the comment in
// interpreter.go for an explanation.
bb.phiNodes = append(bb.phiNodes, inst)
} else {
bb.instructions = append(bb.instructions, inst)
}
} }
} }
return fn return fn
-1
View File
@@ -57,7 +57,6 @@ func (r *runner) errorAt(inst instruction, err error) *Error {
pos := getPosition(inst.llvmInst) pos := getPosition(inst.llvmInst)
return &Error{ return &Error{
ImportPath: r.pkgName, ImportPath: r.pkgName,
Inst: inst.llvmInst,
Pos: pos, Pos: pos,
Err: err, Err: err,
Traceback: []ErrorLine{{pos, inst.llvmInst}}, Traceback: []ErrorLine{{pos, inst.llvmInst}},
+3 -34
View File
@@ -15,7 +15,7 @@ import (
// package is changed in a way that affects the output so that cached package // package is changed in a way that affects the output so that cached package
// builds will be invalidated. // builds will be invalidated.
// This version is independent of the TinyGo version number. // This version is independent of the TinyGo version number.
const Version = 2 // last change: fix GEP on untyped pointers const Version = 1
// Enable extra checks, which should be disabled by default. // Enable extra checks, which should be disabled by default.
// This may help track down bugs by adding a few more sanity checks. // This may help track down bugs by adding a few more sanity checks.
@@ -110,26 +110,17 @@ func Run(mod llvm.Module, debug bool) error {
fmt.Fprintln(os.Stderr, "call:", fn.Name()) fmt.Fprintln(os.Stderr, "call:", fn.Name())
} }
_, mem, callErr := r.run(r.getFunction(fn), nil, nil, " ") _, mem, callErr := r.run(r.getFunction(fn), nil, nil, " ")
call.EraseFromParentAsInstruction()
if callErr != nil { if callErr != nil {
if isRecoverableError(callErr.Err) { if isRecoverableError(callErr.Err) {
if r.debug { if r.debug {
fmt.Fprintln(os.Stderr, "not interpreting", r.pkgName, "because of error:", callErr.Error()) fmt.Fprintln(os.Stderr, "not interpreting", r.pkgName, "because of error:", callErr.Error())
} }
// Remove instructions that were created as part of interpreting
// the package.
mem.revert() mem.revert()
// Create a call to the package initializer (which was
// previously deleted).
i8undef := llvm.Undef(r.i8ptrType)
r.builder.CreateCall(fn, []llvm.Value{i8undef, i8undef}, "")
// Make sure that any globals touched by the package
// initializer, won't be accessed by later package initializers.
r.markExternalLoad(fn)
continue continue
} }
return callErr return callErr
} }
call.EraseFromParentAsInstruction()
for index, obj := range mem.objects { for index, obj := range mem.objects {
r.objects[index] = obj r.objects[index] = obj
} }
@@ -138,7 +129,7 @@ func Run(mod llvm.Module, debug bool) error {
// Update all global variables in the LLVM module. // Update all global variables in the LLVM module.
mem := memoryView{r: r} mem := memoryView{r: r}
for i, obj := range r.objects { for _, obj := range r.objects {
if obj.llvmGlobal.IsNil() { if obj.llvmGlobal.IsNil() {
continue continue
} }
@@ -168,12 +159,6 @@ func Run(mod llvm.Module, debug bool) error {
name := obj.llvmGlobal.Name() name := obj.llvmGlobal.Name()
obj.llvmGlobal.EraseFromParentAsGlobal() obj.llvmGlobal.EraseFromParentAsGlobal()
newGlobal.SetName(name) newGlobal.SetName(name)
// Update interp-internal references.
delete(r.globals, obj.llvmGlobal)
obj.llvmGlobal = newGlobal
r.globals[newGlobal] = i
r.objects[i] = obj
continue continue
} }
if err != nil { if err != nil {
@@ -279,19 +264,3 @@ func (r *runner) getFunction(llvmFn llvm.Value) *function {
r.functionCache[llvmFn] = fn r.functionCache[llvmFn] = fn
return fn return fn
} }
// markExternalLoad marks the given llvmValue as being loaded externally. This
// is primarily used to mark package initializers that could not be run at
// compile time. As an example, a package initialize might store to a global
// variable. Another package initializer might read from the same global
// variable. By marking this function as being run at runtime, that load
// instruction will need to be run at runtime instead of at compile time.
func (r *runner) markExternalLoad(llvmValue llvm.Value) {
mem := memoryView{r: r}
mem.markExternalLoad(llvmValue)
for index, obj := range mem.objects {
if obj.marked > r.objects[index].marked {
r.objects[index].marked = obj.marked
}
}
}
+1 -2
View File
@@ -13,11 +13,10 @@ import (
func TestInterp(t *testing.T) { func TestInterp(t *testing.T) {
for _, name := range []string{ for _, name := range []string{
"basic", "basic",
"phi",
"slice-copy", "slice-copy",
"consteval", "consteval",
"map",
"interface", "interface",
"revert",
} { } {
name := name // make tc local to this closure name := name // make tc local to this closure
t.Run(name, func(t *testing.T) { t.Run(name, func(t *testing.T) {
+96 -84
View File
@@ -5,7 +5,6 @@ import (
"fmt" "fmt"
"math" "math"
"os" "os"
"strconv"
"strings" "strings"
"time" "time"
@@ -34,50 +33,6 @@ func (r *runner) run(fn *function, params []value, parentMem *memoryView, indent
lastBB := -1 // last basic block is undefined, only defined after a branch lastBB := -1 // last basic block is undefined, only defined after a branch
var operands []value var operands []value
for instIndex := 0; instIndex < len(bb.instructions); instIndex++ { for instIndex := 0; instIndex < len(bb.instructions); instIndex++ {
if instIndex == 0 {
// This is the start of a new basic block.
// There may be PHI nodes that need to be resolved. Resolve all PHI
// nodes before continuing with regular instructions.
// PHI nodes need to be treated specially because they can have a
// mutual dependency:
// for.loop:
// %a = phi i8 [ 1, %entry ], [ %b, %for.loop ]
// %b = phi i8 [ 3, %entry ], [ %a, %for.loop ]
// If these PHI nodes are processed like a regular instruction, %a
// and %b are both 3 on the second iteration of the loop because %b
// loads the value of %a from the second iteration, while it should
// load the value from the previous iteration. The correct behavior
// is that these two values swap each others place on each
// iteration.
var phiValues []value
var phiIndices []int
for _, inst := range bb.phiNodes {
var result value
for i := 0; i < len(inst.operands); i += 2 {
if int(inst.operands[i].(literalValue).value.(uint32)) == lastBB {
incoming := inst.operands[i+1]
if local, ok := incoming.(localValue); ok {
result = locals[fn.locals[local.value]]
} else {
result = incoming
}
break
}
}
if r.debug {
fmt.Fprintln(os.Stderr, indent+"phi", inst.operands, "->", result)
}
if result == nil {
panic("could not find PHI input")
}
phiValues = append(phiValues, result)
phiIndices = append(phiIndices, inst.localIndex)
}
for i, value := range phiValues {
locals[phiIndices[i]] = value
}
}
inst := bb.instructions[instIndex] inst := bb.instructions[instIndex]
operands = operands[:0] operands = operands[:0]
isRuntimeInst := false isRuntimeInst := false
@@ -148,24 +103,27 @@ func (r *runner) run(fn *function, params []value, parentMem *memoryView, indent
default: default:
panic("unknown operands length") panic("unknown operands length")
} }
case llvm.Switch: break // continue with next block
// Switch statement: [value, defaultLabel, case0, label0, case1, label1, ...] case llvm.PHI:
value := operands[0].Uint() var result value
targetLabel := operands[1].Uint() // default label for i := 0; i < len(inst.operands); i += 2 {
// Do a lazy switch by iterating over all cases. if int(inst.operands[i].(literalValue).value.(uint32)) == lastBB {
for i := 2; i < len(operands); i += 2 { incoming := inst.operands[i+1]
if value == operands[i].Uint() { if local, ok := incoming.(localValue); ok {
targetLabel = operands[i+1].Uint() result = locals[fn.locals[local.value]]
} else {
result = incoming
}
break break
} }
} }
lastBB = currentBB
currentBB = int(targetLabel)
bb = fn.blocks[currentBB]
instIndex = -1 // start at 0 the next cycle
if r.debug { if r.debug {
fmt.Fprintln(os.Stderr, indent+"switch", operands, "->", currentBB) fmt.Fprintln(os.Stderr, indent+"phi", inst.operands, "->", result)
} }
if result == nil {
panic("could not find PHI input")
}
locals[inst.localIndex] = result
case llvm.Select: case llvm.Select:
// Select is much like a ternary operator: it picks a result from // Select is much like a ternary operator: it picks a result from
// the second and third operand based on the boolean first operand. // the second and third operand based on the boolean first operand.
@@ -197,7 +155,7 @@ func (r *runner) run(fn *function, params []value, parentMem *memoryView, indent
// which case this call won't even get to this point but will // which case this call won't even get to this point but will
// already be emitted in initAll. // already be emitted in initAll.
continue continue
case strings.HasPrefix(callFn.name, "runtime.print") || callFn.name == "runtime._panic" || callFn.name == "runtime.hashmapGet" || callFn.name == "os.runtime_args": case strings.HasPrefix(callFn.name, "runtime.print") || callFn.name == "runtime._panic" || callFn.name == "runtime.hashmapGet":
// These functions should be run at runtime. Specifically: // These functions should be run at runtime. Specifically:
// * Print and panic functions are best emitted directly without // * Print and panic functions are best emitted directly without
// interpreting them, otherwise we get a ton of putchar (etc.) // interpreting them, otherwise we get a ton of putchar (etc.)
@@ -205,9 +163,6 @@ func (r *runner) run(fn *function, params []value, parentMem *memoryView, indent
// * runtime.hashmapGet tries to access the map value directly. // * runtime.hashmapGet tries to access the map value directly.
// This is not possible as the map value is treated as a special // This is not possible as the map value is treated as a special
// kind of object in this package. // kind of object in this package.
// * os.runtime_args reads globals that are initialized outside
// the view of the interp package so it always needs to be run
// at runtime.
err := r.runAtRuntime(fn, inst, locals, &mem, indent) err := r.runAtRuntime(fn, inst, locals, &mem, indent)
if err != nil { if err != nil {
return nil, mem, err return nil, mem, err
@@ -373,7 +328,7 @@ func (r *runner) run(fn *function, params []value, parentMem *memoryView, indent
return nil, mem, r.errorAt(inst, err) return nil, mem, r.errorAt(inst, err)
} }
actualType := actualTypePtrToInt.Operand(0) actualType := actualTypePtrToInt.Operand(0)
if strings.TrimPrefix(actualType.Name(), "reflect/types.type:") == strings.TrimPrefix(assertedType.Name(), "reflect/types.typeid:") { if actualType.Name()+"$id" == assertedType.Name() {
locals[inst.localIndex] = literalValue{uint8(1)} locals[inst.localIndex] = literalValue{uint8(1)}
} else { } else {
locals[inst.localIndex] = literalValue{uint8(0)} locals[inst.localIndex] = literalValue{uint8(0)}
@@ -460,6 +415,74 @@ func (r *runner) run(fn *function, params []value, parentMem *memoryView, indent
return nil, mem, r.errorAt(inst, errors.New("could not find method: "+signature.Name())) return nil, mem, r.errorAt(inst, errors.New("could not find method: "+signature.Name()))
} }
locals[inst.localIndex] = r.getValue(method) locals[inst.localIndex] = r.getValue(method)
case callFn.name == "runtime.hashmapMake":
// Create a new map.
hashmapPointerType := inst.llvmInst.Type()
keySize := uint32(operands[1].Uint())
valueSize := uint32(operands[2].Uint())
m := newMapValue(r, hashmapPointerType, keySize, valueSize)
alloc := object{
llvmType: hashmapPointerType,
globalName: r.pkgName + "$map",
buffer: m,
size: m.len(r),
}
index := len(r.objects)
r.objects = append(r.objects, alloc)
// Create a pointer to this map. Maps are reference types, so
// are implemented as pointers.
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 {
return nil, mem, r.errorAt(inst, err)
}
m := mem.getWritable(mapPtr.index()).buffer.(*mapValue)
keyPtr, err := operands[2].asPointer(r)
if err != nil {
return nil, mem, r.errorAt(inst, err)
}
valuePtr, err := operands[3].asPointer(r)
if err != nil {
return nil, mem, r.errorAt(inst, err)
}
err = m.putBinary(&mem, keyPtr, valuePtr)
if err != nil {
return nil, mem, r.errorAt(inst, err)
}
case callFn.name == "runtime.hashmapStringSet":
// Do a mapassign operation with a string key.
if r.debug {
fmt.Fprintln(os.Stderr, indent+"runtime.hashmapBinarySet:", operands[1:])
}
mapPtr, err := operands[1].asPointer(r)
if err != nil {
return nil, mem, r.errorAt(inst, err)
}
m := mem.getWritable(mapPtr.index()).buffer.(*mapValue)
stringPtr, err := operands[2].asPointer(r)
if err != nil {
return nil, mem, r.errorAt(inst, err)
}
stringLen := operands[3].Uint()
valuePtr, err := operands[4].asPointer(r)
if err != nil {
return nil, mem, r.errorAt(inst, err)
}
err = m.putString(&mem, stringPtr, stringLen, valuePtr)
if err != nil {
return nil, mem, r.errorAt(inst, err)
}
default: default:
if len(callFn.blocks) == 0 { if len(callFn.blocks) == 0 {
// Call to a function declaration without a definition // Call to a function declaration without a definition
@@ -590,14 +613,18 @@ func (r *runner) run(fn *function, params []value, parentMem *memoryView, indent
// GetElementPtr does pointer arithmetic, changing the offset of the // GetElementPtr does pointer arithmetic, changing the offset of the
// pointer into the underlying object. // pointer into the underlying object.
var offset uint64 var offset uint64
var gepOperands []uint64
for i := 2; i < len(operands); i += 2 { for i := 2; i < len(operands); i += 2 {
index := operands[i].Uint() index := operands[i].Uint()
elementSize := operands[i+1].Uint() elementSize := operands[i+1].Uint()
if int64(elementSize) < 0 { if int64(elementSize) < 0 {
// This is a struct field. // This is a struct field.
// The field number is encoded by flipping all the bits.
gepOperands = append(gepOperands, ^elementSize)
offset += index offset += index
} else { } else {
// This is a normal GEP, probably an array index. // This is a normal GEP, probably an array index.
gepOperands = append(gepOperands, index)
offset += elementSize * index offset += elementSize * index
} }
} }
@@ -927,31 +954,16 @@ func (r *runner) runAtRuntime(fn *function, inst instruction, locals []value, me
result = r.builder.CreateBitCast(operands[0], inst.llvmInst.Type(), inst.name) result = r.builder.CreateBitCast(operands[0], inst.llvmInst.Type(), inst.name)
case llvm.ExtractValue: case llvm.ExtractValue:
indices := inst.llvmInst.Indices() indices := inst.llvmInst.Indices()
// Note: the Go LLVM API doesn't support multiple indices, so simulate if len(indices) != 1 {
// this operation with some extra extractvalue instructions. Hopefully panic("expected exactly one index")
// this is optimized to a single instruction.
agg := operands[0]
for i := 0; i < len(indices)-1; i++ {
agg = r.builder.CreateExtractValue(agg, int(indices[i]), inst.name+".agg")
} }
result = r.builder.CreateExtractValue(agg, int(indices[len(indices)-1]), inst.name) result = r.builder.CreateExtractValue(operands[0], int(indices[0]), inst.name)
case llvm.InsertValue: case llvm.InsertValue:
indices := inst.llvmInst.Indices() indices := inst.llvmInst.Indices()
// Similar to extractvalue, we're working around a limitation in the Go if len(indices) != 1 {
// LLVM API here by splitting the insertvalue into multiple instructions panic("expected exactly one index")
// if there is more than one operand.
agg := operands[0]
aggregates := []llvm.Value{agg}
for i := 0; i < len(indices)-1; i++ {
agg = r.builder.CreateExtractValue(agg, int(indices[i]), inst.name+".agg"+strconv.Itoa(i))
aggregates = append(aggregates, agg)
} }
result = operands[1] result = r.builder.CreateInsertValue(operands[0], operands[1], int(indices[0]), inst.name)
for i := len(indices) - 1; i >= 0; i-- {
agg := aggregates[i]
result = r.builder.CreateInsertValue(agg, result, int(indices[i]), inst.name+".insertvalue"+strconv.Itoa(i))
}
case llvm.Add: case llvm.Add:
result = r.builder.CreateAdd(operands[0], operands[1], inst.name) result = r.builder.CreateAdd(operands[0], operands[1], inst.name)
case llvm.Sub: case llvm.Sub:
+282 -14
View File
@@ -178,9 +178,6 @@ func (mv *memoryView) markExternal(llvmValue llvm.Value, mark uint8) {
default: default:
panic("interp: unknown constant expression") panic("interp: unknown constant expression")
} }
} else if !llvmValue.IsAInlineAsm().IsNil() {
// Inline assembly can modify globals but only exported globals. Let's
// hope the author knows what they're doing.
} else { } else {
llvmType := llvmValue.Type() llvmType := llvmValue.Type()
switch llvmType.TypeKind() { switch llvmType.TypeKind() {
@@ -572,17 +569,6 @@ func (v pointerValue) toLLVMValue(llvmType llvm.Type, mem *memoryView) (llvm.Val
} }
if llvmType.IsNil() { if llvmType.IsNil() {
if v.offset() != 0 {
// If there is an offset, make sure to use a GEP to index into the
// pointer. Because there is no expected type, we use whatever is
// most convenient: an *i8 type. It is trivial to index byte-wise.
if llvmValue.Type() != mem.r.i8ptrType {
llvmValue = llvm.ConstBitCast(llvmValue, mem.r.i8ptrType)
}
llvmValue = llvm.ConstInBoundsGEP(llvmValue, []llvm.Value{
llvm.ConstInt(llvmValue.Type().Context().Int32Type(), uint64(v.offset()), false),
})
}
return llvmValue, nil return llvmValue, nil
} }
@@ -654,6 +640,288 @@ func (v pointerValue) toLLVMValue(llvmType llvm.Type, mem *memoryView) (llvm.Val
return gep, nil return gep, nil
} }
// 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 {
keyValue, err := key.rawLLVMValue(mem)
if err != nil {
panic(err)
}
keyValues = append(keyValues, keyValue)
}
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 {
v, err := value.rawLLVMValue(mem)
if err != nil {
panic(err)
}
valueValues = append(valueValues, v)
}
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, error) {
if !v.hashmap.IsNil() {
return v.hashmap, nil
}
// 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() {
return llvm.Value{}, errors.New("interp: todo: map key with pointer")
}
data = key.buf
keyValue = key
default:
return llvm.Value{}, errors.New("interp: 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, nil
}
// 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
// data. This is the fallback data for everything that isn't clearly a // data. This is the fallback data for everything that isn't clearly a
// literalValue or pointerValue. // literalValue or pointerValue.
-47
View File
@@ -8,7 +8,6 @@ target triple = "x86_64--linux"
@main.exportedValue = global [1 x i16*] [i16* @main.exposedValue1] @main.exportedValue = global [1 x i16*] [i16* @main.exposedValue1]
@main.exposedValue1 = global i16 0 @main.exposedValue1 = global i16 0
@main.exposedValue2 = global i16 0 @main.exposedValue2 = global i16 0
@main.insertedValue = global {i8, i32, {float, {i64, i16}}} zeroinitializer
declare void @runtime.printint64(i64) unnamed_addr declare void @runtime.printint64(i64) unnamed_addr
@@ -66,22 +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 that inline assembly is ignored.
call void @modifyExternal(i32* bitcast (void ()* @hasInlineAsm to i32*))
; 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)
; Test extractvalue/insertvalue with multiple operands.
%agg = call {i8, i32, {float, {i64, i16}}} @nestedStruct()
%elt = extractvalue {i8, i32, {float, {i64, i16}}} %agg, 2, 1, 0
call void @runtime.printint64(i64 %elt)
%agg2 = insertvalue {i8, i32, {float, {i64, i16}}} %agg, i64 5, 2, 1, 0
store {i8, i32, {float, {i64, i16}}} %agg2, {i8, i32, {float, {i64, i16}}}* @main.insertedValue
ret void ret void
} }
@@ -104,33 +87,3 @@ entry:
store i16 8, i16* @main.exposedValue2 store i16 8, i16* @main.exposedValue2
ret void ret void
} }
; Inline assembly should be ignored in the interp package. While it is possible
; to modify other globals that way, usually that's not the case and there is no
; real way to check.
define void @hasInlineAsm() {
entry:
call void asm sideeffect "", ""()
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
}
declare {i8, i32, {float, {i64, i16}}} @nestedStruct()
-44
View File
@@ -7,7 +7,6 @@ target triple = "x86_64--linux"
@main.exportedValue = global [1 x i16*] [i16* @main.exposedValue1] @main.exportedValue = global [1 x i16*] [i16* @main.exposedValue1]
@main.exposedValue1 = global i16 0 @main.exposedValue1 = global i16 0
@main.exposedValue2 = local_unnamed_addr global i16 0 @main.exposedValue2 = local_unnamed_addr global i16 0
@main.insertedValue = local_unnamed_addr global { i8, i32, { float, { i64, i16 } } } zeroinitializer
declare void @runtime.printint64(i64) unnamed_addr declare void @runtime.printint64(i64) unnamed_addr
@@ -26,20 +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 @modifyExternal(i32* bitcast (void ()* @hasInlineAsm to i32*))
call void @runtime.printint64(i64 6)
call void @runtime.printint64(i64 -1)
%agg = call { i8, i32, { float, { i64, i16 } } } @nestedStruct()
%elt.agg = extractvalue { i8, i32, { float, { i64, i16 } } } %agg, 2
%elt.agg1 = extractvalue { float, { i64, i16 } } %elt.agg, 1
%elt = extractvalue { i64, i16 } %elt.agg1, 0
call void @runtime.printint64(i64 %elt)
%agg2.agg0 = extractvalue { i8, i32, { float, { i64, i16 } } } %agg, 2
%agg2.agg1 = extractvalue { float, { i64, i16 } } %agg2.agg0, 1
%agg2.insertvalue2 = insertvalue { i64, i16 } %agg2.agg1, i64 5, 0
%agg2.insertvalue1 = insertvalue { float, { i64, i16 } } %agg2.agg0, { i64, i16 } %agg2.insertvalue2, 1
%agg2.insertvalue0 = insertvalue { i8, i32, { float, { i64, i16 } } } %agg, { float, { i64, i16 } } %agg2.insertvalue1, 2
store { i8, i32, { float, { i64, i16 } } } %agg2.insertvalue0, { i8, i32, { float, { i64, i16 } } }* @main.insertedValue
ret void ret void
} }
@@ -59,32 +44,3 @@ entry:
store i16 8, i16* @main.exposedValue2 store i16 8, i16* @main.exposedValue2
ret void ret void
} }
define void @hasInlineAsm() {
entry:
call void asm sideeffect "", ""()
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
}
declare { i8, i32, { float, { i64, i16 } } } @nestedStruct() local_unnamed_addr
+2 -2
View File
@@ -6,7 +6,7 @@ target triple = "x86_64--linux"
@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, %runtime.interfaceMethodInfo* null }
@"reflect/types.typeid:named:main.foo" = external constant i8 @"reflect/types.type:named:main.foo$id" = external constant i8
@"reflect/types.type:basic:int" = external constant %runtime.typecodeID @"reflect/types.type:basic:int" = external constant %runtime.typecodeID
@@ -21,7 +21,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.typecodeID* @"reflect/types.type:named:main.foo" to i64), i8* @"reflect/types.type:named:main.foo$id", 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
}
-32
View File
@@ -1,32 +0,0 @@
target datalayout = "e-m:e-i64:64-f80:128-n8:16:32:64-S128"
target triple = "x86_64--linux"
declare void @externalCall(i64)
@foo.knownAtRuntime = global i64 0
@bar.knownAtRuntime = global i64 0
define void @runtime.initAll() unnamed_addr {
entry:
call void @foo.init(i8* undef, i8* undef)
call void @bar.init(i8* undef, i8* undef)
call void @main.init(i8* undef, i8* undef)
ret void
}
define internal void @foo.init(i8* %context, i8* %parentHandle) unnamed_addr {
store i64 5, i64* @foo.knownAtRuntime
unreachable ; this triggers a revert of @foo.init.
}
define internal void @bar.init(i8* %context, i8* %parentHandle) unnamed_addr {
%val = load i64, i64* @foo.knownAtRuntime
store i64 %val, i64* @bar.knownAtRuntime
ret void
}
define internal void @main.init(i8* %context, i8* %parentHandle) unnamed_addr {
entry:
call void @externalCall(i64 3)
ret void
}
-21
View File
@@ -1,21 +0,0 @@
target datalayout = "e-m:e-i64:64-f80:128-n8:16:32:64-S128"
target triple = "x86_64--linux"
@foo.knownAtRuntime = local_unnamed_addr global i64 0
@bar.knownAtRuntime = local_unnamed_addr global i64 0
declare void @externalCall(i64) local_unnamed_addr
define void @runtime.initAll() unnamed_addr {
entry:
call fastcc void @foo.init(i8* undef, i8* undef)
%val = load i64, i64* @foo.knownAtRuntime, align 8
store i64 %val, i64* @bar.knownAtRuntime, align 8
call void @externalCall(i64 3)
ret void
}
define internal fastcc void @foo.init(i8* %context, i8* %parentHandle) unnamed_addr {
store i64 5, i64* @foo.knownAtRuntime, align 8
unreachable
}
-10
View File
@@ -23,13 +23,3 @@ type Error struct {
func (e Error) Error() string { func (e Error) Error() string {
return e.Err.Error() return e.Err.Error()
} }
// Error returned when loading a *Program for a test binary but no test files
// are present.
type NoTestFilesError struct {
ImportPath string
}
func (e NoTestFilesError) Error() string {
return "no test files"
}
+16 -38
View File
@@ -2,14 +2,6 @@ package loader
// This file constructs a new temporary GOROOT directory by merging both the // This file constructs a new temporary GOROOT directory by merging both the
// standard Go GOROOT and the GOROOT from TinyGo using symlinks. // standard Go GOROOT and the GOROOT from TinyGo using symlinks.
//
// The goal is to replace specific packages from Go with a TinyGo version. It's
// never a partial replacement, either a package is fully replaced or it is not.
// This is important because if we did allow to merge packages (e.g. by adding
// files to a package), it would lead to a dependency on implementation details
// with all the maintenance burden that results in. Only allowing to replace
// packages as a whole avoids this as packages are already designed to have a
// public (backwards-compatible) API.
import ( import (
"crypto/sha512" "crypto/sha512"
@@ -147,7 +139,6 @@ func mergeDirectory(goroot, tinygoroot, tmpgoroot, importPath string, overrides
if err != nil { if err != nil {
return err return err
} }
hasTinyGoFiles := false
for _, e := range tinygoEntries { for _, e := range tinygoEntries {
if e.IsDir() { if e.IsDir() {
// A directory, so merge this thing. // A directory, so merge this thing.
@@ -163,7 +154,6 @@ func mergeDirectory(goroot, tinygoroot, tmpgoroot, importPath string, overrides
if err != nil { if err != nil {
return err return err
} }
hasTinyGoFiles = true
} }
} }
@@ -174,30 +164,21 @@ func mergeDirectory(goroot, tinygoroot, tmpgoroot, importPath string, overrides
return err return err
} }
for _, e := range gorootEntries { for _, e := range gorootEntries {
if e.IsDir() { if !e.IsDir() {
if _, ok := overrides[path.Join(importPath, e.Name())+"/"]; ok { // Don't merge in files from Go. Otherwise we'd end up with a
// Already included above, so don't bother trying to create this // weird syscall package with files from both roots.
// symlink. continue
continue }
} if _, ok := overrides[path.Join(importPath, e.Name())+"/"]; ok {
newname := filepath.Join(tmpgoroot, "src", importPath, e.Name()) // Already included above, so don't bother trying to create this
oldname := filepath.Join(goroot, "src", importPath, e.Name()) // symlink.
err := symlink(oldname, newname) continue
if err != nil { }
return err newname := filepath.Join(tmpgoroot, "src", importPath, e.Name())
} oldname := filepath.Join(goroot, "src", importPath, e.Name())
} else { err := symlink(oldname, newname)
// Only merge files from Go if TinyGo does not have any files. if err != nil {
// Otherwise we'd end up with a weird mix from both Go return err
// implementations.
if !hasTinyGoFiles {
newname := filepath.Join(tmpgoroot, "src", importPath, e.Name())
oldname := filepath.Join(goroot, "src", importPath, e.Name())
err := symlink(oldname, newname)
if err != nil {
return err
}
}
} }
} }
} }
@@ -208,7 +189,7 @@ func mergeDirectory(goroot, tinygoroot, tmpgoroot, importPath string, overrides
// with the TinyGo version. This is the case on some targets. // with the TinyGo version. This is the case on some targets.
func needsSyscallPackage(buildTags []string) bool { func needsSyscallPackage(buildTags []string) bool {
for _, tag := range buildTags { for _, tag := range buildTags {
if tag == "baremetal" || tag == "darwin" || tag == "nintendoswitch" || tag == "tinygo.wasm" { if tag == "baremetal" || tag == "darwin" || tag == "nintendoswitch" || tag == "wasi" {
return true return true
} }
} }
@@ -220,8 +201,6 @@ func needsSyscallPackage(buildTags []string) bool {
func pathsToOverride(needsSyscallPackage bool) map[string]bool { func pathsToOverride(needsSyscallPackage bool) map[string]bool {
paths := map[string]bool{ paths := map[string]bool{
"/": true, "/": true,
"crypto/": true,
"crypto/rand/": false,
"device/": false, "device/": false,
"examples/": false, "examples/": false,
"internal/": true, "internal/": true,
@@ -229,7 +208,6 @@ func pathsToOverride(needsSyscallPackage bool) map[string]bool {
"internal/reflectlite/": false, "internal/reflectlite/": false,
"internal/task/": false, "internal/task/": false,
"machine/": false, "machine/": false,
"net/": true,
"os/": true, "os/": true,
"reflect/": false, "reflect/": false,
"runtime/": false, "runtime/": false,
+7 -16
View File
@@ -210,12 +210,6 @@ func Load(config *compileopts.Config, inputPkgs []string, clangHeaders string, t
p.Packages[pkg.ImportPath] = pkg p.Packages[pkg.ImportPath] = pkg
} }
if config.TestConfig.CompileTestBinary && !strings.HasSuffix(p.sorted[len(p.sorted)-1].ImportPath, ".test") {
// Trying to compile a test binary but there are no test files in this
// package.
return p, NoTestFilesError{p.sorted[len(p.sorted)-1].ImportPath}
}
return p, nil return p, nil
} }
@@ -343,11 +337,12 @@ func (p *Package) Check() error {
checker.Importer = p checker.Importer = p
packageName := p.ImportPath packageName := p.ImportPath
if p == p.program.MainPkg() { if p.Name == "main" {
if p.Name != "main" { // The main package normally has a different import path, such as
// Sanity check. Should not ever trigger. // "command-line-arguments" or "./testdata/cgo". Therefore, use the name
panic("expected main package to have name 'main'") // "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" packageName = "main"
} }
typesPkg, err := checker.Check(packageName, p.program.fset, p.Files, &p.info) typesPkg, err := checker.Check(packageName, p.program.fset, p.Files, &p.info)
@@ -386,11 +381,7 @@ func (p *Package) parseFiles() ([]*ast.File, error) {
} }
// Do CGo processing. // Do CGo processing.
// This is done when there are any CgoFiles at all. In that case, len(files) if len(p.CgoFiles) != 0 {
// should be non-zero. However, if len(GoFiles) == 0 and len(CgoFiles) == 1
// and there is a syntax error in a CGo file, len(files) may be 0. Don't try
// to call cgo.Process in that case as it will only cause issues.
if len(p.CgoFiles) != 0 && len(files) != 0 {
var initialCFlags []string var initialCFlags []string
initialCFlags = append(initialCFlags, p.program.config.CFlags()...) initialCFlags = append(initialCFlags, p.program.config.CFlags()...)
initialCFlags = append(initialCFlags, "-I"+p.Dir) initialCFlags = append(initialCFlags, "-I"+p.Dir)
+131 -431
View File
@@ -13,15 +13,11 @@ import (
"os/exec" "os/exec"
"os/signal" "os/signal"
"path/filepath" "path/filepath"
"regexp"
"runtime" "runtime"
"runtime/pprof"
"strconv"
"strings" "strings"
"sync/atomic" "sync/atomic"
"time" "time"
"github.com/google/shlex"
"github.com/mattn/go-colorable" "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"
@@ -32,7 +28,6 @@ import (
"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 (
@@ -97,31 +92,12 @@ func copyFile(src, dst string) error {
// executeCommand is a simple wrapper to exec.Cmd // executeCommand is a simple wrapper to exec.Cmd
func executeCommand(options *compileopts.Options, name string, arg ...string) *exec.Cmd { func executeCommand(options *compileopts.Options, name string, arg ...string) *exec.Cmd {
if options.PrintCommands != nil { if options.PrintCommands {
options.PrintCommands(name, arg...) fmt.Printf("%s %s\n", name, strings.Join(arg, " "))
} }
return exec.Command(name, arg...) return exec.Command(name, arg...)
} }
// printCommand prints a command to stdout while formatting it like a real
// command (escaping characters etc). The resulting command should be easy to
// run directly in a shell, although it is not guaranteed to be a safe shell
// escape. That's not a problem as the primary use case is printing the command,
// not running it.
func printCommand(cmd string, args ...string) {
command := append([]string{cmd}, args...)
for i, arg := range command {
// Source: https://www.oreilly.com/library/view/learning-the-bash/1565923472/ch01s09.html
const specialChars = "~`#$&*()\\|[]{};'\"<>?! "
if strings.ContainsAny(arg, specialChars) {
// See: https://stackoverflow.com/questions/15783701/which-characters-need-to-be-escaped-when-using-bash
arg = "'" + strings.ReplaceAll(arg, `'`, `'\''`) + "'"
command[i] = arg
}
}
fmt.Fprintln(os.Stderr, strings.Join(command, " "))
}
// 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)
@@ -157,17 +133,15 @@ func Build(pkgName, outpath string, options *compileopts.Options) error {
}) })
} }
// Test runs the tests in the given package. Returns whether the test passed and // Test runs the tests in the given package.
// possibly an error if the test failed to run. func Test(pkgName string, options *compileopts.Options, testCompileOnly bool, outpath string) error {
func Test(pkgName string, options *compileopts.Options, testCompileOnly, testVerbose bool, outpath string) (bool, error) {
options.TestConfig.CompileTestBinary = true options.TestConfig.CompileTestBinary = true
config, err := builder.NewConfig(options) config, err := builder.NewConfig(options)
if err != nil { if err != nil {
return false, err return err
} }
passed := true return builder.Build(pkgName, outpath, config, func(result builder.BuildResult) error {
err = builder.Build(pkgName, outpath, config, func(result builder.BuildResult) error {
if testCompileOnly || outpath != "" { if testCompileOnly || outpath != "" {
// Write test binary to the specified file name. // Write test binary to the specified file name.
if outpath == "" { if outpath == "" {
@@ -181,83 +155,48 @@ func Test(pkgName string, options *compileopts.Options, testCompileOnly, testVer
// Do not run the test. // Do not run the test.
return nil return nil
} }
if len(config.Target.Emulator) == 0 {
// Run the test. // Run directly.
start := time.Now() cmd := executeCommand(config.Options, result.Binary)
var err error cmd.Stdout = os.Stdout
passed, err = runPackageTest(config, result, testVerbose) cmd.Stderr = os.Stderr
if err != nil { cmd.Dir = result.MainDir
return err err := cmd.Run()
} if err != nil {
duration := time.Since(start) // Propagate the exit code
if err, ok := err.(*exec.ExitError); ok {
// Print the result. os.Exit(err.ExitCode())
importPath := strings.TrimSuffix(result.ImportPath, ".test") }
if passed { return &commandError{"failed to run compiled binary", result.Binary, err}
fmt.Printf("ok \t%s\t%.3fs\n", importPath, duration.Seconds()) }
return nil
} else { } else {
fmt.Printf("FAIL\t%s\t%.3fs\n", importPath, duration.Seconds()) // Run in an emulator.
args := append(config.Target.Emulator[1:], result.Binary)
cmd := executeCommand(config.Options, config.Target.Emulator[0], args...)
buf := &bytes.Buffer{}
w := io.MultiWriter(os.Stdout, buf)
cmd.Stdout = w
cmd.Stderr = os.Stderr
err := cmd.Run()
if err != nil {
if err, ok := err.(*exec.ExitError); !ok || !err.Exited() {
// Workaround for QEMU which always exits with an error.
return &commandError{"failed to run emulator with", result.Binary, err}
}
}
testOutput := string(buf.Bytes())
if testOutput == "PASS\n" || strings.HasSuffix(testOutput, "\nPASS\n") {
// Test passed.
return nil
} else {
// Test failed, either by ending with the word "FAIL" or with a
// panic of some sort.
os.Exit(1)
return nil // unreachable
}
} }
return nil
}) })
if err, ok := err.(loader.NoTestFilesError); ok {
fmt.Printf("? \t%s\t[no test files]\n", err.ImportPath)
// Pretend the test passed - it at least didn't fail.
return true, nil
}
return passed, err
}
// runPackageTest runs a test binary that was previously built. The return
// values are whether the test passed and any errors encountered while trying to
// run the binary.
func runPackageTest(config *compileopts.Config, result builder.BuildResult, testVerbose bool) (bool, error) {
if len(config.Target.Emulator) == 0 {
// Run directly.
var flags []string
if testVerbose {
flags = append(flags, "-test.v")
}
cmd := executeCommand(config.Options, result.Binary, flags...)
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
cmd.Dir = result.MainDir
err := cmd.Run()
if err != nil {
if _, ok := err.(*exec.ExitError); ok {
// Binary exited with a non-zero exit code, which means the test
// failed.
return false, nil
}
return false, &commandError{"failed to run compiled binary", result.Binary, err}
}
return true, nil
} else {
// Run in an emulator.
// TODO: pass the -test.v flag if needed.
args := append(config.Target.Emulator[1:], result.Binary)
cmd := executeCommand(config.Options, config.Target.Emulator[0], args...)
buf := &bytes.Buffer{}
w := io.MultiWriter(os.Stdout, buf)
cmd.Stdout = w
cmd.Stderr = os.Stderr
err := cmd.Run()
if err != nil {
if err, ok := err.(*exec.ExitError); !ok || !err.Exited() {
// Workaround for QEMU which always exits with an error.
return false, &commandError{"failed to run emulator with", result.Binary, err}
}
}
testOutput := string(buf.Bytes())
if testOutput == "PASS\n" || strings.HasSuffix(testOutput, "\nPASS\n") {
// Test passed.
return true, nil
} else {
// Test failed, either by ending with the word "FAIL" or with a
// panic of some sort.
return false, nil
}
}
} }
// Flash builds and flashes the built binary to the given serial port. // Flash builds and flashes the built binary to the given serial port.
@@ -282,8 +221,6 @@ func Flash(pkgName, port string, options *compileopts.Options) error {
fileExt = ".bin" fileExt = ".bin"
case strings.Contains(config.Target.FlashCommand, "{uf2}"): case strings.Contains(config.Target.FlashCommand, "{uf2}"):
fileExt = ".uf2" fileExt = ".uf2"
case strings.Contains(config.Target.FlashCommand, "{zip}"):
fileExt = ".zip"
default: default:
return errors.New("invalid target file - did you forget the {hex} token in the 'flash-command' section?") return errors.New("invalid target file - did you forget the {hex} token in the 'flash-command' section?")
} }
@@ -294,8 +231,6 @@ func Flash(pkgName, port string, options *compileopts.Options) error {
fileExt = filepath.Ext(config.Target.FlashFilename) fileExt = filepath.Ext(config.Target.FlashFilename)
case "openocd": case "openocd":
fileExt = ".hex" fileExt = ".hex"
case "bmp":
fileExt = ".elf"
case "native": case "native":
return errors.New("unknown flash method \"native\" - did you miss a -target flag?") return errors.New("unknown flash method \"native\" - did you miss a -target flag?")
default: default:
@@ -305,12 +240,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(port, config.Target.SerialPort) 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}
} }
@@ -323,36 +261,36 @@ func Flash(pkgName, port string, options *compileopts.Options) error {
case "", "command": case "", "command":
// Create the command. // Create the command.
flashCmd := config.Target.FlashCommand flashCmd := config.Target.FlashCommand
flashCmdList, err := shlex.Split(flashCmd) fileToken := "{" + fileExt[1:] + "}"
if err != nil { flashCmd = strings.ReplaceAll(flashCmd, fileToken, result.Binary)
return fmt.Errorf("could not parse flash command %#v: %w", flashCmd, err)
}
if strings.Contains(flashCmd, "{port}") { if port == "" && strings.Contains(flashCmd, "{port}") {
var err error var err error
port, err = getDefaultPort(port, config.Target.SerialPort) port, err = getDefaultPort()
if err != nil { if err != nil {
return err return err
} }
} }
// Fill in fields in the command template. flashCmd = strings.ReplaceAll(flashCmd, "{port}", port)
fileToken := "{" + fileExt[1:] + "}"
for i, arg := range flashCmdList {
arg = strings.ReplaceAll(arg, fileToken, result.Binary)
arg = strings.ReplaceAll(arg, "{port}", port)
flashCmdList[i] = arg
}
// Execute the command. // Execute the command.
if len(flashCmdList) < 2 { var cmd *exec.Cmd
return fmt.Errorf("invalid flash command: %#v", flashCmd) switch runtime.GOOS {
case "windows":
command := strings.Split(flashCmd, " ")
if len(command) < 2 {
return errors.New("invalid flash command")
}
cmd = executeCommand(config.Options, command[0], command[1:]...)
default:
cmd = executeCommand(config.Options, "/bin/sh", "-c", flashCmd)
} }
cmd := executeCommand(config.Options, flashCmdList[0], flashCmdList[1:]...)
cmd.Stdout = os.Stdout cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr cmd.Stderr = os.Stderr
cmd.Dir = goenv.Get("TINYGOROOT") cmd.Dir = goenv.Get("TINYGOROOT")
err = cmd.Run() err := cmd.Run()
if err != nil { if err != nil {
return &commandError{"failed to flash", result.Binary, err} return &commandError{"failed to flash", result.Binary, err}
} }
@@ -388,25 +326,6 @@ func Flash(pkgName, port string, options *compileopts.Options) error {
return &commandError{"failed to flash", result.Binary, err} return &commandError{"failed to flash", result.Binary, err}
} }
return nil return nil
case "bmp":
gdb, err := config.Target.LookupGDB()
if err != nil {
return err
}
var bmpGDBPort string
bmpGDBPort, _, err = getBMPPorts()
if err != nil {
return err
}
args := []string{"-ex", "target extended-remote " + bmpGDBPort, "-ex", "monitor swdp_scan", "-ex", "attach 1", "-ex", "load", filepath.ToSlash(result.Binary)}
cmd := executeCommand(config.Options, gdb, args...)
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
err = cmd.Run()
if err != nil {
return &commandError{"failed to flash", result.Binary, err}
}
return nil
default: default:
return fmt.Errorf("unknown flash method: %s", flashMethod) return fmt.Errorf("unknown flash method: %s", flashMethod)
} }
@@ -461,15 +380,8 @@ func FlashGDB(pkgName string, ocdOutput bool, options *compileopts.Options) erro
switch gdbInterface { switch gdbInterface {
case "native": case "native":
// Run GDB directly. // Run GDB directly.
case "bmp":
var bmpGDBPort string
bmpGDBPort, _, err = getBMPPorts()
if err != nil {
return err
}
gdbCommands = append(gdbCommands, "target extended-remote "+bmpGDBPort, "monitor swdp_scan", "compare-sections", "attach 1", "load")
case "openocd": case "openocd":
gdbCommands = append(gdbCommands, "target extended-remote :3333", "monitor halt", "load", "monitor reset halt") gdbCommands = append(gdbCommands, "target remote :3333", "monitor halt", "load", "monitor reset halt")
// We need a separate debugging daemon for on-chip debugging. // We need a separate debugging daemon for on-chip debugging.
args, err := config.OpenOCDConfiguration() args, err := config.OpenOCDConfiguration()
@@ -488,7 +400,7 @@ func FlashGDB(pkgName string, ocdOutput bool, options *compileopts.Options) erro
daemon.Stderr = w daemon.Stderr = w
} }
case "jlink": case "jlink":
gdbCommands = append(gdbCommands, "target extended-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 = executeCommand(config.Options, "JLinkGDBServer", "-device", config.Target.JLinkDevice)
@@ -503,7 +415,7 @@ func FlashGDB(pkgName string, ocdOutput bool, options *compileopts.Options) erro
daemon.Stderr = w daemon.Stderr = w
} }
case "qemu": case "qemu":
gdbCommands = append(gdbCommands, "target extended-remote :1234") gdbCommands = append(gdbCommands, "target remote :1234")
// Run in an emulator. // Run in an emulator.
args := append(config.Target.Emulator[1:], result.Binary, "-s", "-S") args := append(config.Target.Emulator[1:], result.Binary, "-s", "-S")
@@ -511,7 +423,7 @@ func FlashGDB(pkgName string, ocdOutput bool, options *compileopts.Options) erro
daemon.Stdout = os.Stdout daemon.Stdout = os.Stdout
daemon.Stderr = os.Stderr daemon.Stderr = os.Stderr
case "qemu-user": case "qemu-user":
gdbCommands = append(gdbCommands, "target extended-remote :1234") gdbCommands = append(gdbCommands, "target remote :1234")
// 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)
@@ -519,7 +431,7 @@ func FlashGDB(pkgName string, ocdOutput bool, options *compileopts.Options) erro
daemon.Stdout = os.Stdout daemon.Stdout = os.Stdout
daemon.Stderr = os.Stderr daemon.Stderr = os.Stderr
case "mgba": case "mgba":
gdbCommands = append(gdbCommands, "target extended-remote :2345") gdbCommands = append(gdbCommands, "target remote :2345")
// Run in an emulator. // Run in an emulator.
args := append(config.Target.Emulator[1:], result.Binary, "-g") args := append(config.Target.Emulator[1:], result.Binary, "-g")
@@ -527,7 +439,7 @@ func FlashGDB(pkgName string, ocdOutput bool, options *compileopts.Options) erro
daemon.Stdout = os.Stdout daemon.Stdout = os.Stdout
daemon.Stderr = os.Stderr daemon.Stderr = os.Stderr
case "simavr": case "simavr":
gdbCommands = append(gdbCommands, "target extended-remote :1234") gdbCommands = append(gdbCommands, "target remote :1234")
// 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)
@@ -753,155 +665,41 @@ func windowsFindUSBDrive(volume string, options *compileopts.Options) (string, e
} }
// getDefaultPort returns the default serial port depending on the operating system. // getDefaultPort returns the default serial port depending on the operating system.
func getDefaultPort(portFlag string, usbInterfaces []string) (port string, err error) { func getDefaultPort() (port string, err error) {
portCandidates := strings.FieldsFunc(portFlag, func(c rune) bool { return c == ',' }) var portPath string
if len(portCandidates) == 1 {
return portCandidates[0], nil
}
var ports []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
} }
var preferredPortIDs [][2]uint16
for _, s := range usbInterfaces {
parts := strings.Split(s, ":")
if len(parts) != 3 || (parts[0] != "acm" && parts[0] == "usb") {
// acm and usb are the two types of serial ports recognized
// under Linux (ttyACM*, ttyUSB*). Other operating systems don't
// generally make this distinction. If this is not one of the
// given USB devices, don't try to parse the USB IDs.
continue
}
vid, err := strconv.ParseUint(parts[1], 16, 16)
if err != nil {
return "", fmt.Errorf("could not parse USB vendor ID %q: %w", parts[1], err)
}
pid, err := strconv.ParseUint(parts[2], 16, 16)
if err != nil {
return "", fmt.Errorf("could not parse USB product ID %q: %w", parts[1], err)
}
preferredPortIDs = append(preferredPortIDs, [2]uint16{uint16(vid), uint16(pid)})
}
var primaryPorts []string // ports picked from preferred USB VID/PID
var secondaryPorts []string // other ports (as a fallback)
for _, p := range portsList {
if !p.IsUSB {
continue
}
if p.VID != "" && p.PID != "" {
foundPort := false
vid, vidErr := strconv.ParseUint(p.VID, 16, 16)
pid, pidErr := strconv.ParseUint(p.PID, 16, 16)
if vidErr == nil && pidErr == nil {
for _, id := range preferredPortIDs {
if uint16(vid) == id[0] && uint16(pid) == id[1] {
primaryPorts = append(primaryPorts, p.Name)
foundPort = true
continue
}
}
}
if foundPort {
continue
}
}
secondaryPorts = append(secondaryPorts, p.Name)
}
if len(primaryPorts) == 1 {
// There is exactly one match in the set of preferred ports. Use
// this port, even if there may be others available. This allows
// flashing a specific board even if there are multiple available.
return primaryPorts[0], nil
} else if len(primaryPorts) > 1 {
// There are multiple preferred ports, probably because more than
// one device of the same type are connected (e.g. two Arduino
// Unos).
ports = primaryPorts
} else {
// No preferred ports found. Fall back to other serial ports
// available in the system.
ports = secondaryPorts
}
if len(ports) == 0 { if len(ports) == 0 {
// fallback return "", errors.New("no serial ports available")
switch runtime.GOOS { } else if len(ports) > 1 {
case "darwin": return "", errors.New("multiple serial ports available - use -port flag")
ports, err = filepath.Glob("/dev/cu.usb*")
case "linux":
ports, err = filepath.Glob("/dev/ttyACM*")
case "windows":
ports, err = serial.GetPortsList()
}
} }
return ports[0], nil
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, ", "))
}
// getBMPPorts returns BlackMagicProbe's serial ports if any
func getBMPPorts() (gdbPort, uartPort string, err error) {
var portsList []*enumerator.PortDetails
portsList, err = enumerator.GetDetailedPortsList()
if err != nil {
return "", "", err
}
var ports []string
for _, p := range portsList {
if !p.IsUSB {
continue
}
if p.VID != "" && p.PID != "" {
vid, vidErr := strconv.ParseUint(p.VID, 16, 16)
pid, pidErr := strconv.ParseUint(p.PID, 16, 16)
if vidErr == nil && pidErr == nil && vid == 0x1d50 && pid == 0x6018 {
ports = append(ports, p.Name)
}
}
}
if len(ports) == 2 {
return ports[0], ports[1], nil
} else if len(ports) == 0 {
return "", "", errors.New("no BMP detected")
} else {
return "", "", fmt.Errorf("expected 2 BMP serial ports, found %d - did you perhaps connect more than one BMP?", len(ports))
}
} }
func usage() { func usage() {
@@ -1014,52 +812,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.")
@@ -1072,7 +824,6 @@ func main() {
gc := flag.String("gc", "", "garbage collector to use (none, leaking, extalloc, conservative)") gc := flag.String("gc", "", "garbage collector to use (none, leaking, extalloc, conservative)")
panicStrategy := flag.String("panic", "print", "panic strategy (print, trap)") panicStrategy := flag.String("panic", "print", "panic strategy (print, trap)")
scheduler := flag.String("scheduler", "", "which scheduler to use (none, coroutines, tasks)") scheduler := flag.String("scheduler", "", "which scheduler to use (none, coroutines, tasks)")
serial := flag.String("serial", "", "which serial output to use (none, uart, usb)")
printIR := flag.Bool("printir", false, "print LLVM IR") printIR := flag.Bool("printir", false, "print LLVM IR")
dumpSSA := flag.Bool("dumpssa", false, "dump internal Go SSA") dumpSSA := flag.Bool("dumpssa", false, "dump internal Go SSA")
verifyIR := flag.Bool("verifyir", false, "run extra verification steps on LLVM IR") verifyIR := flag.Bool("verifyir", false, "run extra verification steps on LLVM IR")
@@ -1080,32 +831,27 @@ 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") printCommands := flag.Bool("x", false, "Print commands")
nodebug := flag.Bool("no-debug", false, "strip debug information") nodebug := flag.Bool("no-debug", false, "disable DWARF debug symbol generation")
ocdCommandsString := flag.String("ocd-commands", "", "OpenOCD commands, overriding target spec (can specify multiple separated by commas)")
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")
llvmFeatures := flag.String("llvm-features", "", "comma separated LLVM features to enable")
cpuprofile := flag.String("cpuprofile", "", "cpuprofile output")
var flagJSON, flagDeps, flagTest *bool var flagJSON, flagDeps *bool
if command == "help" || command == "list" { if command == "help" || command == "list" {
flagJSON = flag.Bool("json", false, "print data in JSON format") flagJSON = flag.Bool("json", false, "print data in JSON format")
flagDeps = flag.Bool("deps", false, "supply -deps flag to go list") flagDeps = flag.Bool("deps", false, "")
flagTest = flag.Bool("test", false, "supply -test flag to go list")
} }
var outpath string var outpath string
if command == "help" || command == "build" || command == "build-library" || command == "test" { if command == "help" || command == "build" || command == "build-library" || command == "test" {
flag.StringVar(&outpath, "o", "", "output filename") flag.StringVar(&outpath, "o", "", "output filename")
} }
var testCompileOnlyFlag, testVerboseFlag *bool var testCompileOnlyFlag *bool
if command == "help" || command == "test" { if command == "help" || command == "test" {
testCompileOnlyFlag = flag.Bool("c", false, "compile the test binary but do not run it") testCompileOnlyFlag = flag.Bool("c", false, "compile the test binary but do not run it")
testVerboseFlag = flag.Bool("v", false, "verbose: print additional output")
} }
// Early command processing, before commands are interpreted by the Go flag // Early command processing, before commands are interpreted by the Go flag
@@ -1121,74 +867,41 @@ 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)
}
}
var ocdCommands []string
if *ocdCommandsString != "" {
ocdCommands = strings.Split(*ocdCommandsString, ",")
}
options := &compileopts.Options{ options := &compileopts.Options{
Target: *target, Target: *target,
Opt: *opt, Opt: *opt,
GC: *gc, GC: *gc,
PanicStrategy: *panicStrategy, PanicStrategy: *panicStrategy,
Scheduler: *scheduler, Scheduler: *scheduler,
Serial: *serial, PrintIR: *printIR,
PrintIR: *printIR, DumpSSA: *dumpSSA,
DumpSSA: *dumpSSA, VerifyIR: *verifyIR,
VerifyIR: *verifyIR, Debug: !*nodebug,
Debug: !*nodebug, PrintSizes: *printSize,
PrintSizes: *printSize, PrintStacks: *printStacks,
PrintStacks: *printStacks, PrintCommands: *printCommands,
PrintAllocs: printAllocs, Tags: *tags,
Tags: *tags, WasmAbi: *wasmAbi,
GlobalValues: globalVarValues, Programmer: *programmer,
WasmAbi: *wasmAbi,
Programmer: *programmer,
OpenOCDCommands: ocdCommands,
LLVMFeatures: *llvmFeatures,
} }
if *printCommands {
options.PrintCommands = printCommand if *cFlags != "" {
options.CFlags = strings.Split(*cFlags, " ")
}
if *ldFlags != "" {
options.LDFlags = strings.Split(*ldFlags, " ")
} }
os.Setenv("CC", "clang -target="+*target) os.Setenv("CC", "clang -target="+*target)
err = options.Verify() err := options.Verify()
if err != nil { if err != nil {
fmt.Fprintln(os.Stderr, err.Error()) fmt.Fprintln(os.Stderr, err.Error())
usage() usage()
os.Exit(1) os.Exit(1)
} }
if *cpuprofile != "" {
f, err := os.Create(*cpuprofile)
if err != nil {
fmt.Fprintln(os.Stderr, "could not create CPU profile: ", err)
os.Exit(1)
}
defer f.Close()
if err := pprof.StartCPUProfile(f); err != nil {
fmt.Fprintln(os.Stderr, "could not start CPU profile: ", err)
os.Exit(1)
}
defer pprof.StopCPUProfile()
}
switch command { switch command {
case "build": case "build":
if outpath == "" { if outpath == "" {
@@ -1270,26 +983,16 @@ func main() {
err := Run(pkgName, options) err := Run(pkgName, options)
handleCompilerError(err) handleCompilerError(err)
case "test": case "test":
var pkgNames []string pkgName := "."
for i := 0; i < flag.NArg(); i++ { if flag.NArg() == 1 {
pkgNames = append(pkgNames, filepath.ToSlash(flag.Arg(i))) pkgName = filepath.ToSlash(flag.Arg(0))
} } else if flag.NArg() > 1 {
if len(pkgNames) == 0 { fmt.Fprintln(os.Stderr, "test only accepts a single positional argument: package name, but multiple were specified")
pkgNames = []string{"."} usage()
}
allTestsPassed := true
for _, pkgName := range pkgNames {
// TODO: parallelize building the test binaries
passed, err := Test(pkgName, options, *testCompileOnlyFlag, *testVerboseFlag, outpath)
handleCompilerError(err)
if !passed {
allTestsPassed = false
}
}
if !allTestsPassed {
fmt.Println("FAIL")
os.Exit(1) os.Exit(1)
} }
err := Test(pkgName, options, *testCompileOnlyFlag, outpath)
handleCompilerError(err)
case "targets": case "targets":
dir := filepath.Join(goenv.Get("TINYGOROOT"), "targets") dir := filepath.Join(goenv.Get("TINYGOROOT"), "targets")
entries, err := ioutil.ReadDir(dir) entries, err := ioutil.ReadDir(dir)
@@ -1364,9 +1067,6 @@ func main() {
if *flagDeps { if *flagDeps {
extraArgs = append(extraArgs, "-deps") extraArgs = append(extraArgs, "-deps")
} }
if *flagTest {
extraArgs = append(extraArgs, "-test")
}
cmd, err := loader.List(config, extraArgs, flag.Args()) cmd, err := loader.List(config, extraArgs, flag.Args())
if err != nil { if err != nil {
fmt.Fprintln(os.Stderr, "failed to run `go list`:", err) fmt.Fprintln(os.Stderr, "failed to run `go list`:", err)
+91 -184
View File
@@ -13,8 +13,8 @@ import (
"os/exec" "os/exec"
"path/filepath" "path/filepath"
"runtime" "runtime"
"sort"
"strings" "strings"
"sync"
"testing" "testing"
"time" "time"
@@ -28,52 +28,41 @@ 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",
"float.go",
"gc.go",
"goroutines.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",
"testing.go",
"zeroalloc.go",
} }
_, minor, err := goenv.GetGorootVersion(goenv.Get("GOROOT")) dirMatches, err := filepath.Glob(filepath.Join(TESTDATA, "*", "main.go"))
if err != nil { if err != nil {
t.Fatal("could not read version from GOROOT:", err) t.Fatal("could not read test packages:", err)
} }
if minor >= 17 { if len(matches) == 0 || len(dirMatches) == 0 {
tests = append(tests, "go1.17.go") 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)
if runtime.GOOS == "darwin" {
runTest("testdata/libc/filesystem.go", "", t,
nil, nil)
runTest("testdata/libc/env.go", "", t,
[]string{"ENV1=VALUE1", "ENV2=VALUE2"}, nil)
}
}) })
} }
@@ -82,135 +71,67 @@ 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)
})
t.Run("WebAssembly", func(t *testing.T) {
runPlatTests("wasm", tests, t)
}) })
goVersion, err := goenv.GorootVersionString(goenv.Get("GOROOT"))
if err != nil {
t.Error("could not get Go version:", err)
return
}
minorVersion := strings.Split(goVersion, ".")[1]
if minorVersion != "13" {
// WebAssembly tests fail on Go 1.13, so skip them there. Versions
// below that are also not supported but still seem to pass, so
// include them in the tests for now.
t.Run("WebAssembly", func(t *testing.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)
runTest("testdata/libc/env.go", "wasi", t,
[]string{"--env", "ENV1=VALUE1", "--env", "ENV2=VALUE2"}, nil)
runTest("testdata/libc/filesystem.go", "wasi", t, nil, []string{"--dir=."})
}) })
} }
// 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{
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, 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"})
})
if target == "" || target == "wasi" {
t.Run("filesystem.go", func(t *testing.T) {
t.Parallel()
runTest("filesystem.go", target, t, nil, nil)
})
}
if target == "" || target == "wasi" || target == "wasm" {
t.Run("rand.go", func(t *testing.T) {
t.Parallel()
runTest("rand.go", target, t, nil, nil)
}) })
} }
} }
// Due to some problems with LLD, we cannot run links in parallel, or in parallel with compiles. func runTest(path, target string, t *testing.T, environmentVars []string, additionalArgs []string) {
// Therefore, we put a lock around builds and run everything else in parallel.
var buildLock sync.Mutex
// runBuild is a thread-safe wrapper around Build.
func runBuild(src, out string, opts *compileopts.Options) error {
buildLock.Lock()
defer buildLock.Unlock()
return Build(src, out, opts)
}
func runTest(name, target string, t *testing.T, cmdArgs, environmentVars []string) {
options := compileopts.Options{
Target: target,
}
runTestWithConfig(name, target, t, options, cmdArgs, environmentVars)
}
func runTestWithConfig(name, target string, t *testing.T, options compileopts.Options, cmdArgs, environmentVars []string) {
// Set default config.
options.Debug = true
options.VerifyIR = true
if options.Opt == "" {
options.Opt = "z"
}
// 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)
@@ -219,78 +140,64 @@ func runTestWithConfig(name, target string, t *testing.T, options compileopts.Op
} }
// Create a temporary directory for test output files. // Create a temporary directory for test output files.
tmpdir := t.TempDir() tmpdir, err := ioutil.TempDir("", "tinygo-test")
// Determine whether we're on a system that supports environment variables
// and command line parameters (operating systems, WASI) or not (baremetal,
// WebAssembly in the browser). If we're on a system without an environment,
// we need to pass command line arguments and environment variables through
// global variables (built into the binary directly) instead of the
// conventional way.
spec, err := compileopts.LoadTarget(target)
if err != nil { if err != nil {
t.Fatal("failed to load target spec:", err) t.Fatal("could not create temporary directory:", err)
} }
needsEnvInVars := spec.GOOS == "js" defer func() {
for _, tag := range spec.BuildTags { rerr := os.RemoveAll(tmpdir)
if tag == "baremetal" { if rerr != nil {
needsEnvInVars = true t.Errorf("failed to remove temporary directory %q: %s", tmpdir, rerr.Error())
} }
} }()
if needsEnvInVars {
runtimeGlobals := make(map[string]string)
if len(cmdArgs) != 0 {
runtimeGlobals["osArgs"] = strings.Join(cmdArgs, "\x00")
}
if len(environmentVars) != 0 {
runtimeGlobals["osEnv"] = strings.Join(environmentVars, "\x00")
}
if len(runtimeGlobals) != 0 {
// This sets the global variables like they would be set with
// `-ldflags="-X=runtime.osArgs=first\x00second`.
// The runtime package has two variables (osArgs and osEnv) that are
// both strings, from which the parameters and environment variables
// are read.
options.GlobalValues = map[string]map[string]string{
"runtime": runtimeGlobals,
}
}
}
// 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 = Build("./"+path, binary, config)
if err != nil { if err != nil {
printCompilerError(t.Log, err) printCompilerError(t.Log, err)
t.Fail() t.Fail()
return return
} }
// Create the test command, taking care of emulators etc. // Run the test.
runComplete := make(chan struct{})
var cmd *exec.Cmd var cmd *exec.Cmd
if len(spec.Emulator) == 0 { ranTooLong := false
if target == "" {
cmd = exec.Command(binary) cmd = exec.Command(binary)
cmd.Env = append(cmd.Env, environmentVars...)
} else { } else {
args := append(spec.Emulator[1:], binary) spec, err := compileopts.LoadTarget(target)
cmd = exec.Command(spec.Emulator[0], args...) if err != nil {
} t.Fatal("failed to load target spec:", err)
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...) if len(spec.Emulator) == 0 {
} else { cmd = exec.Command(binary)
if !needsEnvInVars { } else {
cmd.Args = append(cmd.Args, cmdArgs...) // works on qemu-aarch64 etc args := append(spec.Emulator[1:], binary)
cmd = exec.Command(spec.Emulator[0], append(args, additionalArgs...)...)
}
if len(spec.Emulator) != 0 && spec.Emulator[0] == "wasmtime" {
for _, v := range environmentVars {
cmd.Args = append(cmd.Args, "--env", v)
}
} else {
cmd.Env = append(cmd.Env, environmentVars...) cmd.Env = append(cmd.Env, environmentVars...)
} }
} }
// Run the test.
runComplete := make(chan struct{})
ranTooLong := false
stdout := &bytes.Buffer{} stdout := &bytes.Buffer{}
cmd.Stdout = stdout cmd.Stdout = stdout
cmd.Stderr = os.Stderr cmd.Stderr = os.Stderr
-19
View File
@@ -1,19 +0,0 @@
// Copyright 2010 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Package rand implements a cryptographically secure
// random number generator.
package rand
import "io"
// Reader is a global, shared instance of a cryptographically
// secure random number generator.
var Reader io.Reader
// Read is a helper function that calls Reader.Read using io.ReadFull.
// On return, n == len(b) if and only if err == nil.
func Read(b []byte) (n int, err error) {
return io.ReadFull(Reader, b)
}
-30
View File
@@ -1,30 +0,0 @@
// +build darwin freebsd tinygo.wasm
// This implementation of crypto/rand uses the arc4random_buf function
// (available on both MacOS and WASI) to generate random numbers.
//
// Note: arc4random_buf (unlike what the name suggets) does not use the insecure
// RC4 cipher. Instead, it uses a high-quality cipher, varying by the libc
// implementation.
package rand
import "unsafe"
func init() {
Reader = &reader{}
}
type reader struct {
}
func (r *reader) Read(b []byte) (n int, err error) {
if len(b) != 0 {
libc_arc4random_buf(unsafe.Pointer(&b[0]), uint(len(b)))
}
return len(b), nil
}
// void arc4random_buf(void *buf, size_t buflen);
//export arc4random_buf
func libc_arc4random_buf(buf unsafe.Pointer, buflen uint)
-38
View File
@@ -1,38 +0,0 @@
// +build linux,!baremetal,!wasi
// This implementation of crypto/rand uses the /dev/urandom pseudo-file to
// generate random numbers.
// TODO: convert to the getentropy or getrandom libc function on Linux once it
// is more widely supported.
package rand
import (
"syscall"
)
func init() {
Reader = &reader{}
}
type reader struct {
fd int
}
func (r *reader) Read(b []byte) (n int, err error) {
if len(b) == 0 {
return
}
// Open /dev/urandom first if needed.
if r.fd == 0 {
fd, err := syscall.Open("/dev/urandom", syscall.O_RDONLY, 0)
if err != nil {
return 0, err
}
r.fd = fd
}
// Read from the file.
return syscall.Read(r.fd, b)
}
-143
View File
@@ -1,143 +0,0 @@
// Copyright 2011 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package rand
import (
"errors"
"io"
"math/big"
)
// smallPrimes is a list of small, prime numbers that allows us to rapidly
// exclude some fraction of composite candidates when searching for a random
// prime. This list is truncated at the point where smallPrimesProduct exceeds
// a uint64. It does not include two because we ensure that the candidates are
// odd by construction.
var smallPrimes = []uint8{
3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53,
}
// smallPrimesProduct is the product of the values in smallPrimes and allows us
// to reduce a candidate prime by this number and then determine whether it's
// coprime to all the elements of smallPrimes without further big.Int
// operations.
var smallPrimesProduct = new(big.Int).SetUint64(16294579238595022365)
// Prime returns a number, p, of the given size, such that p is prime
// with high probability.
// Prime will return error for any error returned by rand.Read or if bits < 2.
func Prime(rand io.Reader, bits int) (p *big.Int, err error) {
if bits < 2 {
err = errors.New("crypto/rand: prime size must be at least 2-bit")
return
}
b := uint(bits % 8)
if b == 0 {
b = 8
}
bytes := make([]byte, (bits+7)/8)
p = new(big.Int)
bigMod := new(big.Int)
for {
_, err = io.ReadFull(rand, bytes)
if err != nil {
return nil, err
}
// Clear bits in the first byte to make sure the candidate has a size <= bits.
bytes[0] &= uint8(int(1<<b) - 1)
// Don't let the value be too small, i.e, set the most significant two bits.
// Setting the top two bits, rather than just the top bit,
// means that when two of these values are multiplied together,
// the result isn't ever one bit short.
if b >= 2 {
bytes[0] |= 3 << (b - 2)
} else {
// Here b==1, because b cannot be zero.
bytes[0] |= 1
if len(bytes) > 1 {
bytes[1] |= 0x80
}
}
// Make the value odd since an even number this large certainly isn't prime.
bytes[len(bytes)-1] |= 1
p.SetBytes(bytes)
// Calculate the value mod the product of smallPrimes. If it's
// a multiple of any of these primes we add two until it isn't.
// The probability of overflowing is minimal and can be ignored
// because we still perform Miller-Rabin tests on the result.
bigMod.Mod(p, smallPrimesProduct)
mod := bigMod.Uint64()
NextDelta:
for delta := uint64(0); delta < 1<<20; delta += 2 {
m := mod + delta
for _, prime := range smallPrimes {
if m%uint64(prime) == 0 && (bits > 6 || m != uint64(prime)) {
continue NextDelta
}
}
if delta > 0 {
bigMod.SetUint64(delta)
p.Add(p, bigMod)
}
break
}
// There is a tiny possibility that, by adding delta, we caused
// the number to be one bit too long. Thus we check BitLen
// here.
if p.ProbablyPrime(20) && p.BitLen() == bits {
return
}
}
}
// Int returns a uniform random value in [0, max). It panics if max <= 0.
func Int(rand io.Reader, max *big.Int) (n *big.Int, err error) {
if max.Sign() <= 0 {
panic("crypto/rand: argument to Int is <= 0")
}
n = new(big.Int)
n.Sub(max, n.SetUint64(1))
// bitLen is the maximum bit length needed to encode a value < max.
bitLen := n.BitLen()
if bitLen == 0 {
// the only valid result is 0
return
}
// k is the maximum byte length needed to encode a value < max.
k := (bitLen + 7) / 8
// b is the number of bits in the most significant byte of max-1.
b := uint(bitLen % 8)
if b == 0 {
b = 8
}
bytes := make([]byte, k)
for {
_, err = io.ReadFull(rand, bytes)
if err != nil {
return nil, err
}
// Clear bits in the first byte to increase the probability
// that the candidate is < max.
bytes[0] &= uint8(int(1<<b) - 1)
n.SetBytes(bytes)
if n.Cmp(max) < 0 {
return
}
}
}
+6 -15
View File
@@ -26,21 +26,12 @@ type SCB_Type struct {
SHPR2 volatile.Register32 // 0xD1C: System Handler Priority Register 2 SHPR2 volatile.Register32 // 0xD1C: System Handler Priority Register 2
SHPR3 volatile.Register32 // 0xD20: System Handler Priority Register 3 SHPR3 volatile.Register32 // 0xD20: System Handler Priority Register 3
// the following are only applicable for Cortex-M3/M33/M4/M7 // the following are only applicable for Cortex-M3/M33/M4/M7
SHCSR volatile.Register32 // 0xD24: System Handler Control and State Register SHCSR volatile.Register32 // 0xD24: System Handler Control and State Register
CFSR volatile.Register32 // 0xD28: Configurable Fault Status Register CFSR volatile.Register32 // 0xD28: Configurable Fault Status Register
HFSR volatile.Register32 // 0xD2C: HardFault Status Register HFSR volatile.Register32 // 0xD2C: HardFault Status Register
DFSR volatile.Register32 // 0xD30: Debug Fault Status Register DFSR volatile.Register32 // 0xD30: Debug Fault Status Register
MMFAR volatile.Register32 // 0xD34: MemManage Fault Address Register MMFAR volatile.Register32 // 0xD34: MemManage Fault Address Register
BFAR volatile.Register32 // 0xD38: BusFault Address Register BFAR volatile.Register32 // 0xD38: BusFault Address Register
AFSR volatile.Register32 // 0xD3C: Auxiliary Fault Status Register
PFR [2]volatile.Register32 // 0xD40: Processor Feature Register
DFR volatile.Register32 // 0xD48: Debug Feature Register
ADR volatile.Register32 // 0xD4C: Auxiliary Feature Register
MMFR [4]volatile.Register32 // 0xD50: Memory Model Feature Register
ISAR [5]volatile.Register32 // 0xD60: Instruction Set Attributes Register
_ [5]uint32 // reserved
CPACR volatile.Register32 // 0xD88: Coprocessor Access Control Register
} }
var SCB = (*SCB_Type)(unsafe.Pointer(uintptr(SCB_BASE))) var SCB = (*SCB_Type)(unsafe.Pointer(uintptr(SCB_BASE)))
-49
View File
@@ -1,49 +0,0 @@
// This is a very minimal bootloader for the ESP32-C3. It only initializes the
// flash and then continues with the generic RISC-V initialization code, which
// in turn will call runtime.main.
// It is written in assembly (and not in a higher level language) to make sure
// it is entirely loaded into IRAM and doesn't accidentally call functions
// stored in IROM.
//
// For reference, here is a nice introduction into RISC-V assembly:
// https://www.imperialviolet.org/2016/12/31/riscv.html
.section .init
.global call_start_cpu0
.type call_start_cpu0,@function
call_start_cpu0:
// At this point:
// - The ROM bootloader is finished and has jumped to here.
// - We're running from IRAM: both IRAM and DRAM segments have been loaded
// by the ROM bootloader.
// - We have a usable stack (but not the one we would like to use).
// - No flash mappings (MMU) are set up yet.
// Reset MMU, see bootloader_reset_mmu in the ESP-IDF.
call Cache_Suspend_ICache
mv s0, a0 // autoload value
call Cache_Invalidate_ICache_All
call Cache_MMU_Init
// Set up DROM from flash.
// Somehow, this also sets up IROM from flash. Not sure why, but it avoids
// the need for another such call.
// C equivalent:
// Cache_Dbus_MMU_Set(MMU_ACCESS_FLASH, 0x3C00_0000, 0, 64, 128, 0)
li a0, 0 // ext_ram: MMU_ACCESS_FLASH
li a1, 0x3C000000 // vaddr: address in the data bus
li a2, 0 // paddr: physical address in the flash chip
li a3, 64 // psize: always 64 (kilobytes)
li a4, 128 // num: pages to be set (8192K / 64K = 128)
li a5, 0 // fixed
call Cache_Dbus_MMU_Set
// Enable the flash cache.
mv a0, s0 // restore autoload value from Cache_Suspend_ICache call
call Cache_Resume_ICache
// Jump to generic RISC-V initialization, which initializes the stack
// pointer and globals register. It should not return.
// (It appears that the linker relaxes this jump and instead inserts the
// _start function right after here).
j _start
+14 -16
View File
@@ -2,7 +2,6 @@
// Type definitions, fields, and constants associated with the MPU peripheral // Type definitions, fields, and constants associated with the MPU peripheral
// of the NXP MIMXRT1062. // of the NXP MIMXRT1062.
//go:build nxp && mimxrt1062
// +build nxp,mimxrt1062 // +build nxp,mimxrt1062
package nxp package nxp
@@ -217,12 +216,6 @@ func enableIcache(enable bool) {
} }
} }
var (
dcacheCcsidr volatile.Register32
dcacheSets volatile.Register32
dcacheWays volatile.Register32
)
func enableDcache(enable bool) { func enableDcache(enable bool) {
if enable != SystemControl.CCR.HasBits(SCB_CCR_DC_Msk) { if enable != SystemControl.CCR.HasBits(SCB_CCR_DC_Msk) {
if enable { if enable {
@@ -251,6 +244,11 @@ func enableDcache(enable bool) {
isb 0xF isb 0xF
`, nil) `, nil)
} else { } else {
var (
ccsidr volatile.Register32
sets volatile.Register32
ways volatile.Register32
)
SystemControl.CSSELR.Set(0) SystemControl.CSSELR.Set(0)
arm.AsmFull(` arm.AsmFull(`
dsb 0xF dsb 0xF
@@ -259,17 +257,17 @@ func enableDcache(enable bool) {
arm.AsmFull(` arm.AsmFull(`
dsb 0xF dsb 0xF
`, nil) `, nil)
dcacheCcsidr.Set(SystemControl.CCSIDR.Get()) ccsidr.Set(SystemControl.CCSIDR.Get())
dcacheSets.Set((dcacheCcsidr.Get() & SCB_CCSIDR_NUMSETS_Msk) >> SCB_CCSIDR_NUMSETS_Pos) sets.Set((ccsidr.Get() & SCB_CCSIDR_NUMSETS_Msk) >> SCB_CCSIDR_NUMSETS_Pos)
for dcacheSets.Get() != 0 { for sets.Get() != 0 {
dcacheWays.Set((dcacheCcsidr.Get() & SCB_CCSIDR_ASSOCIATIVITY_Msk) >> SCB_CCSIDR_ASSOCIATIVITY_Pos) ways.Set((ccsidr.Get() & SCB_CCSIDR_ASSOCIATIVITY_Msk) >> SCB_CCSIDR_ASSOCIATIVITY_Pos)
for dcacheWays.Get() != 0 { for ways.Get() != 0 {
SystemControl.DCCISW.Set( SystemControl.DCCISW.Set(
((dcacheSets.Get() << SCB_DCCISW_SET_Pos) & SCB_DCCISW_SET_Msk) | ((sets.Get() << SCB_DCCISW_SET_Pos) & SCB_DCCISW_SET_Msk) |
((dcacheWays.Get() << SCB_DCCISW_WAY_Pos) & SCB_DCCISW_WAY_Msk)) ((ways.Get() << SCB_DCCISW_WAY_Pos) & SCB_DCCISW_WAY_Msk))
dcacheWays.Set(dcacheWays.Get() - 1) ways.Set(ways.Get() - 1)
} }
dcacheSets.Set(dcacheSets.Get() - 1) sets.Set(sets.Get() - 1)
} }
arm.AsmFull(` arm.AsmFull(`
dsb 0xF dsb 0xF
-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
)
-15
View File
@@ -1,15 +0,0 @@
// +build feather_m4_can
package main
import (
"machine"
)
func init() {
// power on the CAN Transceiver
// https://learn.adafruit.com/adafruit-feather-m4-can-express/pinouts#can-bus-3078990-8
boost_en := machine.BOOST_EN
boost_en.Configure(machine.PinConfig{Mode: machine.PinOutput})
boost_en.High()
}
-53
View File
@@ -1,53 +0,0 @@
package main
import (
"fmt"
"machine"
"time"
)
func main() {
can1 := machine.CAN1
can1.Configure(machine.CANConfig{
TransferRate: machine.CANTransferRate500kbps,
TransferRateFD: machine.CANTransferRate1000kbps,
Rx: machine.CAN1_RX,
Tx: machine.CAN1_TX,
Standby: machine.CAN1_STANDBY,
})
can0 := machine.CAN0
can0.Configure(machine.CANConfig{
TransferRate: machine.CANTransferRate500kbps,
TransferRateFD: machine.CANTransferRate1000kbps,
Rx: machine.CAN0_RX,
Tx: machine.CAN0_TX,
Standby: machine.NoPin,
})
rxMsg := machine.CANRxBufferElement{}
for {
can1.Tx(0x123, []byte{0x01, 0x23, 0x45, 0x67, 0x89, 0xAB, 0xCD, 0xEF}, false, false)
can1.Tx(0x789, []byte{0x02, 0x24, 0x46, 0x67, 0x89, 0xAB, 0xCD, 0xEF, 0x01, 0x23, 0x45, 0x67, 0x89, 0xAB, 0xCD, 0xEF}, true, false)
time.Sleep(time.Millisecond * 1000)
sz0 := can0.RxFifoSize()
if sz0 > 0 {
fmt.Printf("CAN0 %d\r\n", sz0)
for i := 0; i < sz0; i++ {
can0.RxRaw(&rxMsg)
fmt.Printf("-> %08X %X %#v\r\n", rxMsg.ID, rxMsg.DLC, rxMsg.Data())
}
}
sz1 := can1.RxFifoSize()
if sz1 > 0 {
fmt.Printf("CAN1 %d\r\n", sz1)
for i := 0; i < sz1; i++ {
can1.RxRaw(&rxMsg)
fmt.Printf("-> %08X %X %#v\r\n", rxMsg.ID, rxMsg.DLC, rxMsg.Data())
}
}
}
}
@@ -1,15 +0,0 @@
// +build feather_m4_can
package main
import (
"machine"
)
func init() {
// power on the CAN Transceiver
// https://learn.adafruit.com/adafruit-feather-m4-can-express/pinouts#can-bus-3078990-8
boost_en := machine.BOOST_EN
boost_en.Configure(machine.PinConfig{Mode: machine.PinOutput})
boost_en.High()
}
-75
View File
@@ -1,75 +0,0 @@
package main
import (
"device/sam"
"fmt"
"machine"
"time"
)
type canMsg struct {
ch byte
id uint32
dlc byte
data []byte
}
func main() {
ch := make(chan canMsg, 10)
go func() {
for {
select {
case m := <-ch:
fmt.Printf("%d %03X %X ", m.ch, m.id, m.dlc)
for _, d := range m.data {
fmt.Printf("%02X ", d)
}
fmt.Printf("\r\n")
}
}
}()
can1 := machine.CAN1
can1.Configure(machine.CANConfig{
TransferRate: machine.CANTransferRate500kbps,
TransferRateFD: machine.CANTransferRate1000kbps,
Rx: machine.CAN1_RX,
Tx: machine.CAN1_TX,
Standby: machine.CAN1_STANDBY,
})
// RF0NE : Rx FIFO 0 New Message Interrupt Enable
can1.SetInterrupt(sam.CAN_IE_RF0NE, func(*machine.CAN) {
rxMsg := machine.CANRxBufferElement{}
can1.RxRaw(&rxMsg)
m := canMsg{ch: 1, id: rxMsg.ID, dlc: rxMsg.DLC, data: rxMsg.Data()}
select {
case ch <- m:
}
})
can0 := machine.CAN0
can0.Configure(machine.CANConfig{
TransferRate: machine.CANTransferRate500kbps,
TransferRateFD: machine.CANTransferRate1000kbps,
Rx: machine.CAN0_RX,
Tx: machine.CAN0_TX,
Standby: machine.NoPin,
})
// RF0NE : Rx FIFO 0 New Message Interrupt Enable
can0.SetInterrupt(sam.CAN_IE_RF0NE, func(*machine.CAN) {
rxMsg := machine.CANRxBufferElement{}
can0.RxRaw(&rxMsg)
m := canMsg{ch: 2, id: rxMsg.ID, dlc: rxMsg.DLC, data: rxMsg.Data()}
select {
case ch <- m:
}
})
for {
can0.Tx(0x123, []byte{0x01, 0x23, 0x45, 0x67, 0x89, 0xAB, 0xCD, 0xEF}, false, false)
time.Sleep(time.Millisecond * 500)
can1.Tx(0x456, []byte{0xAA, 0xBB, 0xCC}, false, false)
time.Sleep(time.Millisecond * 1000)
}
}

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